1. 程式人生 > >微服務架構下的分散式限流方案思考

微服務架構下的分散式限流方案思考

1.微服務限流

隨著微服務的流行,服務和服務之間的穩定性變得越來越重要。快取、降級和限流是保護微服務系統執行穩定性的三大利器。快取的目的是提升系統訪問速度和增大系統能處理的容量,而降級是當服務出問題或者影響到核心流程的效能則需要暫時遮蔽掉,待高峰或者問題解決後再開啟,而有些場景並不能用快取和降級來解決,比如稀缺資源、資料庫的寫操作、頻繁的複雜查詢,因此需有一種手段來限制這些場景的請求量,即限流。

比如當我們設計了一個函式,準備上線,這時候這個函式會消耗一些資源,處理上限是1秒服務3000個QPS,但如果實際情況遇到高於3000的QPS該如何解決呢?所以限流的目的應當是通過對併發訪問/請求進行限速或者一個時間視窗內的的請求進行限速來保護系統,一旦達到限制速率就可以拒絕服務、等待、降級。

學習如何去實現一個分散式限流框架,首先,我們需要去了解最基本的兩種限流演算法。

2.限流演算法

2.1漏桶演算法

漏桶演算法思路很簡單,水(也就是請求)先進入到漏桶裡,漏桶以一定的速度出水,當水流入速度過大會直接溢位,然後就拒絕請求,可以看出漏桶演算法能強行限制資料的傳輸速率。示意圖(來源網路)如下:

2.2令牌桶演算法

令牌桶演算法和漏桶演算法效果一樣但方向相反的演算法,更加容易理解。隨著時間流逝,系統會按恆定1/QPS時間間隔(如果QPS=100,則間隔是10ms)往桶裡加入令牌(想象和漏洞漏水相反,有個水龍頭在不斷的加水),如果桶已經滿了就不再加了。新請求來臨時,會各自拿走一個令牌,如果沒有令牌可拿了就阻塞或者拒絕服務。示意圖(來源網路)如下:

2.3演算法選擇

漏桶演算法與令牌桶演算法的區別在於,漏桶演算法能夠強行限制資料的傳輸速率,令牌桶演算法能夠在限制資料的平均傳輸速率的同時還允許某種程度的突發情況。令牌桶還有一個好處是可以方便的改變速度。 一旦需要提高速率,則按需提高放入桶中的令牌的速率。所以,限流框架的核心演算法還是以令牌桶演算法為主。

3.本地限流

已知上面講述的令牌桶演算法的原理,如何通過程式碼實現?

本地限流的實現可以用Long長整型作為令牌桶,為了達到無鎖,建議使用Long的原子型別AtomicLong,使用AtomicLong的好處就是可以非常方便的對其進行CAS加操作與CAS減操作(也就是令牌桶令牌的放入與拿取),以避免執行緒的上下文切換的開銷,核心CAS演算法如下:

private boolean tryAcquireFailed() {
   long l = bucket.longValue();
   while (l > 0) {
      if (bucket.compareAndSet(l, l - 1)) {
          return true;
      }
      l = bucket.longValue();
   }
   return false;
}

根據上述瞭解的令牌桶演算法可以得知,令牌桶需要一個ScheduledThread不斷的放入令牌,這部分的程式碼如下:

ScheduledThreadExecutor.scheduleAtFixedRate(() -> 
    bucket.set(rule.getLimit()), rule.getInitialDelay(), rule.getPeriod(), rule.getUnit()
);

4.分散式限流概述

分散式限流需要解決什麼問題呢?我想至少有下面幾個:

1.動態規則:比如限流的QPS我們希望可以動態修改,限流的功能可以隨時開啟、關閉,限流的規則可以跟隨業務進行動態變更等。

2.叢集限流:比如對Spring Cloud微服務架構中的某服務下的所有例項進行統一限流,以控制後續訪問資料庫的流量。

3.熔斷降級:比如在呼叫鏈路中某個資源出現不穩定狀態時(例如呼叫超時或異常比例升高),對這個資源的呼叫進行限制,讓請求快速失敗,避免影響到其它的資源而導致級聯錯誤。

可選的其它幾個功能,諸如實時監控資料、閘道器流控、熱點引數限流、系統自適應限流、黑白名單控制、註解支援等,這些功能其實可以非常方便的進行擴充套件。

5.分散式限流方案

分散式限流的思想我列舉下面三個方案:

1.Redis令牌桶

這種方案是最簡單的一種叢集限流思想。在本地限流中,我們使用Long的原子類作令牌桶,當例項數量超過1,我們就考慮將Redis用作公共記憶體區域,進行讀寫。涉及到的併發控制,也可以使用Redis實現分散式鎖。方案的缺點顯而易見,每取一次令牌都會進行一次網路開銷,而網路開銷起碼是毫秒級,所以這種方案支援的併發量是非常有限的。

2.QPS統一分配

這種方案的思想是將叢集限流最大程度的本地化。舉個例子,我們有兩臺伺服器例項,對應的是同一個應用程式(Application.name相同),程式中設定的QPS為100,將應用程式與同一個控制檯程式進行連線,控制檯端依據應用的例項數量將QPS進行均分,動態設定每個例項的QPS為50,若是遇到兩個伺服器的配置並不相同,在負載均衡層的就已經根據伺服器的優劣對流量進行分配,例如一臺分配70%流量,另一臺分配30%的流量。面對這種情況,控制檯也可以對其實行加權分配QPS的策略。客觀來說,這是一種叢集限流的實現方案,但依舊存在不小的問題。該模式的分配比例是建立在大資料流量下的趨勢進行分配,實際情況中可能並不是嚴格的五五分或三七分,誤差不可控,極容易出現使用者連續訪問某一臺伺服器遇到請求駁回而另一臺伺服器此刻空閒流量充足的尷尬情況。

3.發票伺服器

這種方案的思想是建立在Redis令牌桶方案的基礎之上的。如何解決每次取令牌都伴隨一次網路開銷,該方案的解決方法是建立一層控制端,利用該控制端與Redis令牌桶進行互動,只有當客戶端的剩餘令牌數不足時,客戶端才向該控制層取令牌並且每次取一批,這種思想類似於Java集合框架的陣列擴容,設定一個閾值,只有當超過該臨界值時,才會觸發非同步呼叫。其餘存取令牌的操作與本地限流無二。雖然該方案依舊存在誤差,但誤差最大也就一批次令牌數而已。

6.開源專案

上面說了三種分散式限流方案的實現思路,這裡推薦一個基於發票伺服器思想實現的分散式限流專案SnowJean(https://github.com/yueshutong/SnowJena)。

筆者通過該專案原始碼觀察到該限流專案在解決分散式限流上的有很多巧妙的點,比如,SnowJean內部使用觀察者模式實現動態規則配置,使用工廠模式實現限流器的構造,使用建造者模式構建限流規則。在解決如何對客戶端例項的健康狀況進行檢查時,利用的是Redis的過期時間與客戶端傳送的心跳包(傳送心跳時再進行延期)。比較不錯的一點是,該專案提供基於前端Echarts圖表的QPS檢視展示,如下圖。

由於作者水平有限,文章一定存在一些漏洞或不足,希望各位專家、大佬提出批評指正!