1. 程式人生 > >dubbo原始碼分析23 -- provider 接收與傳送原理

dubbo原始碼分析23 -- provider 接收與傳送原理

在前面一篇部落格中分享了 dubbo 在網路通訊當中的 consumer 的傳送以及接收原理。通過叢集容錯最終選擇一個合適的 Invoke 通過 netty 直聯呼叫 provider 的服務。眾所周知, netty 是基於 Java Nio 的 Reactor 模型的非同步網路通訊框架,所以 dubbo 在 consumer 端把非同步變成了同步。

大概總結了 consumer 的傳送與接收原理,下面我們來討論一下 dubbo 網路通訊當中 provider 的接收與傳送原理。這樣就完成了 dubbo 架構圖裡面的 consumer 呼叫 provider 的過程.

dubbo-architecture.png

本次是分析 dubbo 的 provider 的接收與傳送原理,討論包括以下幾個點:

  • provider 接收 consumer 請求
  • provider 的擴充套件點呼叫
  • provider 響應 consumer 呼叫
  • dubbo 服務呼叫總結

1、provider 接收 consumer 請求

同 consumer 一樣 provider 預設也是通過 netty 進行網路通訊的。在之前的分析 dubbo 進行服務暴露(NettyServer#doOpen)的時候, 它是通過 Netty 進行服務暴露,添加了一個 dubbo 的自定義 netty 的 ChannelHandler 也就是 NettyServerHandler 來處理網路通訊事件。下面我們來看一下 provider 是如何接收 consumer 傳送過來請求的。

NettyServerHandler.jpg

以上就是 provider 接收 consumer 端的呼叫圖,可以發現其實是和 consumer 端接收 provider 端的類似都是通過自定義 netty 的 ChannelHandler 也就是 NettyServerHandler 來接收網路請求。並且同樣的通過 dubbo 自定義的 ChannelHandler 來處理請求。下面我們還是來分析一下這些 dubbo 自定義 ChannelHandler 的作用:

  • MultiMessageHandler:支援 MultiMessage 訊息處理,也就是多條訊息處理。
  • HeartbeatHandler:netty 心條檢測。如果心跳請求,傳送心跳然後直接 return,如果是心跳響應直接 return。
  • AllChannelHandler:使用執行緒池通過 ChannelEventRunnable 工作類來處理網路事件。
  • DecodeHandler:解碼 message,解析成 dubbo 中的 Request 物件
  • HeaderExchangeHandler:處理解析後的 consumer 端請求的 Request 資訊,把請求資訊傳遞到 DubboProtocol 並從 DubboExpoter 裡面找到相應具體的 Invoke 進行服務呼叫(後面具體分析)。

其實可以看到 consumer 與 provider 接收網路請求都是通過自定義 netty 的 ChannelHandler。然後通過呼叫自定義 ChannelHandler#channelRead (其實是 ChannelHandler 的子介面 ChannelInboundHandler#channelRead )來接收並處理網路請求。在之前服務暴露分析的時候我們講過AbstractProtocol#exporterMap 也就是 dubbo 在進行服務暴露的時候通過 AbstractProtocol#serviceKey 為 key 以 DubboExporter(Invoke 轉化成 Exporter) 為 value 的服務介面暴露資訊。然後把請求資訊交給 DubboProtocol 根據 consumer 裡面 Request 裡面的 Invocation 請求資訊獲取到 DubboExporter。最後通過DubboExporter#getInvoker 獲取暴露服務具體的服務實現,完成整個呼叫。

我們可以看到 consumer 與 provider 進行網路接收資訊是類似的,相同點都是通過自定義的 ChannelHandler 來處理網路請求資訊。通過 dubbo 這個自定義的 ChannelHandler 來適配不同的 Java Nio 框架,因為在 AbstractPeer 類中都持有 dubbo 自定義的這個 ChannelHandler 。 dubbo 預設使用的是 netty 作為 Nio 框架,通過配置 dubbo 還可以以 Mina 與 Grizzly 作為 Nio 框架。

>這個就用到了 dubbo 的核心 SPI 平等的對待第三方框架。

上面我們討論了相同點,下面我們來看一下 consumer 與 provider 接收網路請求的不同點:

  • consumer 接收的是 provider 端傳送過來的 Response(響應資訊),而 provider 是接收 consumer 端傳送過來的 Request(請求資訊)。
  • consumer 最後在 HeaderExchangeHandler 中呼叫 handleResponse 方法,而 provider 最在是在HeaderExchangeHandler 中呼叫 handleRequest 方法。
  • consumer 會預設會同步等待 provider 處理後的響應資訊(也可以非同步處理),而 provider 在處理完成之後就會同步的把響應請求傳送給 consumer.

2、provider 的擴充套件點呼叫

與 consumer 引用服務一樣, provider 在暴露服務的時候也會有擴充套件點。 就像 J2EE 呼叫 Servlet 的時候也可以通過 java.servlet.Filter 進行呼叫擴充套件,dubbo 在進行服務暴露方的時候也會有 dubbo 自己的 Filter 擴充套件。那麼我們就來看一下在進行 Invoke 呼叫的時候 dubbo 都有哪些擴充套件:

DubboProtocol.jpg

可以看到預設情況下,dubbo 在進行服務暴露的時候會加上框架自定義的 7 個 Filter 擴充套件。下面就來簡單描述一下這 7 個 Filter 的作用:

  • EchoFilter:回聲測試,用於檢測服務是否可用,回聲測試按照正常請求流程執行,能夠測試整個呼叫是否通暢,可用於監控。
  • ClassLoaderFilter:
  • GenericFilter:實現泛化呼叫,泛介面實現方式主要用於伺服器端沒有API介面及模型類元的情況,引數及返回值中的所有POJO均用Map表示,通常用於框架整合.比如:實現一個通用的遠端服務Mock框架,可通過實現GenericService介面處理所有服務請求。
  • TraceFilter:方法呼叫時間查探擴充套件器, 通過 TraceFilter#addTracer 新增需要查探類的方法與查探最大次數。當進行方法呼叫的時如果該方法的呼叫次數少於傳遞的最大次數就會把方法呼叫耗時傳送給遠端服務。
  • MonitorFilter:MonitorFilter 其實是在分析之前 dubbo monitor 的時候就進行了詳細的分析。它主要是通過<dubbo:monitor protocol="registry" />來啟用 provider 與 consumer 端的指標監控。
  • TimeoutFilter:如果呼叫時間超過設定的 timeout 就列印 Log,但是不要阻止伺服器的執行。
  • ExceptionFilter:非檢測的異常將會為 ERROR 級別記錄在 Provider 端。非檢測的異常是未在介面上宣告的未經檢查的異常.dubbo 會將在這在 API 包中未引入的異常包裝到RuntimeException中。

以上就是 dubbo 框架在 provider 端的預設 Filter 擴充套件,當然如果你有需求也可以自定義 Filter 擴充套件。具體可以參考 dubbo 官網的 呼叫攔截擴充套件

3、呼叫服務並響應 consumer

provider 端通過接收 consumer 的請求並且解碼,然後呼叫 provider 的一系列自定義擴充套件。下面就是呼叫服務端暴露服務的真正實現了。在進行服務暴露的時候最終會呼叫 SPI 介面 ProxyFactory (預設是 JavassistProxyFactory) 來獲取 Invoke。我們可以來看一下 dubbo 官網對於服務提供者暴露一個服務的詳細過程:

dubbo_rpc_export.jpg

下面我們來看一下 JavassistProxyFactory 的原始碼:

> JavassistProxyFactory .java

public class JavassistProxyFactory extends AbstractProxyFactory {

    @Override
    @SuppressWarnings("unchecked")
    public <T> T getProxy(Invoker<T> invoker, Class<?>[] interfaces) {
        return (T) Proxy.getProxy(interfaces).newInstance(new InvokerInvocationHandler(invoker));
    }

    @Override
    public <T> Invoker<T> getInvoker(T proxy, Class<T> type, URL url) {
        // TODO Wrapper cannot handle this scenario correctly: the classname contains '$'
        final Wrapper wrapper = Wrapper.getWrapper(proxy.getClass().getName().indexOf('$') < 0 ? proxy.getClass() : type);
        return new AbstractProxyInvoker<T>(proxy, type, url) {
            @Override
            protected Object doInvoke(T proxy, String methodName,
                                      Class<?>[] parameterTypes,
                                      Object[] arguments) throws Throwable {
                return wrapper.invokeMethod(proxy, methodName, parameterTypes, arguments);
            }
        };
    }

}

在這裡需要說明 ProxyFactory#getInvoker 這個方法的三個請求引數:

  • proxy : 暴露介面服務的具體實現類,比如 dubbo-demo-provider 中的 org.apache.dubbo.demo.provider.DemoServiceImpl 例項物件。
  • type : 暴露介面服務的 Class 物件,比如 dubbo-demo-api 中的 org.apache.dubbo.demo.DemoService 的 Class 例項物件。
  • url : 暴露介面服務的配置資訊。具體資訊如下:
registry://localhost:2181/org.apache.dubbo.registry.RegistryService?application=demo-provider&dubbo=2.0.2&export=dubbo%3A%2F%2F192.168.75.1%3A20880%2Forg.apache.dubbo.demo.DemoService%3Fanyhost%3Dtrue%26application%3Ddemo-provider%26bind.ip%3D192.168.75.1%26bind.port%3D20880%26dubbo%3D2.0.2%26generic%3Dfalse%26interface%3Dorg.apache.dubbo.demo.DemoService%26methods%3DsayHello%26pid%3D3900%26qos.port%3D22222%26side%3Dprovider%26timestamp%3D1530184958055&pid=3900&qos.port=22222&registry=zookeeper&timestamp=1530184958041

然後進行服務呼叫的時候最終就會呼叫到暴露介面服務的具體實現類,也就是 DemoServiceImpl。最終返回的結果如下:

Result.png

HeaderExchangeHandler 再通過 DubboInvoke 呼叫到了暴露介面服務的真正實現,並獲取到返回值時。它還需要通過 HeaderExchangeHandler 也就是它自身把響應傳送給 consumer。具體的呼叫時序圖如下:

Provider.jpg

可以看到 dubbo 在響應 consumer 時最終也是通過 netty 來進行網路通訊的。

4、服務呼叫總結

當服務越來越多,容量的評估,小服務資源的浪費等問題逐漸顯現,此時需增加一個排程中心基於訪問壓力實時管理叢集容量,提高叢集利用率。此時,用於提高機器利用率的資源排程和治理中心(SOA)是關鍵。

  • 服務容器負責啟動,載入,執行服務提供者。
  • 服務提供者在啟動時,向註冊中心註冊自己提供的服務。
  • 服務消費者在啟動時,向註冊中心訂閱自己所需的服務。
  • 註冊中心返回服務提供者地址列表給消費者,如果有變更,註冊中心將基於長連線推送變更資料給消費者。
  • 服務消費者,從提供者地址列表中,基於軟負載均衡演算法,選一臺提供者進行呼叫,如果呼叫失敗,再選另一臺呼叫。
  • 服務消費者和提供者,在記憶體中累計呼叫次數和呼叫時間,定時每分鐘傳送一次統計資料到監控中心。

當分析了整個 dubbo 從服務暴露到服務引用,然後再分析了 dubbo 的叢集呼叫 以及 consumer 與 provider 的呼叫細節之後。再來看 dubbo 的呼叫圖是不是另外有一番滋味。

參考資料: