高并發下常見的限流算法

限流簡介

現在說到高可用系統,都會說到高可用的保護手段:緩存、降級和限流,本博文就主要說說限流。限流是流量限速(Rate Limit)的簡稱,是指只允許指定的事件進入系統,超過的部分將被拒絕服務、排隊或等待、降級等處理。對于server服務而言,限流為了保證一部分的請求流量可以得到正常的響應,總好過全部的請求都不能得到響應,甚至導致系統雪崩。限流與熔斷經常被人弄混,博主認為它們最大的區別在于限流主要在server實現,而熔斷主要在client實現,當然了,一個服務既可以充當server也可以充當client,這也是讓限流與熔斷同時存在一個服務中,這兩個概念才容易被混淆。

那為什么需要限流呢?很多人第一反應就是服務扛不住了所以需要限流。這是不全面的說法,博主認為限流是因為資源的稀缺或出于安全防范的目的,采取的自我保護的措施。限流可以保證使用有限的資源提供最大化的服務能力,按照預期流量提供服務,超過的部分將會拒絕服務、排隊或等待、降級等處理。

現在的系統對限流的支持各有不同,但是存在一些標準。在HTTP RFC 6585標準中規定了『429 Too Many Requests 』,429狀態碼表示用戶在給定時間內發送了太多的請求,需要進行限流(“速率限制”),同時包含一個 Retry-After 響應頭用于告訴客戶端多長時間后可以再次請求服務.

HTTP/1.1 429 Too Many Requests

Content-Type: text/html

Retry-After: 3600


? ? <title>Too Many Requests</title>



? ? <h1>Too Many Requests</h1>

? ? <p>I only allow 50 requests per hour to this Web site per

? ? ? ? logged in user.? Try again soon.</p>


很多應用框架同樣集成了,限流功能并且在返回的Header中給出明確的限流標識。

X-Rate-Limit-Limit:同一個時間段所允許的請求的最大數目;

X-Rate-Limit-Remaining:在當前時間段內剩余的請求的數量;

X-Rate-Limit-Reset:為了得到最大請求數所等待的秒數。

這是通過響應頭告訴調用方服務端的限流頻次是怎樣的,保證后端的接口訪問上限,客戶端也可以根據響應的Header調整請求。


限流分類

限流,拆分來看,就兩個字限和流,限就是動詞限制,很好理解。但是流在不同的場景之下就是不同資源或指標,多樣性就在流中體現。在網絡流量中可以是字節流,在數據庫中可以是TPS,在API中可以是QPS亦可以是并發請求數,在商品中還可以是庫存數... ...但是不管是哪一種『流』,這個流必須可以被量化,可以被度量,可以被觀察到、可以統計出來。我們把限流的分類基于不同的方式分為不同的類別,如下圖。


因為篇幅有限,本文只會挑選幾個常見的類型分類進行說明。

限流粒度分類

根據限流的粒度分類:

單機限流

分布式限流

現狀的系統基本上都是分布式架構,單機的模式已經很少了,這里說的單機限流更加準確一點的說法是單服務節點限流。單機限流是指請求進入到某一個服務節點后超過了限流閾值,服務節點采取了一種限流保護措施。


分布式限流狹義的說法是在接入層實現多節點合并限流,比如NGINX+redis,分布式網關等,廣義的分布式限流是多個節點(可以為不同服務節點)有機整合,形成整體的限流服務。

單機限流防止流量壓垮服務節點,缺乏對整體流量的感知。分布式限流適合做細粒度不同的限流控制,可以根據場景不同匹配不同的限流規則。與單機限流最大的區別,分布式限流需要中心化存儲,常見的使用redis實現。引入了中心化存儲,就需要解決以下問題:

數據一致性

在限流模式中理想的模式為時間點一致性。時間點一致性的定義中要求所有數據組件的數據在任意時刻都是完全一致的,但是一般來說信息傳播的速度最大是光速,其實并不能達到任意時刻一致,總有一定的時間不一致,對于我們CAP中的一致性來說只要達到讀取到最新數據即可,達到這種情況并不需要嚴格的任意時間一致。這只能是理論當中的一致性模型,可以在限流中達到線性一致性即可。

