Dubbo源碼閱讀——過濾器鏈


對於rpc框架,過濾器肯定是需要支持的。Dubbo中的過濾器和web應用的過濾器概念是一樣的,提供服務調用前後插入自定義邏輯的途徑,可針對所有調用,也可針對部分服務的調用,Dubbo提供靈活的配置。過濾器是整個Dubbo框架的重要組成部分,Dubbo的很多功能都是基於過濾器擴展而來的。Dubbo的過濾器同樣支持通過SPI機制擴展。需要注意的是,對於某一支服務,每一次調用都會執行對應的過濾器,所以擴展的時候要考慮對性能的影響。
注:本文Dubbo版本爲 2.6.5

1. 過濾器的使用

Dubbo已經有很多的內置過濾器,並且大多是默認啓用的。不管是已實現的過濾器,自定義擴展的過濾器,啓用方式有兩種。

  • 使用@Activate註解默認啓用
  • 在配置文件中配置

1.1 @Activate註解啓用過濾器

@Activate註解在應用啓動時會加載幷包裝到Invoker中,可以通過配置group屬性控制過濾器對Provider或Consumer生效,還可以通過order參數設置過濾器執行順序,order越小,越先執行。例如:
@Activate(group = {Constants.CONSUMER, Constants.PROVIDER}, value = Constants.CACHE_KEY, order = -10000)

1.2 xml配置啓用過濾器

1.2.1 配置方式
	<!--攔截所有service-->
    <dubbo:provider filter="aaa,bbb"/>
    <!--服務提供方過濾器-->
    <dubbo:service interface="com.alibaba.dubbo.demo.DemoService" ref="demoService" filter="aaa,bbb"/>
    <!--攔截所有reference-->
    <dubbo:consumer filter="aaa,bbb"/>
    <!--服務消費方過濾器-->
    <dubbo:reference interface="com.alibaba.dubbo.demo.DemoService" filter="aaa,bbb"/>
1.2.2 過濾器順序

用戶自定義過濾器默認在框架內置過濾器之後執行,我們可以使用 filter = "aaa,default"方式調整過濾器順序。
寫在前面的過濾器比後面的先執行。

1.2.3 剔除過濾器

可以使用filter = "-xxxFilter"使xxxFilter不生效。filter = "-default"可剔除所有默認過濾器。

1.2.4 過濾器的疊加

如果服務端和消費端都配置了同一個過濾器,兩邊都會執行該過濾器。不會覆蓋。

2.Dubbo內置過濾器

Dubbo實現的內置過濾器有十幾種,包括服務端過濾器和消費端過濾器,以及特殊的MonitorFilter,它會在服務暴露和服務引用是同時包裝到Invoker。
下面是部分過濾器功能列表:

過濾器名 使用方 作用
AccessLogFilter P 打印每一次請求的日誌
ActiveLimitFilter C 用於限制消費端對服務端的最大並行數
ExecuteLimitFilter P 用於限制服務端的最大並行數
ClassLoaderFilter P 用於切換不同線程的類加載器,服務調用完後會還原回去
ConsumerContextFilter C 爲消費端把一些上下文信息設置到當前線程的RpcContext對象中,主要是invocation,local host,remote host
ContextFilter P 爲服務端把一些上下文信息設置到當前線程的RpcContext對象中
DeprecatedFilter C 如果被調用的方法標記爲被棄用,DeprecatedFilter 會記錄一條錯誤信息
EchoFilter P 用於回聲測試
ExceptionFilter P 統一異常處理,防止序列化失敗
GenericFilter P 服務端實現泛化調用,實現序列化檢查和處理
GenericImplFilter C 消費端實現泛化調用,實現序列化檢查和處理
TimeoutFilter P 如果服務調用超時,記錄告警日誌
TokenFilter P 服務端下發令牌給消費端,用於防止消費端繞過註冊中心,直連服務端
TpsLimitFilter P 用於服務端限流
FutureFilter C 在發起invoke調用,或得到返回值,或出現異常時觸發回調事件
MonitorFilter P & C 監控並統計所有接口的調用情況,如成功、失敗、耗時。後續DubboMonitor會把該過濾器收集到的數據定時發送到Dubbo-Monitor服務上

