Golang編寫自定義IP限流中間件的方法詳解
基于令牌桶的限流算法
- r:每秒鐘向桶內(nèi)放入 r 個(gè)令牌,即每隔 1/r秒放一個(gè)令牌
- b:桶的最大容量是 b,桶滿后試圖再放入的令牌會(huì)被丟棄掉
- 當(dāng)有人請(qǐng)求 n 個(gè)令牌時(shí),如果桶中的令牌數(shù)小于n,則請(qǐng)求放阻塞或直接放棄,否則順利從桶中取走 n 個(gè)令牌
- 當(dāng) b > 1 時(shí),任意 1/r 秒內(nèi)最多可以取走 b 個(gè)令牌
- 限制很短的一個(gè)瞬間的一個(gè)最高的并發(fā)量,b=最高的并發(fā)量,r=最短的時(shí)間段
- 當(dāng) b = 1 時(shí),每秒鐘最多可被取走 r 個(gè)令牌
- 限制每秒鐘的最高的QPS:b=1,r=最高的QPS
- 限制每分鐘的最高請(qǐng)求量:把每分鐘的請(qǐng)求量/60=轉(zhuǎn)換成1秒鐘,賦給 r 就可以了
- 限制每5分鐘、每10分鐘,同理
- 當(dāng) b > 1 時(shí),任意 1/r 秒內(nèi)最多可以取走 b 個(gè)令牌
實(shí)現(xiàn)高并發(fā)限流(使用golang官方限流器)
Go代碼
源碼地址: GitHub-golang版本
middleware/rateLimiterMiddleware.go
package middleware import ( "net/http" "time" "github.com/gin-gonic/gin" "golang.org/x/time/rate" ) var Limiter *rate.Limiter // 定義一個(gè)中間件函數(shù)來進(jìn)行限流 func RateLimiterMiddleware() gin.HandlerFunc { return func(c *gin.Context) { if !Limiter.AllowN(time.Now(), 1) { c.JSON(http.StatusTooManyRequests, gin.H{"message": "Rate limit exceeded"}) // 設(shè)置休眠和業(yè)務(wù)時(shí)長(zhǎng)一樣,為了更好從日志出看出規(guī)則 time.Sleep(50 * time.Millisecond) c.Abort() return } c.Next() } }
main.go
func main() { r := gin.Default() // 創(chuàng)建一個(gè)限流器,每秒允許最多10個(gè)請(qǐng)求 middleware.Limiter = rate.NewLimiter(rate.Limit(10), 1) // 使用限流中間件 r.Use(middleware.RateLimiterMiddleware()) r.GET("/api/resource", func(c *gin.Context) { time.Sleep(50 * time.Millisecond) c.JSON(http.StatusOK, gin.H{"message": "Resource accessed"}) }) r.Run(":8080") }
測(cè)試記錄
ab -t 1 -c 1 http://127.0.0.1:8080/api/resource
使用ab壓力測(cè)試,并發(fā)量為1的(相當(dāng)于串行),在1秒內(nèi)不斷發(fā)出請(qǐng)求(算下來,每個(gè)請(qǐng)求50ms,總共能發(fā)出20個(gè)請(qǐng)求)
結(jié)果預(yù)測(cè):1秒內(nèi)最多生成10個(gè)令牌,而總共有20個(gè)串行的請(qǐng)求,結(jié)果應(yīng)該是1個(gè)成功(在50ms結(jié)束),1個(gè)失?。ê?0ms內(nèi)還未有新的令牌生成),1個(gè)成功,1個(gè)失敗。。。 結(jié)果輸出(符合預(yù)期)
升級(jí):根據(jù)每個(gè)IP地址進(jìn)行限流
Go代碼
源碼地址: GitHub-golang版本
middleware/ipRateLimiterMiddleware.go
package middleware import ( "net/http" "sync" "time" "github.com/gin-gonic/gin" "golang.org/x/time/rate" ) var IPLimiter *IPRateLimiter func NewIPRateLimiter() *IPRateLimiter { return &IPRateLimiter{ limiter: make(map[string]*rate.Limiter), } } type IPRateLimiter struct { mu sync.Mutex limiter map[string]*rate.Limiter } func (i *IPRateLimiter) GetLimiter(ip string) *rate.Limiter { i.mu.Lock() defer i.mu.Unlock() limiter, exists := i.limiter[ip] if !exists { limiter = rate.NewLimiter(2, 5) // 每秒2個(gè)請(qǐng)求,桶容量為5 i.limiter[ip] = limiter } return limiter } // 定義一個(gè)中間件函數(shù)來進(jìn)行限流 func IPRateLimiterMiddleware() gin.HandlerFunc { return func(c *gin.Context) { ip := c.ClientIP() limiter := IPLimiter.GetLimiter(ip) if !limiter.Allow() { c.JSON(http.StatusTooManyRequests, gin.H{"message": "Rate limit exceeded"}) // 設(shè)置休眠和業(yè)務(wù)時(shí)長(zhǎng)一樣,為了更好從日志出看出規(guī)則 time.Sleep(50 * time.Millisecond) c.Abort() return } c.Next() } }
main.go
func main() { r := gin.Default() // 創(chuàng)建IP限流器 middleware.IPLimiter = middleware.NewIPRateLimiter() // 使用限流中間件 r.Use(middleware.IPRateLimiterMiddleware()) r.GET("/api/resource", func(c *gin.Context) { time.Sleep(50 * time.Millisecond) c.JSON(http.StatusOK, gin.H{"message": "Resource accessed"}) }) r.Run(":8080") }
測(cè)試記錄
ab -t 1 -c 1 http://127.0.0.1:8080/api/resource
使用ab壓力測(cè)試,并發(fā)量為1的(相當(dāng)于串行),在1秒內(nèi)不斷發(fā)出請(qǐng)求(算下來,每個(gè)請(qǐng)求50ms,總共能發(fā)出20個(gè)請(qǐng)求)
結(jié)果預(yù)測(cè):1秒內(nèi)最多生成2個(gè)令牌,桶容量為5 代表在1/2秒內(nèi)的最大并發(fā)量是5,總共有20個(gè)串行的請(qǐng)求,結(jié)果應(yīng)該是先成功5個(gè)(桶容量全使用成功), 之后剩余成功的是1個(gè),其余全部失敗
- 當(dāng)程序啟動(dòng)時(shí),限流器初始化,桶是空的,沒有令牌。
- 在每秒的前兩次令牌生成中,每次生成2個(gè)令牌,并放入桶中。
- 在第3秒,生成的2個(gè)令牌中只有1個(gè)能夠被放入桶中,因?yàn)橥耙呀?jīng)滿了。
- 當(dāng)使用ab發(fā)出請(qǐng)求時(shí),前5個(gè)請(qǐng)求依次拿到了令牌并成功,每個(gè)請(qǐng)求耗時(shí)50ms。此時(shí)總耗時(shí)為250ms。
- 第6個(gè)請(qǐng)求在300ms,第7個(gè)請(qǐng)求在350ms,第8個(gè)請(qǐng)求在400ms,第9個(gè)請(qǐng)求在450ms,第10個(gè)請(qǐng)求在500ms。
- 因?yàn)橄蘖髌鞯乃俾适敲棵?個(gè)請(qǐng)求,也就是每500ms生成一個(gè)令牌。 第500ms時(shí)新的令牌生成,所以第6-10請(qǐng)求期間,是沒有令牌的,所以都請(qǐng)求失敗,而11個(gè)請(qǐng)求時(shí)有新令牌生成,所以能成功。
- 之后的請(qǐng)求以此類推,需要等待新令牌生成。
結(jié)果輸出(符合預(yù)期)
以上就是Golang編寫自定義IP限流中間件的方法詳解的詳細(xì)內(nèi)容,更多關(guān)于Golang編寫IP限流中間件的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
golang 結(jié)構(gòu)體初始化時(shí)賦值格式介紹
這篇文章主要介紹了golang 結(jié)構(gòu)體初始化時(shí)賦值格式介紹,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過來看看吧2020-12-12詳解Go并發(fā)編程時(shí)如何避免發(fā)生競(jìng)態(tài)條件和數(shù)據(jù)競(jìng)爭(zhēng)
大家都知道,Go是一種支持并發(fā)編程的編程語言,但并發(fā)編程也是比較復(fù)雜和容易出錯(cuò)的。比如本篇分享的問題:競(jìng)態(tài)條件和數(shù)據(jù)競(jìng)爭(zhēng)的問題2023-04-04細(xì)說Go語言中空結(jié)構(gòu)體的奇妙用途
Go語言中,我們可以定義空結(jié)構(gòu)體,即沒有任何成員變量的結(jié)構(gòu)體,使用關(guān)鍵字?struct{}?來表示。這種結(jié)構(gòu)體似乎沒有任何用處,但實(shí)際上它在?Go?語言中的應(yīng)用非常廣泛,本文就來詳解講講2023-05-05Go實(shí)現(xiàn)數(shù)據(jù)脫敏的方案設(shè)計(jì)
在一些常見的業(yè)務(wù)場(chǎng)景中可能涉及到用戶的手機(jī)號(hào),銀行卡號(hào)等敏感數(shù)據(jù),對(duì)于這部分的數(shù)據(jù)經(jīng)常需要進(jìn)行數(shù)據(jù)脫敏處理,就是將此部分?jǐn)?shù)據(jù)隱私化,防止數(shù)據(jù)泄露,所以本文給大家介紹了Go實(shí)現(xiàn)數(shù)據(jù)脫敏的方案設(shè)計(jì),需要的朋友可以參考下2024-05-05Golang迭代如何在Go中循環(huán)數(shù)據(jù)結(jié)構(gòu)使用詳解
這篇文章主要為大家介紹了Golang迭代之如何在Go中循環(huán)數(shù)據(jù)結(jié)構(gòu)使用詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-10-10Go結(jié)合JavaScript實(shí)現(xiàn)抓取網(wǎng)頁中的圖像鏈接
這篇文章主要為大家詳細(xì)介紹了Go語言如何結(jié)合JavaScript實(shí)現(xiàn)抓取網(wǎng)頁中的圖像鏈接,文中的示例代碼講解詳細(xì),有需要的小伙伴可以跟隨小編一起學(xué)習(xí)一下2023-11-11