時間一致性

這里的時間一致性與上述的時間點一致性不一樣,這里就是指各個服務節點的時間一致性。一個集群有3臺機器,但是在某一個A/B機器的時間為Tue Dec 3 16:29:28 CST 2019,C為Tue Dec 3 16:29:28 CST 2019,那么它們的時間就不一致。那么使用ntpdate進行同步也會存在一定的誤差,對于時間窗口敏感的算法就是誤差點。

超時

在分布式系統中就需要網絡進行通信,會存在網絡抖動問題,或者分布式限流中間件壓力過大導致響應變慢,甚至是超時時間閾值設置不合理,導致應用服務節點超時了,此時是放行流量還是拒絕流量?

性能與可靠性

分布式限流中間件的資源總是有限的,甚至可能是單點的(寫入單點),性能存在上限。如果分布式限流中間件不可用時候如何退化為單機限流模式也是一個很好的降級方案。

限流對象類型分類

按照對象類型分類:

基于請求限流

基于資源限流

基于請求限流,一般的實現方式有限制總量限制QPS。限制總量就是限制某個指標的上限,比如搶購某一個商品,放量是10w,那么最多只能賣出10w件。微信的搶紅包,群里發一個紅包拆分為10個,那么最多只能有10人可以搶到,第十一個人打開就會顯示『手慢了,紅包派完了』。

限制QPS,也是我們常說的限流方式,只要在接口層級進行,某一個接口只允許1秒只能訪問100次,那么它的峰值QPS只能為100。限制QPS的方式最難的點就是如何預估閾值,如何定位閾值,下文中會說到。

基于資源限流是基于服務資源的使用情況進行限制,需要定位到服務的關鍵資源有哪些,并對其進行限制,如限制TCP連接數、線程數、內存使用量等。限制資源更能有效地反映出服務當前地清理,但與限制QPS類似,面臨著如何確認資源的閾值為多少。這個閾值需要不斷地調優,不停地實踐才可以得到一個較為滿意地值。

限流算法分類

不論是按照什么維度,基于什么方式的分類,其限流的底層均是需要算法來實現。下面介紹實現常見的限流算法:

1,計數器

2,令牌桶算法

3,漏桶算法



計數器

固定窗口計數器

計數限流是最為簡單的限流算法,日常開發中,我們說的限流很多都是說固定窗口計數限流算法,比如某一個接口或服務1s最多只能接收1000個請求,那么我們就會設置其限流為1000QPS。該算法的實現思路非常簡單,維護一個固定單位時間內的計數器,如果檢測到單位時間已經過去就重置計數器為零。

其操作步驟:

時間線劃分為多個獨立且固定大小窗口;

落在每一個時間窗口內的請求就將計數器加1;

如果計數器超過了限流閾值,則后續落在該窗口的請求都會被拒絕。但時間達到下一個時間窗口時,計數器會被重置為0。

下面實現一個簡單的代碼。

package limit

import (

? "sync/atomic"

? "time"

)

type Counter struct {

? Count? ? ? uint64? // 初始計數器

? Limit? ? ? uint64? // 單位時間窗口最大請求頻次

? Interval? ? int64? // 單位ms

? RefreshTime int64? // 時間窗口

}

func NewCounter(count, limit uint64, interval, rt int64) *Counter {

? return &amp;Counter{

? ? ? Count:? ? ? count,

? ? ? Limit:? ? ? limit,

? ? ? Interval:? ? interval,

? ? ? RefreshTime: rt,

? }

}

func (c *Counter) RateLimit() bool {

? now := time.Now().UnixNano() / 1e6

? if now &lt; (c.RefreshTime + c.Interval) {

? ? ? atomic.AddUint64(&amp;c.Count, 1)

? ? ? return c.Count &lt;= c.Limit

? } else {

? ? ? c.RefreshTime = now

? ? ? atomic.AddUint64(&amp;c.Count, -c.Count)

? ? ? return true

? }

}

測試代碼:

package limit

import (

? "fmt"

? "testing"

? "time"

)

