源碼分析 Sentinel 之 Dubbo 適配原理
- 2020 年 4 月 13 日
- 筆記
在Alibaba Sentinel 限流與熔斷初探(技巧篇) 的示例中我選擇了 sentinel-demo-apache-dubbo 作為突破點,故本文就從該項目入手,看看 Sentinel 是如何對 Dubbo 做的適配,讓項目使用方無感知,只需要引入對應的依即可。
sentinel-apache-dubbo-adapter 比較簡單,展開如下:
上面的程式碼應該比較簡單,在正式進入源碼研究之前,我先拋出如下二個問題:
- 1、限流、熔斷相關的功能是在 Dubbo 的客戶端實現還是服務端實現?為什麼?
- 2、如何對 Dubbo 進行功能擴展而無需改動業務程式碼?
Dubbo 提供了 Filter 機制對功能進行無縫擴展,有關 Dubbo Filter 機制,大家可以查閱筆者的源碼研究 Dubbo 系列:Dubbo Filter機制概述。
接下來我們帶著上面的問題1開始本章的研究。
@
1、源碼分析 SentinelDubboConsumerFilter
@Activate(group = "consumer") // @1
public class SentinelDubboConsumerFilter implements Filter {
public SentinelDubboConsumerFilter() {
RecordLog.info("Sentinel Apache Dubbo consumer filter initialized");
}
@Override
public Result invoke(Invoker<?> invoker, Invocation invocation) throws RpcException {
Entry interfaceEntry = null;
Entry methodEntry = null;
try {
String resourceName = DubboUtils.getResourceName(invoker, invocation, DubboConfig.getDubboConsumerPrefix()); // @2
interfaceEntry = SphU.entry(invoker.getInterface().getName(),
ResourceTypeConstants.COMMON_RPC, EntryType.OUT); // @3
methodEntry = SphU.entry(resourceName, ResourceTypeConstants.COMMON_RPC, EntryType.OUT); // @4
Result result = invoker.invoke(invocation); // @5
if (result.hasException()) { // @6
Throwable e = result.getException();
// Record common exception.
Tracer.traceEntry(e, interfaceEntry);
Tracer.traceEntry(e, methodEntry);
}
return result;
} catch (BlockException e) {
return DubboFallbackRegistry.getConsumerFallback().handle(invoker, invocation, e); // @7
} catch (RpcException e) {
Tracer.traceEntry(e, interfaceEntry);
Tracer.traceEntry(e, methodEntry);
throw e;
} finally {
if (methodEntry != null) { // @8
methodEntry.exit();
}
if (interfaceEntry != null) {
interfaceEntry.exit();
}
}
}
}
程式碼@1:通過 @Activate 註解定義該 Filter 在客戶端生效。
程式碼@2:在 Sentinel 中一個非常核心的概念就是資源,即要定義限流的目標,當出現什麼異常(匹配用戶配置的規則)對什麼進行熔斷操作,Dubbo 服務中的資源通常是 Dubbo 服務,分為服務介面級或方法級,故該方法返回 Dubbo 的資源名,其主要實現特徵如下:
- 如果啟用用戶定義資源的前綴,默認為 false ,可以通過配置屬性:csp.sentinel.dubbo.resource.use.prefix 來定義是否需要啟用前綴。如果啟用前綴,消費端的默認前綴為 dubbo:consumer:,可以通過配置屬性 csp.sentinel.dubbo.resource.consumer.prefix 來自定義消費端的資源前綴。
- Dubbo 資源的名稱表示方法為:interfaceName + “:” + methodName + “(” + “paramTyp1參數列表,多個用 , 隔開” + “)”。
程式碼@3:調用 Sentinel 核心API SphU.entry 進入 Dubbo InterfaceName。從方法的名稱我們也能很容易的理解,就是使用 Sentienl API 進入資源名為 Dubbo 介面提供者類全路徑限定名,即認為調用該方法,Sentienl 會收集該資源的調用資訊,然後Sentinel 根據運行時收集的資訊,再配合限流規則,熔斷等規則進行計算是否需要限流或熔斷。本節我們不打算深入研究 SphU 的核心方法研究,先初步了解該方法:
-
String name 資源的名稱。
-
int resourceType 資源的類型,在 Sentinel 中目前定義了 如下五中資源:
- ResourceTypeConstants.COMMON
同樣類型。 - ResourceTypeConstants.COMMON_WEB
WEB 類資源。 - ResourceTypeConstants.COMMON_RPC
RPC 類型。 - ResourceTypeConstants.COMMON_API_GATEWAY
介面網關。 - ResourceTypeConstants.COMMON_DB_SQL
資料庫 SQL 語句。
- ResourceTypeConstants.COMMON
-
EntryType type
進入資源的方式,主要分為 EntryType.OUT、EntryType.IN,只有 EntryType.IN 方式才能對資源進行阻塞。
程式碼@4:調用 Sentinel 核心API SphU.entry 進入 Dubbo method 級別。
程式碼@5:調用 Dubbo 服務提供者方法。
程式碼@6:如果出現調用異常,可以通過 Sentinel 的 Tracer.traceEntry 跟蹤本次調用資源進入的情況,詳細 API 將在該系列的後續文章中詳細介紹。
程式碼@7:如果是由於觸發了限流、熔斷等操作,拋出了阻塞異常,可通過 註冊 ConsumerFallback 來實現消費者快速失敗,將在下文詳細介紹。
程式碼@8: SphU.entry 與 資源的 exit 方法需要成對出現,否則會出現統計錯誤。
2、源碼分析 SentienlDubboProviderFilters
@Activate(group = "provider")
public class SentinelDubboProviderFilter implements Filter {
public SentinelDubboProviderFilter() {
RecordLog.info("Sentinel Apache Dubbo provider filter initialized");
}
@Override
public Result invoke(Invoker<?> invoker, Invocation invocation) throws RpcException {
// Get origin caller.
String application = DubboUtils.getApplication(invocation, "");
Entry interfaceEntry = null;
Entry methodEntry = null;
try {
String resourceName = DubboUtils.getResourceName(invoker, invocation, DubboConfig.getDubboProviderPrefix()); // @1
String interfaceName = invoker.getInterface().getName();
// Only need to create entrance context at provider side, as context will take effect
// at entrance of invocation chain only (for inbound traffic).
ContextUtil.enter(resourceName, application);
interfaceEntry = SphU.entry(interfaceName, ResourceTypeConstants.COMMON_RPC, EntryType.IN); // @2
methodEntry = SphU.entry(resourceName, ResourceTypeConstants.COMMON_RPC,
EntryType.IN, invocation.getArguments());
Result result = invoker.invoke(invocation);
if (result.hasException()) {
Throwable e = result.getException();
// Record common exception.
Tracer.traceEntry(e, interfaceEntry);
Tracer.traceEntry(e, methodEntry);
}
return result;
} catch (BlockException e) {
return DubboFallbackRegistry.getProviderFallback().handle(invoker, invocation, e); // @3
} catch (RpcException e) {
Tracer.traceEntry(e, interfaceEntry);
Tracer.traceEntry(e, methodEntry);
throw e;
} finally {
if (methodEntry != null) {
methodEntry.exit(1, invocation.getArguments());
}
if (interfaceEntry != null) {
interfaceEntry.exit();
}
ContextUtil.exit();
}
}
}
Dubbo 服務提供者與消費端的適配套路差不多,這裡就重點闡述一下其不同點。
程式碼@1:如果啟用前綴,默認服務提供者的資源會加上前綴:dubbo:provider:,可以通過在配置文件中配置屬性 csp.sentinel.dubbo.resource.provider.prefix 改變其默認值。
程式碼@2:服務端調用 SphU.entry 時其進入類型為 EntryType.IN。
程式碼@3:同樣可以在 拋出阻塞異常(BlockException) 時指定快速失敗回調處理邏輯。
3、Sentienl Dubbo FallBack 機制
Sentinel Dubbo FallBack 機制比較簡單,就是提供一個全局的 FallBack 回調,可以分別為服務提供端,服務消費端指定。只需實現 DubboFallback 介面,其聲明如下:
然後需要調用 DubboFallbackRegistry 的 setConsumerFallback 和 setProviderFallback 方法分別註冊消費端,服務端相關的監聽器。通常只需要在啟動應用的時候,將其進行註冊即可。
4、總結
本文只是以 Sentienl 對 Dubbo 的適配實現來了解 Sentinel 核心相關的 API,其核心實現就是利用 Dubbo 的 Filter 機制進行無縫的過濾攔截。但本文只是提到 Sentinel 如下核心方法:
- SphU.entry
- Entry.exit
- Tracer.traceEntry
上述這些方法,將在後面的文章中進行深入探究,即從下一篇文章開始,我們將真正進入 Sentinel 的世界中,讓我們一探究竟限流、熔斷通常是如何實現的。
本文就介紹到這裡了,點贊是一種美德,您的點贊是我持續分享的最大動力,謝謝。
推薦閱讀:源碼分析 Alibaba Sentinel 專欄。
1、Alibaba Sentinel 限流與熔斷初探(技巧篇)
作者資訊:丁威,《RocketMQ技術內幕》作者,目前擔任中通科技技術平台部資深架構師,維護 中間件興趣圈公眾號,目前主要發表了源碼閱讀java集合、JUC(java並發包)、Netty、ElasticJob、Mycat、Dubbo、RocketMQ、mybaits等系列源碼。點擊鏈接:加入筆者的知識星球,一起探討高並發、分散式服務架構,分享閱讀源碼心得。