文章目錄
對於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通過裝飾器模式實現過濾器鏈,原理重點是一下三個過程:
- 在加載Protocol的時候將Protocol包裝成ProtocolFilterWrapper,在這裏面加上組裝過濾器的邏輯。
- 當服務暴露調用
protocol.export()
或者服務引用調用protocol.refer()
時,執行組裝過濾器的邏輯。 - Filter通過反覆增強
Invoker.invoke()
,在其調用前後加入各個過濾器自己的邏輯,實現過濾器鏈的織入。
Dubbo內置的過濾器很多,有些默認啓用,有些需要我們配置了才能生效,知道了過濾器的配置方式和實現原理,再去看具體每個過濾器的源碼就很容易理解了,自己擴展一些過濾器也是可以的。