func Test_Counter(t *testing.T) {

? counter := NewCounter(0, 5, 100, time.Now().Unix())

? for i := 0; i &lt; 10; i++ {

? ? ? go func(i int) {

? ? ? ? for k := 0; k &lt;= 10; k++ {

? ? ? ? ? ? fmt.Println(counter.RateLimit())

? ? ? ? ? ? if k%3 == 0 {

? ? ? ? ? ? ? time.Sleep(102 * time.Millisecond)

? ? ? ? ? ? }

? ? ? ? }

? ? ? }(i)

? }

? time.Sleep(10 * time.Second)

}

看了上面的邏輯,有沒有覺得固定窗口計數器很簡單,對,就是這么簡單,這就是它的一個優點實現簡單。同時也存在兩個比較嚴重缺陷。試想一下,固定時間窗口1s限流閾值為100,但是前100ms,已經請求來了99個,那么后續的900ms只能通過一個了,就是一個缺陷,基本上沒有應對突發流量的能力。第二個缺陷,在00:00:00這個時間窗口的后500ms,請求通過了100個,在00:00:01這個時間窗口的前500ms還有100個請求通過,對于服務來說相當于1秒內請求量達到了限流閾值的2倍。


滑動窗口計數器

滑動時間窗口算法是對固定時間窗口算法的一種改進,這詞被大眾所知實在TCP的流量控制中。固定窗口計數器可以說是滑動窗口計數器的一種特例,滑動窗口的操作步驟:

將單位時間劃分為多個區間,一般都是均分為多個小的時間段;

每一個區間內都有一個計數器,有一個請求落在該區間內,則該區間內的計數器就會加一;

每過一個時間段,時間窗口就會往右滑動一格,拋棄最老的一個區間,并納入新的一個區間;

計算整個時間窗口內的請求總數時會累加所有的時間片段內的計數器,計數總和超過了限制數量,則本窗口內所有的請求都被丟棄。

時間窗口劃分的越細,并且按照時間"滑動",這種算法避免了固定窗口計數器出現的上述兩個問題。缺點是時間區間的精度越高,算法所需的空間容量就越大。

常見的實現方式主要有基于redis zset的方式和循環隊列實現。基于redis zset可將Key為限流標識ID,Value保持唯一,可以用UUID生成,Score 也記為同一時間戳,最好是納秒級的。使用redis提供的 ZADD、EXPIRE、ZCOUNT 和 zremrangebyscore 來實現,并同時注意開啟 Pipeline 來盡可能提升性能。實現很簡單,但是缺點就是zset的數據結構會越來越大。


漏桶算法

漏桶算法是水先進入到漏桶里,漏桶再以一定的速率出水,當流入水的數量大于流出水時,多余的水直接溢出。把水換成請求來看,漏桶相當于服務器隊列,但請求量大于限流閾值時,多出來的請求就會被拒絕服務。漏桶算法使用隊列實現,可以以固定的速率控制流量的訪問速度,可以做到流量的“平整化”處理。

大家可以通過網上最流行的一張圖來理解。


漏桶算法實現步驟:

將每個請求放入固定大小的隊列進行存儲;

隊列以固定速率向外流出請求,如果隊列為空則停止流出;

如隊列滿了則多余的請求會被直接拒絕·

漏桶算法有一個明顯的缺陷:當短時間內有大量的突發請求時,即使服務器負載不高,每個請求也都得在隊列中等待一段時間才能被響應。


令牌桶算法

令牌桶算法的原理是系統會以一個恒定的速率往桶里放入令牌,而如果請求需要被處理,則需要先從桶里獲取一個令牌,當桶里沒有令牌可取時,則拒絕服務。從原理上看,令牌桶算法和漏桶算法是相反的,前者為“進”,后者為“出”。漏桶算法與令牌桶算法除了“方向”上的不同還有一個更加主要的區別:令牌桶算法限制的是平均流入速率(允許突發請求,只要有足夠的令牌,支持一次拿多個令牌),并允許一定程度突發流量;

令牌桶算法的實現步驟:

令牌以固定速率生成并放入到令牌桶中;

