Golang編寫(xiě)自定義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ù)來(lái)進(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)求(算下來(lái),每個(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ù)來(lái)進(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)求(算下來(lái),每個(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í),限流器初始化,桶是空的,沒(méi)有令牌。
- 在每秒的前兩次令牌生成中,每次生成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)求期間,是沒(méi)有令牌的,所以都請(qǐng)求失敗,而11個(gè)請(qǐng)求時(shí)有新令牌生成,所以能成功。
- 之后的請(qǐng)求以此類推,需要等待新令牌生成。
結(jié)果輸出(符合預(yù)期)


以上就是Golang編寫(xiě)自定義IP限流中間件的方法詳解的詳細(xì)內(nèi)容,更多關(guān)于Golang編寫(xiě)IP限流中間件的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
golang 結(jié)構(gòu)體初始化時(shí)賦值格式介紹
這篇文章主要介紹了golang 結(jié)構(gòu)體初始化時(shí)賦值格式介紹,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧2020-12-12
詳解Go并發(fā)編程時(shí)如何避免發(fā)生競(jìng)態(tài)條件和數(shù)據(jù)競(jìng)爭(zhēng)
大家都知道,Go是一種支持并發(fā)編程的編程語(yǔ)言,但并發(fā)編程也是比較復(fù)雜和容易出錯(cuò)的。比如本篇分享的問(wèn)題:競(jìng)態(tài)條件和數(shù)據(jù)競(jìng)爭(zhēng)的問(wèn)題2023-04-04
細(xì)說(shuō)Go語(yǔ)言中空結(jié)構(gòu)體的奇妙用途
Go語(yǔ)言中,我們可以定義空結(jié)構(gòu)體,即沒(méi)有任何成員變量的結(jié)構(gòu)體,使用關(guān)鍵字?struct{}?來(lái)表示。這種結(jié)構(gòu)體似乎沒(méi)有任何用處,但實(shí)際上它在?Go?語(yǔ)言中的應(yīng)用非常廣泛,本文就來(lái)詳解講講2023-05-05
Go實(shí)現(xiàn)數(shù)據(jù)脫敏的方案設(shè)計(jì)
在一些常見(jiàn)的業(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-05
Golang迭代如何在Go中循環(huán)數(shù)據(jù)結(jié)構(gòu)使用詳解
這篇文章主要為大家介紹了Golang迭代之如何在Go中循環(huán)數(shù)據(jù)結(jié)構(gòu)使用詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-10-10
Go結(jié)合JavaScript實(shí)現(xiàn)抓取網(wǎng)頁(yè)中的圖像鏈接
這篇文章主要為大家詳細(xì)介紹了Go語(yǔ)言如何結(jié)合JavaScript實(shí)現(xiàn)抓取網(wǎng)頁(yè)中的圖像鏈接,文中的示例代碼講解詳細(xì),有需要的小伙伴可以跟隨小編一起學(xué)習(xí)一下2023-11-11

