Golang限流技术详解
发表时间: 2024-06-11 16:05
限流与熔断降级
限流的意义
高并发系统三大利器: 缓存、降级、限流
缓存: 提升系统访问速度和增大处理容量, 为相应业务增加缓存。
降级: 当服务器压力剧增时, 根据业务策略降级, 以此释放服务资源保证业务正常。
限流: 通过对并发限速, 以达到拒绝服务、排队或等待、降级等处理
限流分类
漏桶限流
每次请求时计算桶流量, 超过阈值则降级请求
令牌桶限流
每次请求时从令牌桶里取令牌, 取不到则降级请求
time/rate 限速器使用(漏桶)
"golang.org/x/time/rate"
rate.NewLimiter(limit, burst)
limit表示每秒产生token数、burst最多存token数
Allow 判断当前是否可以取到token
Wait 阻塞等待直到取到token
Reserve 返回等待时间, 再去取token
【实例】:
package mainimport ( "context" "golang.org/x/time/rate" "log" "testing" "time")func Test_RateLimiter(t *testing.T) { l := rate.NewLimiter(1, 5) log.Println(l.Limit(), l.Burst()) for i := 0; i < 100; i++ { //阻塞等待直到,取到一个token log.Println("before Wait") c, _ := context.WithTimeout(context.Background(), time.Second*2) if err := l.Wait(c); err != nil { log.Println("limiter wait err:" + err.Error()) } log.Println("after Wait") //返回需要等待多久才有新的token,这样就可以等待指定时间执行任务 r := l.Reserve() log.Println("reserve Delay:", r.Delay()) //判断当前是否可以取到token a := l.Allow() log.Println("Allow:", a) }}
测试方法:
在文件所在目录中执行:
go test
测试单个文件
go test -v main_test.go
测试单个函数
go test -v main_test.go -test.run Test_RateLimiter
【实例】:
package mainimport ( "fmt" "sync" "time" //"golang.org/x/time/rate")type Bucket struct { cap int64 // 总容量 lock sync.Mutex // 互斥锁 不需要在初始化中赋值 tokens int64 rate int64 // 速率 每秒产生多少token lastTime int64 // 记录时间}func (b *Bucket) Allow() bool { b.lock.Lock() defer b.lock.Unlock() now := time.Now().Unix() // 当前时间 // (当前时间 - 上次记录时间)*每秒产生 b.tokens = b.tokens + (now-b.lastTime)*b.rate if b.tokens > b.cap { b.tokens = b.cap } b.lastTime = now if b.tokens > 0 { return true } return false}// 添加令牌的操作func (b *Bucket) addToken() { b.lock.Lock() defer b.lock.Unlock() if b.tokens+b.rate <= b.cap { // 4+3> b.tokens += b.rate } else { b.tokens = b.cap } b.lastTime = time.Now().Unix() // 当前时间}// 启动一个协程不停的处理func (b *Bucket) start() { // newBucket的时候 启动start 协程 for { time.Sleep(time.Second) b.addToken() }}func main() { // 方法一:原生使用令牌的方法 /* // 创建一个每秒产生5个令牌的令牌桶 limiter := rate.NewLimiter(5, 1) for i := 0; i < 10; i++ { // 尝试获取一个令牌 if limiter.Allow() { fmt.Println("处理请求", i) } else { fmt.Println("限流请求", i) } // 模拟请求处理时间 time.Sleep(time.Second) } */ // 方法二: 使用协程 limiter := Bucket{ cap: 10, tokens: 5, rate: 1, } // 使用协程的方式添加表单令牌 go limiter.start() for i := 0; i < 10; i++ { // 尝试获取一个令牌 if limiter.Allow() { fmt.Println("处理请求", i) } else { fmt.Println("限流请求", i) } // 模拟请求处理时间 time.Sleep(time.Second) }}