如果令牌桶滿了則多余的令牌會直接丟棄,當請求到達時,會嘗試從令牌桶中取令牌,取到了令牌的請求可以執行;

如果桶空了,則拒絕該請求。

四種策略該如何選擇?

固定窗口:實現簡單,但是過于粗暴,除非情況緊急,為了能快速止損眼前的問題可以作為臨時應急的方案。

滑動窗口:限流算法簡單易實現,可以應對有少量突增流量場景。

漏桶:對于流量絕對均勻有很強的要求,資源的利用率上不是極致,但其寬進嚴出模式,保護系統的同時還留有部分余量,是一個通用性方案。

令牌桶:系統經常有突增流量,并盡可能的壓榨服務的性能。



怎么做限流?

不論使用上述的哪一種分類或者實現方式,系統都會面臨一個共同的問題:如何確認限流閾值。有人團隊根據經驗先設定一個小的閾值,后續慢慢進行調整;有的團隊是通過進行壓力測試后總結出來。這種方式的問題在于壓測模型與線上環境不一定一致,接口的單壓不能反饋整個系統的狀態,全鏈路壓測又難以真實反應實際流量場景流量比例。再換一個思路是通過壓測+各應用監控數據。根據系統峰值的QPS與系統資源使用情況,進行等水位放大預估限流閾值,問題在于系統性能拐點未知,單純的預測不一定準確甚至極大偏離真實場景。正如《Overload Control for Scaling WeChat Microservices》所說,在具有復雜依賴關系的系統中,對特定服務的進行過載控制可能對整個系統有害或者服務的實現有缺陷。希望后續可以出現一個更加AI的運行反饋自動設置限流閾值的系統,可以根據當前QPS、資源狀態、RT情況等多種關聯數據動態地進行過載保護。

不論是哪一種方式給出的限流閾值,系統都應該關注以下幾點:

運行指標狀態,比如當前服務的QPS、機器資源使用情況、數據庫的連接數、線程的并發數等;

資源間的調用關系,外部鏈路請求、內部服務之間的關聯、服務之間的強弱依賴等;

控制方式,達到限流后對后續的請求直接拒絕、快速失敗、排隊等待等處理方式

go限流類庫使用

限流的類庫有很多,不同語言的有不同的類庫,如大Java的有concurrency-limits、Sentinel、Guava 等,這些類庫都有很多的分析和使用方式了,本文主要介紹Golang的限流類庫就是Golang的擴展庫:github.com/golang/time…。可以進去語言類庫的代碼都值得去研讀一番,學習過Java的同學是否對AQS的設計之精妙而感嘆呢!time/rate也有其精妙的部分,下面開始進入類庫學習階段。

github.com/golang/time/rate

進行源碼分析前的,最應該做的是了解類庫的使用方式、使用場景和API。對業務有了初步的了解,閱讀代碼就可以事半功倍。因為篇幅有限后續的博文在對多個限流類庫源碼做分析。類庫的API文檔:godoc.org/golang.org/…

func NewLimiter(r Limit, b int) *Limiter

newLimiter返回一個新的限制器,它允許事件的速率達到r,并允許最多突發b個令牌。也就是說Limter限制時間的發生頻率,但這個桶一開始容量就為b,并且裝滿b個令牌(令牌池中最多有b個令牌,所以一次最多只能允許b個事件發生,一個事件花費掉一個令牌),然后每一個單位時間間隔(默認1s)往桶里放入r個令牌。

limter := rate.NewLimiter(10, 5)

上面的例子表示,令牌桶的容量為5,并且每一秒中就往桶里放入10個令牌。函數NewLimiter第一個參數是Limit類型,可以看源碼就會發現Limit實際上就是float64的別名。

// Limit defines the maximum frequency of some events.

// Limit is represented as number of events per second.

// A zero Limit allows no events.

type Limit float64


限流器還可以指定往桶里放入令牌的時間間隔,實現方式如下:

limter := rate.NewLimiter(rate.Every(100*time.Millisecond), 5)

