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"/>
規則
- 過濾器順序:
- 用戶自定義的過濾器順序默認會在框架內置過濾器之後,我們可以使用filter="xxx,default"這種配置方式讓自定義過濾器順序靠前。
- 我們在配置filter="xxx,yyy"的時候,寫在前面的xxx會比yyy的順序要靠前。
- 剔除過濾器:對於一些默認過濾器或一些自動激活的過濾器,有些方法不想使用這些過濾器,則可以使用"-“加過濾器名稱來過濾,如filter=”-ContextFilter"會讓ContextFilter不生效,如果不想使用所有默認啓動的過濾器,則可以配置filter="-default"來進行剔除。
- 過濾器的疊加:如果服務提供者消費者都配置了過濾器,則兩邊過濾器不會互相覆蓋,而是互相疊加,都會生效。如果需要覆蓋,則可以在消費方使用"-"的方式剔除對應的過濾器。
總體結構圖
作用列表
過濾器名稱 | 作用 | 使用方 |
---|---|---|
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總的來說分爲兩步:
- 獲取並遍歷所有過濾器。通過Dubbo SPIExtensionLoader#getActivateExtension方法獲取所有的過濾器並遍歷。
- 使用裝飾器模式,增強原有的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;
}