原始碼分析 Sentinel 之 Dubbo 適配原理
在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
- 2、原始碼分析 SentienlDubboProviderFilters
- 3、Sentienl Dubbo FallBack 機制
- 4、總結
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等系列原始碼。點選連結:加入筆者的知識星球,一起探討高併發、分散式服務架構,分享閱讀原始碼心得。