1. 程式人生 > >原始碼分析 Sentinel 之 Dubbo 適配原理

原始碼分析 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 語句。
  • 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等系列原始碼。點選連結:加入筆者的知識星球,一起探討高併發、分散式服務架構,分享閱讀原始碼心得。