12、dubbo源碼分析 之 Listener & Filter

原文地址: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();  
        }  
    }  
} 
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章