Dubbo服務消費者呼叫過程

搜雲庫技術團隊發表於2018-06-14

這裡寫圖片描述

上圖是服務消費的主過程:

首先通過ReferenceConfig類的private void init()方法會先檢查初始化所有的配置資訊後,呼叫private T createProxy(Map<String, String> map)建立代理,消費者最終得到的是服務的代理, 在createProxy接著呼叫Protocol 介面實現的<T> Invoker<T> refer(Class<T> type, URL url)方法生成Invoker例項(如上圖中的紅色部分),這是服務消費的關鍵。接下來把Invoker通過ProxyFactory代理工廠轉換為客戶端需要的介面(如:HelloWorld),建立服務代理並返回。

消費端的初始化過程

1、把服務引用的資訊封裝成URL並註冊到zk註冊中心;
2、監聽註冊中心的服務的上下線;
3、連線服務提供端,建立NettyClient物件;
4、將這些資訊包裝成DubboInvoker消費端的呼叫鏈,建立消費端Invoker例項的服務代理並返回;

消費端的服務引用過程

1、經過負載均衡策略,呼叫提供者;
2、選擇其中一個服務的URL與提供者netty建立連線,使用ProxyFactory 建立遠端通訊,或者本地通訊的,Invoker發到netty服務端;
3、伺服器端接收到該Invoker資訊後,找到對應的本地Invoker,處理Invocation請求;
4、獲取非同步,或同步處理結果;

  • 非同步 不需要返回值:直接呼叫ExchangeClient.send()方法;
  • 同步 需要返回值:使用ExchangeClient.request()方法,返回一個ResponseFuture,一直阻塞到服務端返回響應結果;

案例介紹

先看一個簡單的客戶端引用服務的例子,HelloServicedubbo配置如下:

<dubbo:application name="consumer-of-helloService" />
<dubbo:registry  protocol="zookeeper"  address="127.0.0.1:2181" />
<dubbo:protocol name="dubbo" port="20880" />
<dubbo:reference id="helloService" interface="com.demo.dubbo.service.HelloService" />
  • 使用Zookeeper作為註冊中心
  • 引用遠端的HelloService介面服務

根據之前的介紹,在Spring啟動的時候,根據<dubbo:reference>配置會建立一個ReferenceBean,該bean又實現了Spring的FactoryBean介面,所以我們如下方式使用時:

@Autowired
private HelloService helloService;

使用的不是ReferenceBean物件,而是ReferenceBean的getObject()方法返回的物件,該物件通過代理實現了HelloService介面,所以要看服務引用的整個過程就需要從ReferenceBean.getObject()方法開始入手。

服務引用過程

將ReferenceConfig.init()中的內容拆成具體的步驟,如下:

第一步:收集配置引數

methods=hello,
timestamp=1443695417847,
dubbo=2.5.3
application=consumer-of-helloService
side=consumer
pid=7748
interface=com.demo.dubbo.service.HelloService

第二步:從註冊中心獲取服務地址,返回Invoker物件

如果是單個註冊中心,程式碼如下:

Protocol refprotocol = ExtensionLoader.getExtensionLoader(Protocol.class).getAdaptiveExtension();

invoker = refprotocol.refer(interfaceClass, url);

上述url的內容如下:

registry://127.0.0.1:2181/com.alibaba.dubbo.registry.RegistryService?
application=consumer-of-helloService&
dubbo=2.5.6&
pid=8292&
registry=zookeeper&
timestamp=1443707173909&

refer=
    application=consumer-of-helloService&
    dubbo=2.5.6&
    interface=com.demo.dubbo.service.HelloService&
    methods=hello&
    pid=8292&
    side=consumer&
    timestamp=1443707173884&

第三步:使用ProxyFactory建立出Invoker的代理物件

ProxyFactory proxyFactory = ExtensionLoader.getExtensionLoader(ProxyFactory.class).getAdaptiveExtension();

proxyFactory.getProxy(invoker);

下面就詳細說明下上述提到的幾個概念:Protocol、Invoker、ProxyFactory

概念介紹

Invoker

Invoker是一個可執行物件,有三種型別的Invoker:

  1. 本地執行的Invoker(服務端使用)
  2. 遠端通訊執行的Invoker(客戶端使用)
  3. 多個型別2的Invoker聚合成的叢集版Invoker(客戶端使用)

Invoker的實現情況如下:
這裡寫圖片描述

先來看服務引用的第2個步驟,返回Invoker物件

