Davids原理探究:Dubbo過濾器原理

Dubbo過濾器原理

概述

Dubbo過濾器提供了服務提供者和消費者的調用攔截,即每次執行RPC調用的時候,對應的過濾器都會生效。雖然過濾器功能強大,但由於每次調用的時候都會執行,因此在使用的時候需要注意它對性能的影響。

使用方式

在Dubbo中又很多內置過濾器,並且大多數都是使用@Activate註解默認啓用的,比如ContextFilter。對於自行擴展的過濾器有以下兩種啓用方式。

@Activate註解啓用

在自定義的過濾器類上添加@Activate(group = Constants.PROVIDER/CONSUMER, order = -10000)註解即可,group標識是提供者/消費者過濾器,order標識優先級(越小越先執行)。

XML配置啓用

<!-- 消費方調用過程攔截 -->
<dubbo:reference filter="xxx,yyy"/>

<!-- 消費方調用過程默認攔截器,將攔截所有reference -->
<dubbo:consumer filter="xxx,yyy"/>

<!-- 服務提供方調用過程攔截 -->
<dubbo:service filter="xxx,yyy"/>

<!-- 服務提供方調用過程默認攔截器,將攔截所有reference -->
<dubbo:provider filter="xxx,yyy"/>

規則

  1. 過濾器順序:
    1. 用戶自定義的過濾器順序默認會在框架內置過濾器之後,我們可以使用filter="xxx,default"這種配置方式讓自定義過濾器順序靠前。
    2. 我們在配置filter="xxx,yyy"的時候,寫在前面的xxx會比yyy的順序要靠前。
  2. 剔除過濾器:對於一些默認過濾器或一些自動激活的過濾器,有些方法不想使用這些過濾器,則可以使用"-“加過濾器名稱來過濾,如filter=”-ContextFilter"會讓ContextFilter不生效,如果不想使用所有默認啓動的過濾器,則可以配置filter="-default"來進行剔除。
  3. 過濾器的疊加:如果服務提供者消費者都配置了過濾器,則兩邊過濾器不會互相覆蓋,而是互相疊加,都會生效。如果需要覆蓋,則可以在消費方使用"-"的方式剔除對應的過濾器。

總體結構圖

Dubbo過濾器總體結構圖

作用列表

過濾器名稱 作用 使用方
AccessLogFilter 打印每一次請求的訪問日誌。如果需要訪問的日誌只出現在指定的appender中,則可以在log的配置文件中配置additivity provider
ActiveLimitFilter 用於限制消費者端對服務器端的最大並行調用數 consumer
ExecuteLimitFilter 同上,用於限制服務端的最大並行調用數 provider
CassLoaderFilter 用於切換不同線程的類加載器,服務調用完成後還原回去 provider
CompatibleFilter 用於返回值與調用程序的對象版本兼容,默認不啓用。如果啓用,則會把JSON或fastjson類型的返回值轉換爲MapMap類型;如果返回類型和本地接口中定義的不同,則會做POJO的轉換 -
ConsumerContextFilter 爲消費者把一些上下文信息設置到當前線程的RpcContext對象中,包括invocation、localhost、remote host consumer
ContextFilter 同上,但是爲服務端提供服務 provider
DeprecatedFilter 如果調用的方法被標記爲已棄用,那麼DeprecatedFilter將記錄一條錯誤信息 consumer
EchoFilter 用於回聲測試 provider
ExceptionFilter 用於統一異常處理,防止序列化失敗 provider
GenericFilter 用於服務提供者,實現泛化調用,用於序列化的檢查和處理 provider
GenericImplFilter 同上,但用於消費端 consumer
TimeoutFilter 如果某些服務調用超時,則自動記錄告警日誌 provider
TokenFilter 服務提供者下發令牌給消費者,通常用於防止消費者繞過註冊中心直接調用服務提供者 provider
TpsLimitFilter 用於服務端限流,注意與ExecuteLimitFilter區分 provider
FutureFilter 在發起invoke或得到返回值、出現異常的時候觸發回調事件 consumer
TraceFilter Trace指令的使用 provider
MonitorFilter 監控並統計所有接口的調用情況,如成功、失敗、耗時。後續DubboMonitor會定時把該過濾器收集的數據發送到Dubbo-Monitor服務上 provider+consumer

過濾器鏈初始化的實現原理

服務暴露與引用的時候會使用protocol層,而ProtocolFilterWrapper包裝類則實現了過濾器鏈的組裝。在服務的暴露和引用的過程中,會使用ProtocolFilterWrapper#buildInvokerChain方法組裝整個過濾器鏈。

// ProtocolFilterWrapper類
@Override
public <T> Exporter<T> export(Invoker<T> invoker) throws RpcException {
    if (Constants.REGISTRY_PROTOCOL.equals(invoker.getUrl().getProtocol())) {
        return protocol.export(invoker);
    }
    // 標識自己是provider
    return protocol.export(buildInvokerChain(invoker, Constants.SERVICE_FILTER_KEY, Constants.PROVIDER));
}

@Override
public <T> Invoker<T> refer(Class<T> type, URL url) throws RpcException {
    if (Constants.REGISTRY_PROTOCOL.equals(url.getProtocol())) {
        return protocol.refer(type, url);
    }
    // 標識自己是consumer
    return buildInvokerChain(protocol.refer(type, url), Constants.REFERENCE_FILTER_KEY, Constants.CONSUMER);
}

ProtocolFilterWrapper#buildInvokerChain總的來說分爲兩步:

  1. 獲取並遍歷所有過濾器。通過Dubbo SPIExtensionLoader#getActivateExtension方法獲取所有的過濾器並遍歷。
  2. 使用裝飾器模式,增強原有的Invoker,組裝過濾器鏈。
private static <T> Invoker<T> buildInvokerChain(final Invoker<T> invoker, String key, String group) {
    Invoker<T> last = invoker;
    // 獲取所有的過濾器,包括有@Activate註解默認啓動的和用戶在XML中自定義的
    List<Filter> filters = ExtensionLoader.getExtensionLoader(Filter.class).getActivateExtension(invoker.getUrl(), key, group);
    if (!filters.isEmpty()) {
    	// 對過濾器做倒排遍歷,從尾到頭
        for (int i = filters.size() - 1; i >= 0; i--) {
            final Filter filter = filters.get(i);
            // 將實際的調用invoker放到最後面
            // 比如過濾器有A、B、C,則先放last(實際invoker)然後再放C,此時會變成C->Invoker
            // 然後再放B,變成B->C->Invoker,最後放A,此時變成A->B->C->Invoker調用鏈組裝完成
            final Invoker<T> next = last;
            last = new Invoker<T>() {

                ...

                @Override
                public Result invoke(Invocation invocation) throws RpcException {
                    return filter.invoke(next, invocation);
                }

                ...
                
            };
        }
    }
    return last;
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章