這兩個例子的效果是一樣的,使用第一種方式不會出現在每一秒間隔一下子放入10個令牌,也是均勻分散在100ms的間隔放入令牌。rate.Limiter提供了三類方法用來限速:

Allow/AllowN

Wait/WaitN

Reserve/ReserveN

下面對比這三類限流方式的使用方式和適用場景。先看第一類方法:

func (lim *Limiter) Allow() bool

func (lim *Limiter) AllowN(now time.Time, n int) bool

Allow 是AllowN(time.Now(), 1)的簡化方法。那么重點就在方法 AllowN上了,API的解釋有點抽象,說得云里霧里的,可以看看下面的API文檔解釋:

AllowN reports whether n events may happen at time now.

Use this method if you intend to drop / skip events that exceed the rate limit.

Otherwise use Reserve or Wait.

實際上就是為了說,方法 AllowN在指定的時間時是否可以從令牌桶中取出N個令牌。也就意味著可以限定N個事件是否可以在指定的時間同時發生。這個兩個方法是無阻塞,也就是說一旦不滿足,就會跳過,不會等待令牌數量足夠才執行。也就是文檔中的第二行解釋,如果打算丟失或跳過超出速率限制的時間,那么久請使用該方法。比如使用之前實例化好的限流器,在某一個時刻,服務器同時收到超過了8個請求,如果令牌桶內令牌小于8個,那么這8個請求就會被丟棄。一個小示例:

func AllowDemo() {

? limter := rate.NewLimiter(rate.Every(200*time.Millisecond), 5)

? i := 0

? for {

? ? ? i++

? ? ? if limter.Allow() {

? ? ? ? fmt.Println(i, "====Allow======", time.Now())

? ? ? } else {

? ? ? ? fmt.Println(i, "====Disallow======", time.Now())

? ? ? }

? ? ? time.Sleep(80 * time.Millisecond)

? ? ? if i == 15 {

? ? ? ? return

? ? ? }

? }

}

執行結果:

1 ====Allow====== 2019-12-14 15:54:09.9852178 +0800 CST m=+0.005998001

2 ====Allow====== 2019-12-14 15:54:10.1012231 +0800 CST m=+0.122003301

3 ====Allow====== 2019-12-14 15:54:10.1823056 +0800 CST m=+0.203085801

4 ====Allow====== 2019-12-14 15:54:10.263238 +0800 CST m=+0.284018201

5 ====Allow====== 2019-12-14 15:54:10.344224 +0800 CST m=+0.365004201

6 ====Allow====== 2019-12-14 15:54:10.4242458 +0800 CST m=+0.445026001

7 ====Allow====== 2019-12-14 15:54:10.5043101 +0800 CST m=+0.525090301

8 ====Allow====== 2019-12-14 15:54:10.5852232 +0800 CST m=+0.606003401

9 ====Disallow====== 2019-12-14 15:54:10.6662181 +0800 CST m=+0.686998301

10 ====Disallow====== 2019-12-14 15:54:10.7462189 +0800 CST m=+0.766999101

11 ====Allow====== 2019-12-14 15:54:10.8272182 +0800 CST m=+0.847998401

12 ====Disallow====== 2019-12-14 15:54:10.9072192 +0800 CST m=+0.927999401

13 ====Allow====== 2019-12-14 15:54:10.9872224 +0800 CST m=+1.008002601

14 ====Disallow====== 2019-12-14 15:54:11.0672253 +0800 CST m=+1.088005501

15 ====Disallow====== 2019-12-14 15:54:11.1472946 +0800 CST m=+1.168074801


第二類方法:因為ReserveN比較復雜,第二類先說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)的簡化方法。與AllowN不同的是WaitN會阻塞,如果令牌桶內的令牌數不足N個,WaitN會阻塞一段時間,阻塞時間的時長可以用第一個參數ctx進行設置,把 context 實例為context.WithDeadline或context.WithTimeout進行制定阻塞的時長。