對於客戶端來說,Invoker應該是2、3這兩種型別。先來說第2種型別,即遠端通訊的Invoker,看DubboInvoker的原始碼,呼叫過程AbstractInvoker.invoke()->doInvoke():

    protected Result doInvoke(final Invocation invocation) throws Throwable {
        RpcInvocation inv = (RpcInvocation) invocation;
        final String methodName = RpcUtils.getMethodName(invocation);
        inv.setAttachment(Constants.PATH_KEY, getUrl().getPath());
        inv.setAttachment(Constants.VERSION_KEY, version);

        ExchangeClient currentClient;
        if (clients.length == 1) {
            currentClient = clients[0];
        } else {
            currentClient = clients[index.getAndIncrement() % clients.length];
        }
        try {
            boolean isAsync = RpcUtils.isAsync(getUrl(), invocation);
            boolean isOneway = RpcUtils.isOneway(getUrl(), invocation);
            int timeout = getUrl().getMethodParameter(methodName, Constants.TIMEOUT_KEY,Constants.DEFAULT_TIMEOUT);
            if (isOneway) {
                boolean isSent = getUrl().getMethodParameter(methodName, Constants.SENT_KEY, false);
                currentClient.send(inv, isSent);
                RpcContext.getContext().setFuture(null);
                return new RpcResult();
            } else if (isAsync) {
                ResponseFuture future = currentClient.request(inv, timeout) ;
                RpcContext.getContext().setFuture(new FutureAdapter<Object>(future));
                return new RpcResult();
            } else {
                RpcContext.getContext().setFuture(null);
                return (Result) currentClient.request(inv, timeout).get();
            }
        } catch (TimeoutException e) {
            throw new RpcException(RpcException.TIMEOUT_EXCEPTION, "Invoke remote method timeout. method: " + invocation.getMethodName() + ", provider: " + getUrl() + ", cause: " + e.getMessage(), e);
        } catch (RemotingException e) {
            throw new RpcException(RpcException.NETWORK_EXCEPTION, "Failed to invoke remote method: " + invocation.getMethodName() + ", provider: " + getUrl() + ", cause: " + e.getMessage(), e);
        }
    }

大致內容就是:
將通過遠端通訊將Invocation資訊傳遞給伺服器端,伺服器端接收到該Invocation資訊後,找到對應的本地Invoker,然後通過反射執行相應的方法,將方法的返回值再通過遠端通訊將結果傳遞給客戶端。

這裡分3種情況:

  • 執行方法不需要返回值:直接呼叫ExchangeClient.send()方法。
  • 執行方法的結果需要非同步返回:使用ExchangeClient.request()方法返回一個ResponseFuture物件,通過RpcContext中的ThreadLocal使ResponseFuture和當前執行緒繫結,未等服務端響應結果就直接返回,然後服務端通過ProtocolFilterWrapper.buildInvokerChain()方法會呼叫Filter.invoke()方法,即FutureFilter.invoker()->asyncCallback(),會獲取RpcContextResponseFuture物件,非同步返回結果。
  • 執行方法的結果需要同步返回:使用ExchangeClient.request()方法,返回一個ResponseFuture,一直阻塞到服務端返回響應結果

Protocol

服務引用的第二步就是:

Protocol refprotocol = ExtensionLoader.getExtensionLoader(Protocol.class).getAdaptiveExtension();

invoker = refprotocol.refer(interfaceClass, url);

使用協議Protocol根據上述的url和服務介面來引用服務,建立出一個Invoker物件

預設實現的DubboProtocol也會經過ProtocolFilterWrapper、ProtocolListenerWrapper、RegistryProtocol的包裝

首先看下RegistryProtocol.refer()方法,它幹了哪些事呢?

  • 將客戶端的資訊註冊到註冊中心上
  • 建立一個RegistryDirectory,從註冊中心中訂閱自己引用的服務,將訂閱到的url在RegistryDirectory內部轉換成Invoker。
  • RegistryDirectory是Directory的實現,Directory代表多個Invoker,可以把它看成List型別的Invoker,但與List不同的是,它的值可能是動態變化的,比如註冊中心推送變更RegistryDirectory內部含有兩者重要屬性:
  • 註冊中心服務Registry
  • Protocol 它會利用註冊中心服務Registry來獲取最新的伺服器端註冊的url地址,然後再利用協議Protocol將這些url地址轉換成一個具有遠端通訊功能的Invoker物件,如DubboInvoker
  • 在Directory的基礎上使用Cluster將上述多個Invoker物件聚合成一個叢集版的Invoker物件

Directory和Cluster都是服務治理的重點,接下去會單獨拿一章出來講

ProxyFactory

服務引用的第三步就是:

proxyFactory.getProxy(invoker);

對於Server端,ProxyFactory主要負責將服務如HelloServiceImpl統一進行包裝成一個Invoker,這些Invoker通過反射來執行具體的HelloServiceImpl物件的方法

對於client端,則是將上述建立的叢集版Invoker(Cluster)建立出代理物件

程式碼如下:

public class JavassistProxyFactory extends AbstractProxyFactory {

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

可以看到是利用jdk自帶的Proxy來動態代理目標物件Invoker,所以我們呼叫建立出來的代理物件如HelloService的方法時,會執行InvokerInvocationHandler中的邏輯:

public class InvokerInvocationHandler implements InvocationHandler {

    private final Invoker<?> invoker;

    public InvokerInvocationHandler(Invoker<?> handler){
        this.invoker = handler;
    }

    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        String methodName = method.getName();
        Class<?>[] parameterTypes = method.getParameterTypes();
        if (method.getDeclaringClass() == Object.class) {
            return method.invoke(invoker, args);
        }
        if ("toString".equals(methodName) && parameterTypes.length == 0) {
            return invoker.toString();
        }
        if ("hashCode".equals(methodName) && parameterTypes.length == 0) {
            return invoker.hashCode();
        }
        if ("equals".equals(methodName) && parameterTypes.length == 1) {
            return invoker.equals(args[0]);
        }
        //AbstractClusterInvoker.invoke()
        return invoker.invoke(new RpcInvocation(method, args)).recreate();
    }

}

參考:http://dubbo.apache.org/books/dubbo-dev-book/implementation.html
參考:https://my.oschina.net/xiaominmin/blog/1599378

Contact

關注公眾號-搜雲庫

相關文章