服務化體系之—限流


服務化體系之—限流

08月 29, 2016 | Filed under 技術

(上)設計篇

在實現算法之前,先臨時客串一下產品經理,嘗試用最少的字,把“限流”這簡單二字所展開的種種需求給說清楚。

 

1.各種目的

1. 保護每個服務節點。
2. 保護服務集群背后的資源,比如數據庫。
3. 避免單個調用者過度使用服務,影響其他調用者。

 

2. 各種設定維度

2.1. 節點級別 vs 集群級別

如果以保護每個服務節點為目的,可以簡單的在本地做節點級別的限流。

但如果以保護服務集群背后的資源為目的,就需要做集群級別的限流。

集群級別的一個做法是使用Redis, Memcached之類做一個集群級別的計數器。但額外多一次訪問Redis的消耗,代價有點大。

而另一個做法是把集群限流總數分攤到每個節點上,但一是不夠精准,二是如果使用Docker動態縮擴容,需要動態更新這個分攤數。

 

2.2 客戶端 vs 服務端

當以避免單個調用者過度使用服務為目的,可以針對客戶端設定限流。

此時限流可以在客戶端實現,節約了網絡往返,但同樣有調用者的節點 or 集群之惑。

也可以在服務端實現,讓所有限流邏輯集中於一處發生。

 

2.3 服務級別 vs 方法級別

可以對消耗特別大的方法專門配置,比如復雜的查詢,昂貴的寫操作。

然后其他方法使用統一的值,或者配一個所有方法加起來的總和。

 

3. 各種觸發條件

觸發條件的設定,難點在於服務的容量,受着本服務節點的能力,背后的資源的能力,下游服務的響應的多重約束。

3.1 靜態配置固定值

當然,這個固定值可以被動態更新。

3.2 根據預設規則觸發

規則的條件可以是服務平均時延,可以是背后數據庫的CPU情況等。

比如平時不限流,當服務時延大於100ms,則觸發限流500 QPS。

還可以是多級條件,比如>100ms 限流500 QPS, >200ms 限流200 QPS。

3.3 全動態自動增減調控

這個誘人的想法,永遠存在於老板的心里。

 

4. 各種處理

4.1 立刻返回拒絕錯誤

由客戶端進行降級處理。

4.2 進行短暫的等待

短暫等待,期待有容量空余,直到超時,依然是客戶端降級。

4.3 觸發服務降級,調用服務端的降級方法

服務端的降級方法,走服務端的簡單路徑與預設值,則代表了服務端這邊的態度和邏輯,各有適用的場景,等下一篇《服務降級》 再詳述。

 

(下)實現篇

開濤的《聊聊高並發系統之限流特技》 已講得非常好,這里再簡單補充兩句。

另一篇《接口限流算法總結》 也非常好非常詳細,差別只在於對令牌桶的突發量的描述上,我有我自己的理解。

1. 並發控制

並發控制本身就是一種最簡單的限流,包括:

框架本身的連接/線程限制
各種數據庫連接池,Http連接池
服務/方法級別的信號量計數器限制

 

2. 窗口流量控制

窗口流量控制有幾種做法。

一種最簡單的計數器,維護一個單位時間內的Counter,如判斷單位時間已經過去,則將Counter重置零。開濤文章里利用guava cache也是一種做法。

但此做法有時被認為粒度太粗,沒有把QPS平攤到一秒的各個毫秒里,同時也沒有很好的處理單位時間的邊界,比如在前一秒的最后一毫秒里和下一秒的第一毫秒都觸發了最大的請求數,將目光移動一下,就看到在兩毫秒內發生了兩倍的TPS。

於是,另一個改進版的做法是把單位時間拆分,比如把1秒分成5個200毫秒寬的桶,然后以滑動窗口來計算限流,好像就能很大程度上解決上面的兩個問題了。。。。滑動窗口的算法,統一到《熔斷篇》再來討論。

最后一組就是漏桶算法或令牌桶算法了,下面會詳述。

另外,集群規模的計數器,基於Memcachd或Redis的實現,也見開濤的博客。

 

3. 令牌桶算法(Token Bucket )

  1. 1. 隨着時間流逝,系統會按速率 1/rate 的時間間隔(如果rate=100,則間隔是10ms)往桶里加入Token
  2. 2. 如果桶滿了(burst),則丟棄新加入的令牌
  3. 3. 每個請求進來,都要消耗一個Token,如果桶空了,則丟棄請求或等待有新的令牌放入。

非常形象,好像不需要更多解析

4. 漏桶算法(Leaky Bucket )

簡單的想象有一個木桶,有新請求就是不斷的倒水進來,然后桶底下有個洞,按照固定的速率把水漏走,如果水進來的速度比漏走的快,桶可能就會滿了,然后就拒絕請求。

可見,兩個算法的基本描述上,只是方向不一樣,其他沒什么不同的。所以WikiMedia里說, 兩個算法實現可以一樣,對於相同的參數得到的限流效果是一樣的。

 

5. Guava版的令牌桶實現 -- RateLimiter

Guava已實現了一個性能非常好的RateLimiter,基本不需要我們再費心實現。但其中有一些實現的細節,需要我們留意:

1. 支持桶外預借的突發

突發,原本是指如果單位時間的前半段流量較少,桶里會積累一些令牌,然后支持來一波大的瞬時流量,將前面積累的令牌消耗掉。

但在RateLimiter的實現里,還多了個桶外預借(我自己給他的命名),就是即使桶里沒有多少令牌,你也可以消耗一波大的,然后桶里面在時間 段內都沒有新令牌。比如桶的容量是5,桶里面現在只有1個令牌,如果你要拿5個令牌,也可以,清了桶里的一個令牌,再預借4個。然后再過800毫秒,桶里 才會出現新令牌。

可見,Guava版的RateLimiter對突發的支持,比原版的兩種算法都要大,你幾乎隨時都可以一次過消費burst個令牌,不管現在桶里有沒有積累的令牌。

不過有個副作用,就是如果前面都沒什么流量,桶里累積了5個令牌,則你其實可以一次過消費10個令牌。。。不過那么一下,超借完接下來還是固定速率的,直到還清了舊賬,才可能再來那么一下。

2. 支持等待可用令牌與立刻返回兩種接口

3. 單位時段是秒,這有點不太好用,不支持設定5分鍾的單位。

4. 發令牌的計算粒度是MicroSeconds,也就是最多支持一百萬的QPS。

有關的...


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM