服務限流
在突發的流量下,通過限制用戶訪問的流量,保證服務能夠正常運行
常見的限流思路
- 排隊
- 應用場景:秒殺搶購,用戶點擊搶購之后,進行排隊,直到搶到或售罄為止
- 拒絕
- 應用場景:除秒殺之外的任何場景
限流算法
- 計數器限流算法
- 漏桶限流算法
- 令牌桶限流算法
計數器限流算法
- 在單位時間內進行計數,如果大於設置的最大值,則進行拒絕
- 如果過了單位時間,則重新進行計數
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) } }
優點
不限制流速, 能夠應對突發流量