Dubbo進階(十五)- Dubbo中 Filter 探究

Filter 機制也稱攔截器機制,在衆多框架或者語言中很常見,可以實現登錄鑑權,網關攔截、封裝全局狀態返回等,博主文章以下幾個問題展開:

  1. Filter 的例子
  2. Dubbo中內置的Filter是怎樣的?Consumer 和Provider 默認使用的 Filter 有哪些?
  3. Filter 何時初始化?
  4. Filter 何時會被調用?
  5. Filter 中 ListenableFilter 有何作用?
  6. Consumer 和Provider 如何使用Dubbo 內置Filter?
  7. 從Filter 看 自定義SPI 在哪些地方會被使用

以上問題看完文章後相信大家就可以清楚,若有疑問,關注博主公衆號:六點A君,回覆標題獲取最新答案><

Filter 是Dubbo 的一個擴展點,可以理解爲攔截作用,Dubbo 本身大多功能都基於該擴展點實現。

Filter 例子

先說說Dubbo 中Filter 的使用,以下摘抄自Dubbo官網:

  1. 用戶自定義 filter 默認在內置 filter 之後。
  2. 特殊值 default,表示缺省擴展點插入的位置。比如:filter="xxx,default,yyy",表示 xxx 在缺省 filter 之前,yyy 在缺省 filter 之後。
  3. 特殊符號 -,表示剔除。比如:filter="-foo1",剔除添加缺省擴展點 foo1。比如:filter="-default",剔除添加所有缺省擴展點。
  4. providerservice 同時配置的 filter 時,累加所有 filter,而不是覆蓋。比如:<dubbo:provider filter="xxx,yyy"/><dubbo:service filter="aaa,bbb" />,則 xxx,yyy,aaa,bbb 均會生效。如果要覆蓋,需配置:<dubbo:service filter="-xxx,-yyy,aaa,bbb" />

Dubbo 文檔:http://dubbo.apache.org/zh-cn/docs/dev/impls/filter.html

Dubbo 中使用 Filter 有兩種途徑,可以使用Dubbo內置的Filter,或者可以使用自定義的Filter
以自定義Filter 爲例,其中帶入

  1. 定義類實現 org.apache.dubbo.rpc.Filter
  2. META-INF/dubbo 下增加 增加 SPI 文件 org.apache.dubbo.rpc.Filter,填寫SPI 映射,例如 traceConsumer=com.anla.rpc.filter.consumer.filter.ConsumerTraceFilter
  3. 在 Consumer 端 的 dubbo:consumerdubbo:reference 進行聲明,例如 <dubbo:consumer filter="traceConsumer"/>,同理可以在 Provider 端的 dubbo:providerdubbo:service 進行聲明。
    當然在這一步可以使用 Dubbo內置的Filter 進行使用。也可以通過 - (減號) 進行內置Filter 刪除。

具體Filter 例子可以看博主寫的例子:https://github.com/anLA7856/dubbolearn/tree/master/filter

Dubbo 內置的Filter

