源碼分析 Sentinel 之 Dubbo 適配原理

Alibaba Sentinel 限流與熔斷初探(技巧篇) 的示例中我選擇了 sentinel-demo-apache-dubbo 做爲突破點,故本文就從該項目入手,看看 Sentinel 是如何對 Dubbo 作的適配,讓項目使用方無感知,只須要引入對應的依便可。java

sentinel-apache-dubbo-adapter 比較簡單,展開以下:
在這裏插入圖片描述
上面的代碼應該比較簡單,在正式進入源碼研究以前,我先拋出以下二個問題:數據庫

  • 一、限流、熔斷相關的功能是在 Dubbo 的客戶端實現仍是服務端實現?爲何?
  • 二、如何對 Dubbo 進行功能擴展而無需改動業務代碼?

Dubbo 提供了 Filter 機制對功能進行無縫擴展,有關 Dubbo Filter 機制,你們能夠查閱筆者的源碼研究 Dubbo 系列:Dubbo Filter機制概述apache

接下來咱們帶着上面的問題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 在客戶端生效。app

代碼@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 的核心方法研究,先初步瞭解該方法:ide

  • 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 方法須要成對出現,不然會出現統計錯誤。

二、源碼分析 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) 時指定快速失敗回調處理邏輯。

三、Sentienl Dubbo FallBack 機制

Sentinel Dubbo FallBack 機制比較簡單,就是提供一個全局的 FallBack 回調,能夠分別爲服務提供端,服務消費端指定。只需實現 DubboFallback 接口,其聲明以下:
在這裏插入圖片描述
而後須要調用 DubboFallbackRegistry 的 setConsumerFallback 和 setProviderFallback 方法分別註冊消費端,服務端相關的監聽器。一般只須要在啓動應用的時候,將其進行註冊便可。

四、總結

本文只是以 Sentienl 對 Dubbo 的適配實現來了解 Sentinel 核心相關的 API,其核心實現就是利用 Dubbo 的 Filter 機制進行無縫的過濾攔截。但本文只是提到 Sentinel 以下核心方法:

  • SphU.entry
  • Entry.exit
  • Tracer.traceEntry

上述這些方法,將在後面的文章中進行深刻探究,即從下一篇文章開始,咱們將真正進入 Sentinel 的世界中,讓咱們一探究竟限流、熔斷一般是如何實現的。

本文就介紹到這裏了,點贊是一種美德,您的點贊是我持續分享的最大動力,謝謝。

推薦閱讀:源碼分析 Alibaba Sentinel 專欄。
一、Alibaba Sentinel 限流與熔斷初探(技巧篇)


做者信息:丁威,《RocketMQ技術內幕》做者,目前擔任中通科技技術平臺部資深架構師,維護 中間件興趣圈公衆號,目前主要發表了源碼閱讀java集合、JUC(java併發包)、Netty、ElasticJob、Mycat、Dubbo、RocketMQ、mybaits等系列源碼。點擊連接:加入筆者的知識星球,一塊兒探討高併發、分佈式服務架構,分享閱讀源碼心得。

相關文章
相關標籤/搜索