func WaitNDemo() {

? limter := rate.NewLimiter(10, 5)

? i := 0

? for {

? ? ? i++

? ? ? ctx, canle := context.WithTimeout(context.Background(), 400*time.Millisecond)

? ? ? if i == 6 {

? ? ? ? // 取消執行

? ? ? ? canle()

? ? ? }

? ? ? err := limter.WaitN(ctx, 4)

? ? ? if err != nil {

? ? ? ? fmt.Println(err)

? ? ? ? continue

? ? ? }

? ? ? fmt.Println(i, ",執行:", time.Now())

? ? ? if i == 10 {

? ? ? ? return

? ? ? }

? }

}


執行結果:

1 ,執行:2019-12-14 15:45:15.538539 +0800 CST m=+0.011023401

2 ,執行:2019-12-14 15:45:15.8395195 +0800 CST m=+0.312003901

3 ,執行:2019-12-14 15:45:16.2396051 +0800 CST m=+0.712089501

4 ,執行:2019-12-14 15:45:16.6395169 +0800 CST m=+1.112001301

5 ,執行:2019-12-14 15:45:17.0385893 +0800 CST m=+1.511073701

context canceled

7 ,執行:2019-12-14 15:45:17.440514 +0800 CST m=+1.912998401

8 ,執行:2019-12-14 15:45:17.8405152 +0800 CST m=+2.312999601

9 ,執行:2019-12-14 15:45:18.2405402 +0800 CST m=+2.713024601

10 ,執行:2019-12-14 15:45:18.6405179 +0800 CST m=+3.113002301

適用于允許阻塞等待的場景,比如消費消息隊列的消息,可以限定最大的消費速率,過大了就會被限流避免消費者負載過高。

第三類方法:

func (lim *Limiter) Reserve() *Reservation

func (lim *Limiter) ReserveN(now time.Time, n int) *Reservation

與之前的兩類方法不同的是Reserve/ReserveN返回了Reservation實例。Reservation在API文檔中有5個方法:

func (r *Reservation) Cancel() // 相當于CancelAt(time.Now())

func (r *Reservation) CancelAt(now time.Time)

func (r *Reservation) Delay() time.Duration // 相當于DelayFrom(time.Now())

func (r *Reservation) DelayFrom(now time.Time) time.Duration

func (r *Reservation) OK() bool

通過這5個方法可以讓開發者根據業務場景進行操作,相比前兩類的自動化,這樣的操作顯得復雜多了。通過一個小示例來學習Reserve/ReserveN:

func ReserveNDemo() {

? limter := rate.NewLimiter(10, 5)

? i := 0

? for {

? ? ? i++

? ? ? reserve := limter.ReserveN(time.Now(), 4)

? ? ? // 如果為flase說明拿不到指定數量的令牌,比如需要的令牌數大于令牌桶容量的場景

? ? ? if !reserve.OK() {

? ? ? ? return

? ? ? }

? ? ? ts := reserve.Delay()

? ? ? time.Sleep(ts)

? ? ? fmt.Println("執行:", time.Now(),ts)

? ? ? if i == 10 {

? ? ? ? return

? ? ? }

? }

}

執行結果:

執行:2019-12-14 16:22:26.6446468 +0800 CST m=+0.008000201 0s

執行:2019-12-14 16:22:26.9466454 +0800 CST m=+0.309998801 247.999299ms

執行:2019-12-14 16:22:27.3446473 +0800 CST m=+0.708000701 398.001399ms

執行:2019-12-14 16:22:27.7456488 +0800 CST m=+1.109002201 399.999499ms

執行:2019-12-14 16:22:28.1456465 +0800 CST m=+1.508999901 398.997999ms

執行:2019-12-14 16:22:28.5456457 +0800 CST m=+1.908999101 399.0003ms

執行:2019-12-14 16:22:28.9446482 +0800 CST m=+2.308001601 399.001099ms

執行:2019-12-14 16:22:29.3446524 +0800 CST m=+2.708005801 399.998599ms

執行:2019-12-14 16:22:29.7446514 +0800 CST m=+3.108004801 399.9944ms

執行:2019-12-14 16:22:30.1446475 +0800 CST m=+3.508000901 399.9954ms

如果在執行Delay()之前操作Cancel()那么返回的時間間隔就會為0,意味著可以立即執行操作,不進行限流。

