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算法如下:
根據上述瞭解的令牌桶算法可以得知,令牌桶需要一個ScheduledThread不斷的放入令牌,這部分的代碼如下:
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視圖展示,如下圖。
出處:https://www.cnblogs.com/yueshutong/p/11110455.html
閱讀更多 Java識堂 的文章