網站首頁 編程語言 正文
限流器是提升服務穩定性的非常重要的組件,可以用來限制請求速率,保護服務,以免服務過載。限流器的實現方法有很多種,常見的限流算法有固定窗口、滑動窗口、漏桶、令牌桶
簡單來說,令牌桶就是想象有一個固定大小的桶,系統會以恒定速率向桶中放 Token,桶滿則暫時不放。在請求比較的少的時候桶可以先"攢"一些Token,應對突發的流量,如果桶中有剩余 Token 就可以一直取。如果沒有剩余 Token,則需要等到桶中被放置了 Token 才行。
有的同學在看明白令牌桶的原理后就非常想去自己實現一個限流器應用到自己的項目里,em... 怎么說呢,造個輪子確實有利于自己水平提高,不過要是應用到商用項目里的話其實大可不必自己去造輪子,Golang官方已經替我們造好輪子啦 ......~!
Golang 官方提供的擴展庫里就自帶了限流算法的實現,即 golang.org/x/time/rate
。該限流器也是基于 Token Bucket(令牌桶) 實現的。
限流器的內部結構
time/rate
包的Limiter
類型對限流器進行了定義,所有限流功能都是通過基于Limiter
類型實現的,其內部結構如下:
type Limiter struct { mu sync.Mutex limit Limit burst int // 令牌桶的大小 tokens float64 last time.Time // 上次更新tokens的時間 lastEvent time.Time // 上次發生限速器事件的時間(通過或者限制都是限速器事件) }
其主要字段的作用是:
limit:
limit
字段表示往桶里放Token的速率,它的類型是Limit,是int64的類型別名。設置limit
時既可以用數字指定每秒向桶中放多少個Token,也可以指定向桶中放Token的時間間隔,其實指定了每秒放Token的個數后就能計算出放每個Token的時間間隔了。burst: 令牌桶的大小。
tokens: 桶中的令牌。
last: 上次往桶中放 Token 的時間。
lastEvent:上次發生限速器事件的時間(通過或者限制都是限速器事件)
可以看到在 timer/rate
的限流器實現中,并沒有單獨維護一個 Timer 和隊列去真的每隔一段時間向桶中放令牌,而是僅僅通過計數的方式表示桶中剩余的令牌。每次消費取 Token 之前會先根據上次更新令牌數的時間差更新桶中Token數。
大概了解了time/rate
限流器的內部實現后,下面的內容我們會集中介紹下該組件的具體使用方法:
構造限流器
我們可以使用以下方法構造一個限流器對象:
limiter := rate.NewLimiter(10, 100);
這里有兩個參數:
第一個參數是
r Limit
,設置的是限流器Limiter的limit
字段,代表每秒可以向 Token 桶中產生多少 token。Limit 實際上是 float64 的別名。第二個參數是
b int
,b 代表 Token 桶的容量大小,也就是設置的限流器 Limiter 的burst
字段。
那么,對于以上例子來說,其構造出的限流器的令牌桶大小為 100, 以每秒 10 個 Token 的速率向桶中放置 Token。
除了給r Limit
參數直接指定每秒產生的 Token 個數外,還可以用 Every 方法來指定向桶中放置 Token 的間隔,例如:
limit := rate.Every(100 * time.Millisecond); limiter := rate.NewLimiter(limit, 100);
以上就表示每 100ms 往桶中放一個 Token。本質上也是一秒鐘往桶里放 10 個。
使用限流器
Limiter 提供了三類方法供程序消費 Token,可以每次消費一個 Token,也可以一次性消費多個 Token。每種方法代表了當 Token 不足時,各自不同的對應手段,可以阻塞等待桶中Token補充,也可以直接返回取Token失敗。
Wait/WaitN
func (lim *Limiter) Wait(ctx context.Context) (err error) func (lim *Limiter) WaitN(ctx context.Context, n int) (err error)
Wait 實際上就是 WaitN(ctx,1)
。
當使用 Wait 方法消費 Token 時,如果此時桶內 Token 數組不足 (小于 N),那么 Wait 方法將會阻塞一段時間,直至 Token 滿足條件。如果充足則直接返回。
這里可以看到,Wait 方法有一個 context 參數。我們可以設置 context 的 Deadline 或者 Timeout,來決定此次 Wait 的最長時間。
// 一直等到獲取到桶中的令牌 err := limiter.Wait(context.Background()) if err != nil { fmt.Println("Error: ", err) } // 設置一秒的等待超時時間 ctx, _ := context.WithTimeout(context.Background(), time.Second * 1) err := limiter.Wait(ctx) if err != nil { fmt.Println("Error: ", err) }
Allow/AllowN
func (lim *Limiter) Allow() bool func (lim *Limiter) AllowN(now time.Time, n int) bool
Allow 實際上就是對 AllowN(time.Now(),1)
進行簡化的函數。
AllowN 方法表示,截止到某一時刻,目前桶中數目是否至少為 n 個,滿足則返回 true,同時從桶中消費 n 個 token。反之不消費桶中的Token,返回false。
對應線上的使用場景是,如果請求速率超過限制,就直接丟棄超頻后的請求。
if limiter.AllowN(time.Now(), 2) { fmt.Println("event allowed") } else { fmt.Println("event not allowed") }
Reserve/ReserveN
func (lim *Limiter) Reserve() *Reservation func (lim *Limiter) ReserveN(now time.Time, n int) *Reservation
Reserve 相當于 ReserveN(time.Now(), 1)
。
ReserveN 的用法就相對來說復雜一些,當調用完成后,無論 Token 是否充足,都會返回一個 *Reservation
對象。你可以調用該對象的Delay()
方法,該方法返回的參數類型為time.Duration
,反映了需要等待的時間,必須等到等待時間之后,才能進行接下來的工作。如果不想等待,可以調用Cancel()
方法,該方法會將 Token 歸還。
舉一個簡單的例子,我們可以這么使用 Reserve 方法。
r := limiter.Reserve() f !r.OK() { // Not allowed to act! Did you remember to set lim.burst to be > 0 ? return } time.Sleep(r.Delay()) Act() // 執行相關邏輯
動態調整速率和桶大小
Limiter 支持創建后動態調整速率和桶大小:
SetLimit(Limit) 改變放入 Token 的速率
SetBurst(int) 改變 Token 桶大小
有了這兩個方法,可以根據現有環境和條件以及我們的需求,動態地改變 Token 桶大小和速率。
總結
今天我們總結了 Golang 官方限流器的使用方法,它是一種令牌桶算實現的限流器。其中 Wait/WaitN,Allow/AllowN 這兩組方法在平時用的比較多,前者是消費Token時如果桶中Token不足可以讓程序等待桶中新Token的放入(最好設置上等待時長)后者則是在桶中的Token不足時選擇直接丟棄請求。
除了Golang官方提供的限流器實現,Uber公司開源的限流器uber-go/ratelimit
也是一個很好的選擇,與Golang官方限流器不同的是Uber的限流器是通過漏桶算法實現的,不過對傳統的漏桶算法進行了改良,有興趣的同學可以自行去體驗一下。
原文鏈接:https://juejin.cn/post/7083811372420562951
相關推薦
- 2023-03-20 C#中程序自刪除實現方法_C#教程
- 2022-11-19 項目適?Oracle改造及SSL安全性配置問題匯總詳解_oracle
- 2023-11-26 數據結構:數組—特殊矩陣的壓縮存儲
- 2021-12-09 Typora自動編號的具體操作_其它綜合
- 2023-02-02 Nginx中default_server指令問題詳解_nginx
- 2022-08-28 ubuntu安裝samba文件共享
- 2022-04-06 教你用Python尋找重復文件并刪除的腳本寫法_python
- 2022-09-14 Redis核心原理詳細解說_Redis
- 最近更新
-
- window11 系統安裝 yarn
- 超詳細win安裝深度學習環境2025年最新版(
- Linux 中運行的top命令 怎么退出?
- MySQL 中decimal 的用法? 存儲小
- get 、set 、toString 方法的使
- @Resource和 @Autowired注解
- Java基礎操作-- 運算符,流程控制 Flo
- 1. Int 和Integer 的區別,Jav
- spring @retryable不生效的一種
- Spring Security之認證信息的處理
- Spring Security之認證過濾器
- Spring Security概述快速入門
- Spring Security之配置體系
- 【SpringBoot】SpringCache
- Spring Security之基于方法配置權
- redisson分布式鎖中waittime的設
- maven:解決release錯誤:Artif
- restTemplate使用總結
- Spring Security之安全異常處理
- MybatisPlus優雅實現加密?
- Spring ioc容器與Bean的生命周期。
- 【探索SpringCloud】服務發現-Nac
- Spring Security之基于HttpR
- Redis 底層數據結構-簡單動態字符串(SD
- arthas操作spring被代理目標對象命令
- Spring中的單例模式應用詳解
- 聊聊消息隊列,發送消息的4種方式
- bootspring第三方資源配置管理
- GIT同步修改后的遠程分支