原文地址:http://www.carlzone.cn/dubbo/12-dubbo-extenstion-filter-listener/
Dubbo 是阿里巴巴開源的一個高性能優秀的服務框架,使得應用可通過高性能的 RPC 實現服務的輸入與輸出功能。作爲一個優秀的框架,至少應該包含以下幾個特點:
- 完善的文檔
- 活躍的社區
- 良好的擴展性
今天主要討論的主題就是 dubbo 中良好的擴展性。 dubbo 的擴展點加載是從 JDK 標準的 SPI (Service Provider Interface) 擴展點發現加強而來。
- JDK 標準的 SPI 會一次性實例化擴展點所有實現,如果有擴展實現初始化很耗時,但如果沒用上也加載,會很浪費資源。
- 如果擴展點加載失敗,連擴展點的名稱都拿不到了。比如:JDK 標準的 ScriptEngine,通過 getName() 獲取腳本類型的名稱,但如果 RubyScriptEngine 因爲所依賴的 jruby.jar 不存在,導致 RubyScriptEngine 類加載失敗,這個失敗原因被喫掉了,和 ruby 對應不起來,當用戶執行 ruby 腳本時,會報不支持 ruby,而不是真正失敗的原因。
- 增加了對擴展點 IoC 和 AOP 的支持,一個擴展點可以直接 setter 注入其它擴展點。
我們知道 dubbo 最核心的三個概念分別是:Provider(服務提供者)、Consumer(服務消費者)與 Registry(註冊中心),通過註冊中心解耦了服務方與消費方的調用關係。在服務發佈與服務引用的時候 dubbo 分別提供了 ExporterListener 與 InvokerListener 對於服務進行不同的擴展。
dubbo 在進行服務調用的過程中最核心的概念就是 Invoke,就相當於 Spring 裏面的 bean 一樣。對於 Invoke 這個核心模型 dubbo 也有 Filter 對其進行擴展。在這裏大家有沒有聯想到 Servlet 裏面的 Listener 與 Filter,可以看到優秀的框架裏面有很多思想都是相通的。
1、dubbo 的領域模型
在 dubbo 中主要包含以下三個核心領域模型:
- Protocol 是服務域,它是 Invoker 暴露和引用的主功能入口,它負責 Invoker 的生命週期管理。
- Invoker 是實體域,它是 Dubbo 的核心模型,其它模型都向它靠擾,或轉換成它,它代表一個可執行體,可向它發起 invoke 調用,它有可能是一個本地的實現,也可能是一個遠程的實現,也可能一個集羣實現。
- Invocation 是會話域,它持有調用過程中的變量,比如方法名,參數等。
dubbo 通過 Protocol 服務暴露 Invoke 這個服務調用的執行體,然後通過 Protocol 引用 Invoke 調用 Invocation 提供調用過程中的變量就可以完成一次服務的發佈與調用過程。
2、dubbo 擴展之 Listener
dubbo 的服務發佈與引用都提供了Listener 分別是 ExporterListener(服務暴露監聽) 與 InvokerListener(服務調用監聽)。
2.1 ExporterListener
ExporterListener 是 dubbo 通過 Protocol 進行服務暴露的時候調用的擴展點,也就是 Protocol 在進行 export() 方法的時候對服務暴露的一個擴展點。最終是在 ProtocolListenerWrapper#export 方法中通過 dubbo 的 SPI 機制加載進去,然後包裝成一個 ListenerExporterWrapper 對象。
public <T> Exporter<T> export(Invoker<T> invoker) throws RpcException {
if (Constants.REGISTRY_PROTOCOL.equals(invoker.getUrl().getProtocol())) {
return protocol.export(invoker);
}
return new ListenerExporterWrapper<T>(protocol.export(invoker),
Collections.unmodifiableList(ExtensionLoader.getExtensionLoader(ExporterListener.class)
.getActivateExtension(invoker.getUrl(), Constants.EXPORTER_LISTENER_KEY)));
}
ExporterListener 接口的定義如下:
public interface ExporterListener {
void exported(Exporter<?> exporter) throws RpcException;
void unexported(Exporter<?> exporter);
}
- exported() :在 ListenerExporterWrapper 對象進行初始化的時候就會進行調用
- unexported() :Exporter#unexport 的時候就會進行調用
2.1 InvokerListener
InvokerListener是 dubbo 通過 Protocol 進行服務引用的時候調用的擴展點,也就是 Protocol 在進行 refer() 方法的時候對服務暴露的一個擴展點。最終是在 ProtocolListenerWrapper#export 方法中通過 dubbo 的 SPI 機制加載進去,然後包裝成一個 ListenerInvokerWrapper 對象。
public <T> Invoker<T> refer(Class<T> type, URL url) throws RpcException {
if (Constants.REGISTRY_PROTOCOL.equals(url.getProtocol())) {
return protocol.refer(type, url);
}
return new ListenerInvokerWrapper<T>(protocol.refer(type, url),
Collections.unmodifiableList(
ExtensionLoader.getExtensionLoader(InvokerListener.class)
.getActivateExtension(url, Constants.INVOKER_LISTENER_KEY)));
}
InvokerListener 接口的定義如下:
public interface InvokerListener {
void referred(Invoker<?> invoker) throws RpcException;
void destroyed(Invoker<?> invoker);
}
- referred() :在 ListenerInvokerWrapper 對象進行初始化的時候就會進行調用
- destroyed() :Invoker#destroyed 的時候就會進行調用
3、dubbo 擴展之 Filter
Filter 是 dubbo 對於服務調用 (Invoker) 的進行攔截。dubbo 中 Invoke 是服務暴露方與服務引用方共用的一個核心概念,所以對於 Filter 對於這兩都都會進行攔截。分別通過 ProtocolFilterWrapper#export 與 ProtocolFilterWrapper#refer 構建 Filter 對服務暴露方與服務引用方的 Invoke 進行攔截。
1) 服務暴露方
public <T> Exporter<T> export(Invoker<T> invoker) throws RpcException {
if (Constants.REGISTRY_PROTOCOL.equals(invoker.getUrl().getProtocol())) {
return protocol.export(invoker);
}
return protocol.export(buildInvokerChain(invoker, Constants.SERVICE_FILTER_KEY, Constants.PROVIDER));
}
2) 服務引用方
public <T> Invoker<T> refer(Class<T> type, URL url) throws RpcException {
if (Constants.REGISTRY_PROTOCOL.equals(url.getProtocol())) {
return protocol.refer(type, url);
}
return buildInvokerChain(protocol.refer(type, url), Constants.REFERENCE_FILTER_KEY, Constants.CONSUMER);
}
服務暴露方與引用方它們都是構建成一個或者多個 Filter 攔截鏈。然後反向遍歷 Filter 對 Invoke 調用來進行增強。
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.size() > 0) {
for (int i = filters.size() - 1; i >= 0; i--) {
final Filter filter = filters.get(i);
final Invoker<T> next = last;
last = new Invoker<T>() {
public Class<T> getInterface() {
return invoker.getInterface();
}
public URL getUrl() {
return invoker.getUrl();
}
public boolean isAvailable() {
return invoker.isAvailable();
}
public Result invoke(Invocation invocation) throws RpcException {
return filter.invoke(next, invocation);
}
public void destroy() {
invoker.destroy();
}
@Override
public String toString() {
return invoker.toString();
}
};
}
}
return last;
}
4、擴展點自動激活
對於 Filter, InvokerListener, ExportListener 這些擴展點,可以用 @Activate 來自動激活。
@Activate // 無條件自動激活
public class XxxFilter implements Filter {
// ...
}
或者:
@Activate("xxx") // 當配置了xxx參數,並且參數爲有效值時激活,比如配了cache="lru",自動激活CacheFilter。
public class XxxFilter implements Filter {
// ...
}
或者:
@Activate(group = "provider", value = "xxx") // 只對提供方激活,group可選"provider"或"consumer"
public class XxxFilter implements Filter {
// ...
}
在項目中 ,放置擴展點配置文件 META-INF/dubbo/接口全限定名
,內容爲:配置名=擴展實現類全限定名
,多個實現類用換行符分隔。然後在項目啓動的時候 dubbo 就會掃描所有的 jar 包內的同名文件,然後進行合併。
5、擴展點應用
dubbo 裏面的 Listener 與 Filter 擴展點,應用最廣的還是 Filter。在 dubbo 的內部也大量的使用了 Filter 進行擴展。
同時我們可以使用使用自定義的 Filter 進行自定義的擴展,舉一個最簡單的例子就是日誌根據。基於 dubbo 進行服務間的調用,一般都會涉及到很多個系統之間的調用。這裏就可以使用 Filter 對於服務間的調用進行過濾。
> 1、日誌
public class TradeIdFilter implements Filter {
@Override
public Result invoke(Invoker<?> invoker, Invocation invocation) throws RpcException {
Result result = null;
String role = invoker.getUrl().getParameter(Constants.SIDE_KEY);
if (Constants.CONSUMER.equals(role)) {// consumer
String tradeId = UUID.randomUUID().toString();
RpcContext.getContext().setAttachment("tradeId", tradeId);
MDC.put("tradeId", tradeId);
} else if (Constants.PROVIDER.equals(role)) {// provider
String tradeId = RpcContext.getContext().getAttachment("tradeId");
MDC.put("tradeId", tradeId);
}
try {
result = invoker.invoke(invocation);
} catch (Exception e) {
throw new RpcException(e);
} finally {
MDC.clear();
}
return result;
}
}
> 2、黑白名單
public class AuthorityFilter implements Filter {
private static final Logger LOGGER = LoggerFactory.getLogger(AuthorityFilter.class);
private IpWhiteList ipWhiteList;
//dubbo通過setter方式自動注入
public void setIpWhiteList(IpWhiteList ipWhiteList) {
this.ipWhiteList = ipWhiteList;
}
@Override
public Result invoke(Invoker<?> invoker, Invocation invocation) throws RpcException {
if (!ipWhiteList.isEnabled()) {
LOGGER.debug("白名單禁用");
return invoker.invoke(invocation);
}
String clientIp = RpcContext.getContext().getRemoteHost();
LOGGER.debug("訪問ip爲{}", clientIp);
List<String> allowedIps = ipWhiteList.getAllowedIps();
if (allowedIps.contains(clientIp)) {
return invoker.invoke(invocation);
} else {
return new RpcResult();
}
}
}