Dubbo之限流TpsLimitFilter原始碼分析
本文基於incubator-dubbo 2.7.0版本
前言
在分散式系統中,限流和熔斷是處理併發的兩大利器。關於限流和熔斷,需要記住一句話,客戶端熔斷,服務端限流。本文我會講解Dubbo框架對限流的支援。
限流的作用
我個人理解限流的作用,保護應用,防止雪崩。每個應用都有自己處理請求的上限,一旦應用承受過多請求,首先會對正在處理中的請求造成影響,如果更嚴重,對上下游也會造成雪崩效應。
TpsLimitFilter分析
Dubbo中的限流通過TpsLimitFilter來實現,會在invoker執行實際業務邏輯前進行攔截,判斷單位時間請求數是否超過上限,如果超過,丟擲異常阻斷呼叫。
TpsLimitFilter原始碼如下
@Activate(group = Constants.PROVIDER, value = Constants.TPS_LIMIT_RATE_KEY)
public class TpsLimitFilter implements Filter {
private final TPSLimiter tpsLimiter = new DefaultTPSLimiter();
public Result invoke(Invoker<?> invoker, Invocation invocation) throws RpcException {
if (!tpsLimiter.isAllowable(invoker.getUrl(), invocation)) {
throw new RpcException(
new StringBuilder(64)
.append("Failed to invoke service ")
.append(invoker.getInterface().getName())
.append(".")
.append(invocation.getMethodName())
.append(" because exceed max service tps.")
.toString());
}
return invoker.invoke(invocation);
}
}
從TpsLimitFilter的原始碼中可以看到,因為是擴充套件點自動啟用配置,首先TpsLimitFilter只對provider端有效,其次provider url的需要包括tps=xxx這個配置才能生效。
通過TPSLimiter的isAllowable實現限流 ,其內部採用了計數器演算法,單位時間內限制多少呼叫次數,超過限制,返回false。
public class DefaultTPSLimiter implements TPSLimiter {
/**
* 每個Service維護一個計數器
*/
private final ConcurrentMap<String, StatItem> stats
= new ConcurrentHashMap<String, StatItem>();
@Override
public boolean isAllowable(URL url, Invocation invocation) {
int rate = url.getParameter(Constants.TPS_LIMIT_RATE_KEY, -1);
long interval = url.getParameter(Constants.TPS_LIMIT_INTERVAL_KEY,
Constants.DEFAULT_TPS_LIMIT_INTERVAL);
//servicekey並沒有和方法繫結,只能限流介面
String serviceKey = url.getServiceKey();
if (rate > 0) {
StatItem statItem = stats.get(serviceKey);
if (statItem == null) {
stats.putIfAbsent(serviceKey,
new StatItem(serviceKey, rate, interval));
statItem = stats.get(serviceKey);
}
return statItem.isAllowable();
} else {
StatItem statItem = stats.get(serviceKey);
if (statItem != null) {
stats.remove(serviceKey);
}
}
return true;
}
}
TPSLimiter 針對每個service都建立一個計數器StatItem,通過StatItem的isAllowable方法判斷請求是否有效
class StatItem {
//介面名
private String name;
//計數週期開始
private long lastResetTime;
//計數間隔
private long interval;
//剩餘計數請求數
private AtomicInteger token;
//總共允許請求數
private int rate;
StatItem(String name, int rate, long interval) {
this.name = name;
this.rate = rate;
this.interval = interval;
this.lastResetTime = System.currentTimeMillis();
this.token = new AtomicInteger(rate);
}
public boolean isAllowable() {
long now = System.currentTimeMillis();
if (now > lastResetTime + interval) {
token.set(rate);
lastResetTime = now;
}
int value = token.get();
boolean flag = false;
while (value > 0 && !flag) {
//樂觀鎖增加計數
flag = token.compareAndSet(value, value - 1);
//失敗重新獲取
value = token.get();
}
return flag;
}
long getLastResetTime() {
return lastResetTime;
}
int getToken() {
return token.get();
}
@Override
public String toString() {
return new StringBuilder(32).append("StatItem ")
.append("[name=").append(name).append(", ")
.append("rate = ").append(rate).append(", ")
.append("interval = ").append(interval).append("]")
.toString();
}
}
StatItem內的邏輯很簡單,針對每段時間(lastResetTime,lastResetTime+interval)允許rate次呼叫,只要計數器達不到上限,返回true。如果超過lastResetTime+interval,重置計數器。
使用TpsLimitFilter
令人費解的是,Dubbo框架並沒有預設通過配置檔案啟動這個Filter,所以我們需要在classpath的META-INF/dubbo/目錄下增加com.alibaba.dubbo.rpc.Filter檔案
tps=com.alibaba.dubbo.rpc.filter.TpsLimitFilter
就算加上了這個配置,其實也還是生效不了,我們的provider url需要有tps=xxx引數
問題就來了,怎麼加這個配置呢,答案就是override,這個功能的官方介紹如下
override的原理是,其實在RegistryProtocol使用export方法對服務進行本地暴露以及註冊Provider Url到zk後,還做了另外一個操作,監聽服務對應的 /dubbo/interface/configurations目錄,一旦configurations目錄下節點發生變化,就會重新生成暴露的url,然後進行reexport。
具體相關原始碼大家可以細細品味下,我覺得這個設計是dubbo服務治理的核心。
註冊監聽程式碼如下
//得到override url,用於監聽configurations目錄
final URL overrideSubscribeUrl = getSubscribedOverrideUrl(registedProviderUrl);
//構造監聽器,用於provider url被override時 重新發布exporter
//監聽路徑為 /dubbo/interface/configurations
final OverrideListener overrideSubscribeListener = new OverrideListener(overrideSubscribeUrl, originInvoker);
overrideListeners.put(overrideSubscribeUrl, overrideSubscribeListener);
//向registry訂閱這個url路徑
registry.subscribe(overrideSubscribeUrl, overrideSubscribeListener);
回到正題,那麼我們怎麼讓tps生效呢?
在zk的configurations目錄下,增加一個目錄,目錄名如下
override://10.111.27.41:20880/com.alibaba.dubbo.demo.DemoService?tps=5&category=configurators
zk操作命令如下
create -e /dubbo/com.alibaba.dubbo.demo.DemoService/configurators/override%3a%2f%2f10.111.27.41%3a20880%2fcom.alibaba.dubbo.demo.DemoService%3ftps%3d5%26category%3dconfigurators 1
注意overrider後面這端url需要進行URLEncode,因為裡面包含了/符號,zk會誤識別為目錄。 -e用於建立臨時目錄,客戶端斷開後這個目錄會失效,也就是限流會失效。建立zk目錄的時候需要注意下。最好設定成永久。
我通過以上方式設定tps=5之後,超過第六次呼叫後,就對客戶端丟擲異常了
限流演算法
Dubbo的限流演算法使用了最簡單的計數器演算法,如果併發流量剛好在上個計數器最後一秒和下個計數器第一秒來臨,也不能完全預防突發流量,所以推薦自己使用令牌桶演算法或漏桶演算法實現自定義限流Filter,並且也可以考慮分散式限流。
關於限流演算法,下面這篇文章還不錯。
https://blog.csdn.net/tianyaleixiaowu/article/details/74942405
總結
Dubbo設計擴充套件性真的很強,我們可以通過對Dubbo原始碼的學習,學習到各個方面的知識,舉一反三,應用到實際專案中去,也會有助於對其他框架的原始碼理解。
最後
希望大家關注下我的公眾號
相關文章
- Dubbo之SPI原始碼分析原始碼
- dubbo原始碼分析之叢集Cluster原始碼
- Dubbo原始碼分析之服務引用原始碼
- Dubbo原始碼分析之服務暴露原始碼
- Dubbo原始碼分析-叢集容錯之Router原始碼
- Dubbo原始碼解析之SPI原始碼
- 來談談限流-RateLimiter原始碼分析MIT原始碼
- dubbo消費者原始碼分析原始碼
- Dubbo 原始碼分析 - SPI 機制原始碼
- Dubbo原始碼分析(一)Dubbo與Spring整合例項原始碼Spring
- Dubbo原始碼分析(五)Dubbo呼叫鏈-服務端原始碼服務端
- Dubbo原始碼分析(三)Dubbo的服務引用Refer原始碼
- Dubbo原始碼之服務引用原始碼
- 從kratos分析BBR限流原始碼實現原始碼
- Dubbo原始碼分析(六)Dubbo通訊的編碼解碼機制原始碼
- Dubbo原始碼學習之-通過原始碼看看dubbo對netty的使用原始碼Netty
- dubbo原始碼分析02:服務引用原始碼
- Dubbo原始碼分析十一、服務路由原始碼路由
- Dubbo原始碼解析之SPI機制原始碼
- Dubbo原始碼之動態編譯原始碼編譯
- dubbo原始碼解析之ExtensionLoader類(二)原始碼
- dubbo原始碼解析之基礎篇原始碼
- dubbo原始碼解析之負載均衡原始碼負載
- 【Dubbo原始碼閱讀系列】之 Dubbo XML 配置載入原始碼XML
- Dubbo原始碼分析(一)Dubbo的擴充套件點機制原始碼套件
- Dubbo原始碼分析(七)服務目錄原始碼
- dubbo 超時設定和原始碼分析原始碼
- Dubbo RPC執行緒模型 原始碼分析RPC執行緒模型原始碼
- Guava 原始碼分析之 EventBus 原始碼分析Guava原始碼
- Android 原始碼分析之 AsyncTask 原始碼分析Android原始碼
- 面試官:來談談限流-RateLimiter原始碼分析面試MIT原始碼
- Dubbo原始碼分析(二)Dubbo是從哪裡初始化的?原始碼
- Dubbo原始碼解析之負載均衡策略原始碼負載
- Dubbo原始碼解析之服務叢集原始碼
- Dubbo原始碼學習之-SPI介紹原始碼
- 原始碼分析Dubbo網路通訊篇NettyClient原始碼Nettyclient
- dubbo原始碼分析之服務呼叫方發起呼叫(入口InvokerInvocationHandler.invoke)原始碼
- Nginx原始碼研究之nginx限流模組詳解Nginx原始碼