轉自:https://zhuanlan.zhihu.com/p/354064637
流量限制的手段有很多,最常見的:漏桶、令牌桶兩種:
- 漏桶是指我們有一個一直裝滿了水的桶,每過固定的一段時間即向外漏一滴水。如果你接到了這滴水,那么你就可以繼續服務請求,如果沒有接到,那么就需要等待下一滴水。
- 令牌桶則是指勻速向桶中添加令牌,服務請求時需要從桶中獲取令牌,令牌的數目可以按照需要消耗的資源進行相應的調整。如果沒有令牌,可以選擇等待,或者放棄。
這兩種方法看起來很像,不過還是有區別的。漏桶流出的速率固定,而令牌桶只要在桶中有令牌,那就可以拿。也就是說令牌桶是允許一定程度的并發的,比如同一個時刻,有100個用戶請求,只要令牌桶中有100個令牌,那么這100個請求全都會放過去。令牌桶在桶中沒有令牌的情況下也會退化為漏桶模型,實際應用中令牌桶應用較為廣泛。
令牌桶算法的原理是系統以恒定的速率產生令牌,然后把令牌放到令牌桶中,令牌桶有一個容量,當令牌桶滿了的時候,再向其中放令牌,那么多余的令牌會被丟棄;當想要處理一個請求的時候,需要從令牌桶中取出一個令牌,如果此時令牌桶中沒有令牌,那么則拒絕該請求。
對于令牌桶的Go語言實現,可以參照 github.com/juju/ratelimit 庫。
- Gin 限流中間件
這里使用令牌桶作為限流策略,編寫一個限流中間件如下:
package main
import (
"net/http"
"time"
"github.com/gin-gonic/gin"
"github.com/juju/ratelimit"
)
func RateLimitMiddleware(fillInterval time.Duration, cap, quantum int64) gin.HandlerFunc {
bucket := ratelimit.NewBucketWithQuantum(fillInterval, cap, quantum)
return func(c *gin.Context) {
if bucket.TakeAvailable(1) < 1 {
c.String(http.StatusForbidden, "rate limit...")
c.Abort()
return
}
c.Next()
}
}
func main() {
r := gin.Default()
gin.ForceConsoleColor()
r.Use(RateLimitMiddleware(time.Second, 100, 100))
r.GET("/", func(c *gin.Context) {
c.String(http.StatusOK, "golang ~")
})
r.Run(":8080")
}
*1. *令牌桶初始化后里面就有 100 個令牌
2. 每秒鐘會產生 100 個令牌, 保證每秒最多有 100 個請求通過限流器, 也就是說 QPS 的上限是 100
3. 流量過大時能夠啟動限流, 在限流過程中, 仍然能讓部分流量通過
github.com/juju/ratelimit 提供了幾種不同特色的令牌桶填充方式:
- 默認的令牌桶,fillInterval 指每過多長時間向桶里放一個令牌,capacity 是桶的容量,超過桶容量的部分會被直接丟棄。桶初始是滿的。
func NewBucket(fillInterval time.Duration, capacity int64) *Bucket
- 和普通的 NewBucket() 的區別是,每次向桶中放令牌時,是放 quantum 個令牌,而不是一個令牌。
func NewBucketWithQuantum(fillInterval time.Duration, capacity, quantum int64) *Bucket
- 這個就有點特殊了,會按照提供的比例,每秒鐘填充令牌數。例如 capacity 是100,而 rate 是 0.1,那么每秒會填充10個令牌。
func NewBucketWithRate(rate float64, capacity int64) *Bucket