服务限流
在突发的流量下,通过限制用户访问的流量,保证服务能够正常运行
常见的限流思路
- 排队
- 应用场景:秒杀抢购,用户点击抢购之后,进行排队,直到抢到或售罄为止
- 拒绝
- 应用场景:除秒杀之外的任何场景
限流算法
- 计数器限流算法
- 漏桶限流算法
- 令牌桶限流算法
计数器限流算法
- 在单位时间内进行计数,如果大于设置的最大值,则进行拒绝
- 如果过了单位时间,则重新进行计数
package main import ( "fmt" "sync/atomic" "time" ) type CounterLimit struct { counter int64 //计数器 limit int64 //指定时间窗口内允许的最大请求数 intervalNano int64 //指定的时间窗口 unixNano int64 //unix时间戳,单位为纳秒 } func NewCounterLimit(interval time.Duration, limit int64) *CounterLimit { return &CounterLimit{ counter: 0, limit: limit, intervalNano: int64(interval), unixNano: time.Now().UnixNano(), } } func (c *CounterLimit) Allow() bool { now := time.Now().UnixNano() if now-c.unixNano > c.intervalNano { //如果当前过了当前的时间窗口,则重新进行计数 atomic.StoreInt64(&c.counter, 0) atomic.StoreInt64(&c.unixNano, now) return true } atomic.AddInt64(&c.counter, 1) return c.counter < c.limit //判断是否要进行限流 } func main() { limit := NewCounterLimit(time.Second, 100) m := make(map[int]bool) for i := 0; i < 1000; i++ { allow := limit.Allow() if allow { //fmt.Printf("i=%d is allow\n", i) m[i] = true } else { //fmt.Printf("i=%d is not allow\n", i) m[i] = false } } for i := 0; i < 1000; i++ { fmt.Printf("i=%d allow=%v\n", i, m[i]) } }
计数器限流算法
优点:
实现非常简单
缺点:
突发流量会出现毛刺现象
比如一秒限流100个请求, 前100ms内处理完了100个请求,后900ms时间内没有请求处理
计数不准确
漏桶限流算法
- 一个固定大小的水桶
- 以固定速率流出
- 水桶满了,则进行溢出(拒绝)
package main import ( "fmt" "math" "time" ) type BucketLimit struct { rate float64 //漏桶中水的漏出速率 bucketSize float64 //漏桶最多能装的水大小 unixNano int64 //unix时间戳 curWater float64 //当前桶里面的水 } func NewBucketLimit(rate float64, bucketSize int64) *BucketLimit { return &BucketLimit{ bucketSize: float64(bucketSize), rate: rate, unixNano: time.Now().UnixNano(), curWater: 0, } } func (b *BucketLimit) reflesh() { now := time.Now().UnixNano() //时间差, 把纳秒换成秒 diffSec := float64(now-b.unixNano) / 1000 / 1000 / 1000 b.curWater = math.Max(0, b.curWater-diffSec*b.rate) b.unixNano = now return } func (b *BucketLimit) Allow() bool { b.reflesh() if b.curWater < b.bucketSize { b.curWater = b.curWater + 1 return true } return false } func main() { //限速50qps, 桶大小100 limit := NewBucketLimit(50, 100) m := make(map[int]bool) for i := 0; i < 1000; i++ { allow := limit.Allow() if allow { m[i] = true continue } m[i] = false time.Sleep(time.Millisecond * 10) } for i := 0; i < 1000; i++ { fmt.Printf("i=%d allow=%v\n", i, m[i]) } }
漏桶限流算法
优点
- 解决了计数器限流算法的毛刺问题
- 整体流量控制的比较平稳
缺点
- 无法应对某些突发的流量
令牌桶限流算法
- 一个固定大小的水桶
- 以固定速率放入token
- 如果能够拿到token则处理,否则拒绝
package main import ( "fmt" "golang.org/x/time/rate" ) func main() { //限速50qps, 桶大小100 limit := rate.NewLimiter(50, 100) for i := 0; i < 1000; i++ { allow := limit.Allow() if allow { fmt.Printf("i=%d is allow\n", i) continue } fmt.Printf("i=%d is not allow\n", i) } }
优点
不限制流速, 能够应对突发流量
本文来自博客园,作者:孙龙-程序员,转载请注明原文链接:https://www.cnblogs.com/sunlong88/p/13622185.html