如何找Dubbo內置Filter呢?
由於Dubbo 提供SPI 機制,所以我們可以沿着這條路往下找,即找到 `org.apache.dubbo.rpc.Filter`` 在找到其有哪些子類就可以了。
在這裏插入圖片描述
上面圖片看起來過於眼花繚亂,下面博主整理表格或許來的更加直觀(起始下面更眼花繚亂,各位看官直接過了就好,需要再來細看)

類名 @Adaptive 是否默認Consumer 是否默認Provider 排序 作用
ClassLoaderFilter @Activate(group = CommonConstants.PROVIDER, order = -30000) 是,默認存在 -30000 用於添加invoker的ClassLoader 到本地線程中
DeprecatedFilter @Activate(group = CommonConstants.CONSUMER, value = DEPRECATED_KEY) 是,需要指定 值爲 deprecated 用於提醒錯誤如果接口配置了deprecated,則會打印錯誤
EchoFilter @Activate(group = CommonConstants.PROVIDER, order = -110000) 是,默認有 -110000 用於在服務端提供基於EchoService的功能鏈路相應功能
MetricsFilter 用於一些統計接口監控統計,提供report 功能
TpsLimitFilter @Activate(group = CommonConstants.PROVIDER, value = TPS_LIMIT_RATE_KEY) 是,需要指定,值爲tpc 限流作用,用於限制tps,用戶可自行配置
AccessLogFilter @Activate(group = PROVIDER, value = ACCESS_LOG_KEY) 是,需要指定,值爲accesslog 用於打印接口log
ValidationFilter @Activate(group = {CONSUMER, PROVIDER}, value = VALIDATION_KEY, order = 10000) 是,需要指定 是,需要指定 10000 在配置完validation後,在具體調用前既可以使用,用戶可以基於org.apache.dubbo.validation.Validation 的 SPI方式實現新Validation
DubboAppContextFilter @Activate(group = "consumer") 是,默認有 用於將當前Conusmer 的applicationName 放入attachment中
CacheFilter @Activate(group = {CONSUMER, PROVIDER}, value = CACHE_KEY) 是,需要指定,值爲cache 是,需要指定,值爲cache 爲Dubbo的核心組件,支持 service,method,consumer or provider 四種粒度緩存
TokenFilter @Activate(group = CommonConstants.PROVIDER, value = TOKEN_KEY) 有,需要指定,value爲token 用於鑑權訪問
TraceFilter @Activate(group = CommonConstants.PROVIDER) 有,默認存在 用於鏈路跟蹤,放入相應信息
SentinelDubboProviderFilter @Activate(group = "provider") 僅Provder,默認有 爲Sentinel 提供的 服務端Filter
GenericImplFilter @Activate(group = CommonConstants.CONSUMER, value = GENERIC_KEY, order = 20000) 是,需要指定,值爲generic 20000 用於支持泛化generic 調用
MonitorFilter @Activate(group = {PROVIDER, CONSUMER}) 是,默認有 是,默認有 用於監控接口調用
ContextFilter @Activate(group = PROVIDER, order = -10000) 是,默認有 -10000 在invoker中設置服務端的RpcContext
ExceptionFilter @Activate(group = CommonConstants.PROVIDER) 是,默認有 unexpect 異常將會在provider 中error層級記錄
CompatibleFilter 爲版本兼容所有,讓rpc調用返回值可以兼容舊版本的invoker 所需
GenericFilter @Activate(group = CommonConstants.PROVIDER, order = -20000) 有,默認有 -20000 用於實現和 generic 泛化調用相關邏輯
FutureFilter @Activate(group = CommonConstants.CONSUMER) 有,默認有 屬於一個事件的Filter,待研究
TimeoutFilter @Activate(group = CommonConstants.PROVIDER) 記錄 超時的 invocation,但是不阻止該invoker 運行,也就是會照常運行
ActiveLimitFilter @Activate(group = CONSUMER, value = ACTIVES_KEY) 有,需要指定 用於限制 client 端的調用量
ConsumerContextFilter @Activate(group = CONSUMER, order = -10000) 有,默認有 -10000 用於設定RpcContext中一些屬性,例如invoker,invocation等。
ExecuteLimitFilter @Activate(group = CommonConstants.PROVIDER, value = EXECUTES_KEY) 有,需要指定 executes 用於限制,單method 下 ,最大並行請求數限制

從上面分析來看,當程序沒有顯示指定Filter,那麼Consumer 和 Provider 存在的Filter 如下:

Provider Consumer
EchoFilter,ClassLoaderFilter, GenericFilter, ContextFilter, TraceFilter, TimeoutFilter, MonitorFilter, ExceptionFilter ,SentinelDubboProviderFilter ConsumerContextFilter, FutureFilter,MonitorFilter, DubboAppContextFilter

以上得出 ConsumerProvider 可以由 上面表格推到出,一方面也可以看到 @Activate 註解用途:

  1. 如果在 @Adaptive 有指定 value,則需要配置才能被加載。
  2. 如果沒有 @Adaptive,則無法被加載
  3. Filter 中 排序按照 order 來的,倒序排。
  4. 最後有刪除的 SentinelDubboProviderFilter 實際上不屬於Dubbo 的包,它的@Adaptive 註解本應被加載,但是如果沒有引入相應starter 文件,不回被加載。。只有當被用到時,纔會被加載,纔會 sentinel 的 SPI 文件纔會被加載,纔會被加入到默認的SPI 中。
  5. 所以在編寫代碼是,如果使用了 @Adaptive註解,滿足條件後,即使不配置filter,也會生效,就是這個道理。

Filter 何時初始化

Filter 初始化是伴隨着 ProtocolFilterWrapper 使用而初始化。而通過前面文章可知,ProtocolFilterWrapper 作爲包裝類,當有Protocol 初始化,它就會初始化,並且由鏈路調用一層一層下去。

    @Override
    public <T> Exporter<T> export(Invoker<T> invoker) throws RpcException {
        if (REGISTRY_PROTOCOL.equals(invoker.getUrl().getProtocol())) {
            return protocol.export(invoker);
        }
        return protocol.export(buildInvokerChain(invoker, SERVICE_FILTER_KEY, CommonConstants.PROVIDER));
    }

    @Override
    public <T> Invoker<T> refer(Class<T> type, URL url) throws RpcException {
        if (REGISTRY_PROTOCOL.equals(url.getProtocol())) {
            return protocol.refer(type, url);
        }
        return buildInvokerChain(protocol.refer(type, url), REFERENCE_FILTER_KEY, CommonConstants.CONSUMER);
    }

當 Provider 暴露服務會調用 export 方法,而 Consumer 則會調用refer 方法,上述方法都做了以下同樣的事:

  1. 首先初始化註冊中心協議
  2. 調用 buildInvokerChain 構造Filter 鏈。

下面看看 buildInvokerChain

  private static <T> Invoker<T> buildInvokerChain(final Invoker<T> invoker, String key, String group) {
        Invoker<T> last = invoker;
        // 獲取 group 和key下所有的 Filter 的 SPI 類文件
        List<Filter> filters = ExtensionLoader.getExtensionLoader(Filter.class).getActivateExtension(invoker.getUrl(), key, group);
		// 構建Filter 鏈
        if (!filters.isEmpty()) {
            for (int i = filters.size() - 1; i >= 0; i--) {
                final Filter filter = filters.get(i);
                final Invoker<T> next = last;
                last = new Invoker<T>() {

                    @Override
                    public Class<T> getInterface() {
                        return invoker.getInterface();
                    }

                    @Override
                    public URL getUrl() {
                        return invoker.getUrl();
                    }

                    @Override
                    public boolean isAvailable() {
                        return invoker.isAvailable();
                    }

                    @Override
                    public Result invoke(Invocation invocation) throws RpcException {
                        Result asyncResult;
                        try {
                        // 直接執行filter 的invoke
                            asyncResult = filter.invoke(next, invocation);
                        } catch (Exception e) {
                            // onError callback
                            if (filter instanceof ListenableFilter) {
                                Filter.Listener listener = ((ListenableFilter) filter).listener();
                                if (listener != null) {
                                    listener.onError(e, invoker, invocation);
                                }
                            }
                            throw e;
                        }
                        return asyncResult;
                    }

                    @Override
                    public void destroy() {
                        invoker.destroy();
                    }

                    @Override
                    public String toString() {
                        return invoker.toString();
                    }
                };
            }
        }

        return new CallbackRegistrationInvoker<>(last, filters);
    }

上面方法看起來也容易理解:

  1. 通過SPI 中,加載 Filter 的擴展類。具體加載規則可以看博主前面文章以及上文表格分析
  2. 將每個Filter鏈起來,即包裝每一個Filter。這樣當有請求時候,就會依次執行每一個鏈起來的Filter
  3. 最後返回 CallbackRegistrationInvoker 的封裝對象

Filter 中有個特殊的子類:ListenableFilter

public abstract class ListenableFilter implements Filter {

    protected Listener listener = null;

    public Listener listener() {
        return listener;
    }
}

Listener則 爲 Filter 中的內部接口,依附 Filter 存在:

    interface Listener {
		// 當結果返回時調用
        void onResponse(Result appResponse, Invoker<?> invoker, Invocation invocation);
		// 當調用方出現異常時調用
        void onError(Throwable t, Invoker<?> invoker, Invocation invocation);
    }

有部分Filter是 實現自 Listener,故其有監聽作用。
buildInvokerChain 中綁定 invoke 鏈時,則使用了 onError,當Filter 出錯時,則會記錄並調用 onError 方法:

                        try {
                            asyncResult = filter.invoke(next, invocation);
                        } catch (Exception e) {
                            // onError callback
                            if (filter instanceof ListenableFilter) {
                                Filter.Listener listener = ((ListenableFilter) filter).listener();
                                if (listener != null) {
                                    listener.onError(e, invoker, invocation);
                                }
                            }
                            throw e;
                        }

而再看 buildInvokerChain 返回的 CallbackRegistrationInvoker 時,在其invoke方法中可以看到,在 Filter鏈 中最後一個 調用 完之後,則會進行異步封裝:

        @Override
        public Result invoke(Invocation invocation) throws RpcException {
        // 異步封裝
            Result asyncResult = filterInvoker.invoke(invocation);

            asyncResult.thenApplyWithContext(r -> {
                for (int i = filters.size() - 1; i >= 0; i--) {
                    Filter filter = filters.get(i);
                    // 告知 ListenableFilter 的子類
                    if (filter instanceof ListenableFilter) {
                        Filter.Listener listener = ((ListenableFilter) filter).listener();
                        if (listener != null) {
                            listener.onResponse(r, filterInvoker, invocation);
                        }
                    } else {
                        filter.onResponse(r, filterInvoker, invocation);
                    }
                }
                return r;
            });

            return asyncResult;
        }

並且對 asyncResult 異步調用完結果進行監聽,並且循環filter,依次調用他們的onResponse方法。

如何 定位 SPI 的使用點

本篇以Filter 這個SPI 類型 爲例,並且分析了 @Adaptive 註解 對於加載SPI 文件區別。因而總結下
SPI 使用點:

  1. 查找其基接口,看何處被 ExtensionLoader 加載
  2. 分析 ExtensionLoader 中使用 什麼方法加載初 其相關聯的SPI文件,方法可能爲:getActivateExtensiongetActivateExtensiongetExtension …,不同方法加載出的SPI擴展類是不一樣的,都是圍繞 SPI配置文件@Adaptive註解,@SPI 註解進行。
  3. 獲取完具體SPI擴展類之後,就是對類的操作了,這個要看具體邏輯代碼了。

覺得博主寫的有用,不妨關注博主公衆號: 六點A君。
哈哈哈,Dubbo小吃街不迷路:
在這裏插入圖片描述

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