func ReserveNDemo2() {

? limter := rate.NewLimiter(5, 5)

? i := 0

? for {

? ? ? i++

? ? ? reserve := limter.ReserveN(time.Now(), 4)

? ? ? // 如果為flase說明拿不到指定數量的令牌,比如需要的令牌數大于令牌桶容量的場景

? ? ? if !reserve.OK() {

? ? ? ? return

? ? ? }

? ? ? if i == 6 || i == 5 {

? ? ? ? reserve.Cancel()

? ? ? }

? ? ? ts := reserve.Delay()

? ? ? time.Sleep(ts)

? ? ? fmt.Println(i, "執行:", time.Now(), ts)

? ? ? if i == 10 {

? ? ? ? return

? ? ? }

? }

}

執行結果:

1 執行:2019-12-14 16:25:45.7974857 +0800 CST m=+0.007005901 0s

2 執行:2019-12-14 16:25:46.3985135 +0800 CST m=+0.608033701 552.0048ms

3 執行:2019-12-14 16:25:47.1984796 +0800 CST m=+1.407999801 798.9722ms

4 執行:2019-12-14 16:25:47.9975269 +0800 CST m=+2.207047101 799.0061ms

5 執行:2019-12-14 16:25:48.7994803 +0800 CST m=+3.009000501 799.9588ms

6 執行:2019-12-14 16:25:48.7994803 +0800 CST m=+3.009000501 0s

7 執行:2019-12-14 16:25:48.7994803 +0800 CST m=+3.009000501 0s

8 執行:2019-12-14 16:25:49.5984782 +0800 CST m=+3.807998401 798.0054ms

9 執行:2019-12-14 16:25:50.3984779 +0800 CST m=+4.607998101 799.0075ms

10 執行:2019-12-14 16:25:51.1995131 +0800 CST m=+5.409033301 799.0078ms

看到這里time/rate的限流方式已經完成,除了上述的三類限流方式,time/rate還提供了動態調整限流器參數的功能。相關API如下:

func (lim *Limiter) SetBurst(newBurst int) // 相當于SetBurstAt(time.Now(), newBurst).

func (lim *Limiter) SetBurstAt(now time.Time, newBurst int)// 重設令牌桶的容量

func (lim *Limiter) SetLimit(newLimit Limit) // 相當于SetLimitAt(time.Now(), newLimit)

func (lim *Limiter) SetLimitAt(now time.Time, newLimit Limit)// 重設放入令牌的速率


這四個方法可以讓程序根據自身的狀態動態的調整令牌桶速率和令牌桶容量。

?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 227,818評論 6 531
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,185評論 3 414
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 175,656評論 0 373
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,647評論 1 309
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,446評論 6 405
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 54,951評論 1 321
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,041評論 3 440
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,189評論 0 287
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 48,718評論 1 333
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,602評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,800評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,316評論 5 358
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,045評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,419評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,671評論 1 281
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,420評論 3 390
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,755評論 2 371

推薦閱讀更多精彩內容

  • 介紹: Nmap使用原始IP數據包來確定網絡上可用的主機、主機提供的服務(應用程序名稱和版本)、正在運行的操作系統...
    三豬技術團隊閱讀 744評論 0 0
  • 504b 0304 1400 0008 0800 fa8c 963d 50740baa dffc 0e00 6cd...
    BossOx閱讀 3,465評論 0 0
  • 對于限流常見有兩種算法: 漏桶算法 令牌桶算法 漏桶算法 漏桶算法比較簡單,就是將流量放入桶中,漏桶同時也按照一定...
    任嘉平生愿閱讀 186評論 0 0
  • 情感傾向可認為是主體對某一客體主觀存在的內心喜惡,內在評價的一種傾向。它由兩個方面來衡量:一個情感傾向方向,一個是...
    mlion閱讀 2,472評論 1 11
  • 我是黑夜里大雨紛飛的人啊 1 “又到一年六月,有人笑有人哭,有人歡樂有人憂愁,有人驚喜有人失落,有的覺得收獲滿滿有...
    陌忘宇閱讀 8,577評論 28 53