3.Dubbo過濾器實現原理

Dubbo過濾器是在服務暴露(見https://blog.csdn.net/weixin_41172473/article/details/103301463)或服務引用(見https://blog.csdn.net/weixin_41172473/article/details/103524664)的時候加載並通過裝飾器模式層層包裝到Invoker上。
下面以服務端爲例,探究服務端過濾器鏈的組裝過程,消費端同理:

3.1 加載Protocol

服務暴露有一個重要過程就是調用Protocol接口的export方法生成Exporter,將服務暴露出去,而這個protocol實例像Dubbo的許多組件一樣,是通過ExtensionLoader加載的(關於Dubbo擴展加載機制見https://blog.csdn.net/weixin_41172473/article/details/102905655),
debug一下ExtensionLoader加載Protocol的過程:
在這裏插入圖片描述
可以看到,最後返回出去的不是DubboProtocol類型的實例,而是將DubboProtocol類型的實例包裝成ProtocolFilterWrapper類型,當然還可能會有其他包裝類型,比如ProtocolListenerWapper。
加載完成後,服務暴露過程調用protocol.export()方法,就會走ProtocolFilterWrapper#export()方法,這裏面就封裝了過濾器組裝的邏輯。

3.2 組裝過濾器 ProtocolFilterWrapper#buildInvokerChain()

在看組裝過濾器代碼之前,先看看Filter接口:

@SPI
public interface Filter {
    Result invoke(Invoker<?> invoker, Invocation invocation) throws RpcException;
}

這裏的invoke方法的實現,其實就是織入自己的過濾器邏輯,然後調用Invoker的invoke方法,返回結果也是invoke方法的結果。
回到組裝過濾器邏輯:
ProtocolFilterWrapper#export()方法調用buildInvokerChain()方法將Filter做層層包裝,導出帶有過濾器邏輯的Invoker。

	@Override
    public <T> Exporter<T> export(Invoker<T> invoker) throws RpcException {
        if (Constants.REGISTRY_PROTOCOL.equals(invoker.getUrl().getProtocol())) {
            return protocol.export(invoker);
        }
        // 暴露帶有過濾器邏輯的Invoker
        return protocol.export(buildInvokerChain(invoker, Constants.SERVICE_FILTER_KEY, Constants.PROVIDER));
    }
    
	private static <T> Invoker<T> buildInvokerChain(final Invoker<T> invoker, String key, String group) {
        Invoker<T> last = invoker;
        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);
                final Invoker<T> next = last;
                last = new Invoker<T>() {
					//這裏省略其他Override方法
					
                    @Override
                    public Result invoke(Invocation invocation) throws RpcException {
                    	// 調用裏層filter的invoke方法
                        return filter.invoke(next, invocation);
                    }

                };
            }
        }
        return last;
    }

代碼比較簡單,就是循環包裝過濾器,Filter接口的invoke方法只是在調用真實Invoker.invoke()方法前後加入自己的邏輯,然後返回真實invoke調用的結果。這裏過濾器鏈的順序比較有意思,通過反向遍歷filterList,組裝到Invoker上,保證最後調用過濾器的順序是正的,類似入棧和出棧。

4.總結

Dubbo通過裝飾器模式實現過濾器鏈,原理重點是一下三個過程:

  1. 在加載Protocol的時候將Protocol包裝成ProtocolFilterWrapper,在這裏面加上組裝過濾器的邏輯。
  2. 當服務暴露調用protocol.export()或者服務引用調用protocol.refer()時,執行組裝過濾器的邏輯。
  3. Filter通過反覆增強Invoker.invoke(),在其調用前後加入各個過濾器自己的邏輯,實現過濾器鏈的織入。

Dubbo內置的過濾器很多,有些默認啓用,有些需要我們配置了才能生效,知道了過濾器的配置方式和實現原理,再去看具體每個過濾器的源碼就很容易理解了,自己擴展一些過濾器也是可以的。

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