dubbo原始碼分析之服務呼叫方發起呼叫(入口InvokerInvocationHandler.invoke)

jy02268879發表於2020-11-05

一、簡介

dubbo的服務呼叫主要包括幾個大的步驟

1.傳送請求

2.編解碼

3.服務降級

4.過濾器鏈處理

5.序列化

6.執行緒派發以及響應請求

二、服務消費者發起呼叫

dubbo服務呼叫支援的方式

同步呼叫(預設)

非同步呼叫

1.有返回值

2.無返回值(不關心結果。直接返回一個空的 RpcResult)

觸發的呼叫鏈

 

能夠看到先是通過ReferenceAnnotationBeanPostProcessor$ReferenceBeanInvocationHandler.invoke-------->反射呼叫-------->DelegatingMethodAccessorImpl.invoke-------->NativeMethodAccessorImpl.invoke-------->代理類proxy0.sayHello--------> InvokerInvocationHandler.invoke 

代理類的程式碼 

Dubbo 預設使用 Javassist 框架為服務介面生成動態代理類,因此我們需要先將代理類進行反編譯才能看到原始碼 

反編譯後看一下代理類的程式碼

(反編譯的方式看這章:dubbo原始碼分析之服務呼叫方refer(服務引用、建立invoker、建立代理、檢視動態生成的.class檔案)


package com.alibaba.dubbo.common.bytecode;
 
import com.alibaba.dubbo.common.bytecode.ClassGenerator.DC;
import com.alibaba.dubbo.rpc.service.EchoService;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import org.apache.dubbo.samples.api.client.HelloService;
 
public class proxy0 implements DC, HelloService, EchoService {

    // 方法陣列
    public static Method[] methods;
    private InvocationHandler handler;
 
    public proxy0(InvocationHandler var1) {
        this.handler = var1;
    }
 
    public proxy0() {
    }
 
    public String sayHello(String var1) {

         // 將引數儲存到 Object 陣列中
        Object[] var2 = new Object[]{var1};

        // 呼叫 InvocationHandler 實現類的 invoke 方法得到呼叫結果
        Object var3 = this.handler.invoke(this, methods[0], var2);

        // 返回撥用結果
        return (String)var3;
    }
 

     /** 回聲測試方法 */
    public Object $echo(Object var1) {
        Object[] var2 = new Object[]{var1};
        Object var3 = this.handler.invoke(this, methods[1], var2);
        return (Object)var3;
    }

 主要做了幾件事:

1.將執行時引數儲存到陣列中

2.呼叫 InvocationHandler 介面實現類的 invoke 方法,得到呼叫結果

3.將結果轉型並返回給呼叫方。

跟進去看一下這個invoke方法

InvokerInvocationHandler.invoke 

@Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        String methodName = method.getName();
        Class<?>[] parameterTypes = method.getParameterTypes();

        // 攔截定義在 Object 類中的方法(未被子類重寫),比如 wait/notify
        if (method.getDeclaringClass() == Object.class) {
            return method.invoke(invoker, args);
        }

        // 如果 toString、hashCode 和 equals 等方法被子類重寫了,這裡也直接呼叫
        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]);
        }

        // 將 method 和 args 封裝到 RpcInvocation 中,並執行後續的呼叫
        return invoker.invoke(new RpcInvocation(method, args)).recreate();
    }

主要做了幾件事:

1.如果呼叫的方法是屬於Object的,比如wait/notify,那麼直接呼叫AnnotationHelloServiceConsumer類的該方法

2.如果呼叫的方法是toString  hashCode equals,那麼呼叫invoker對應的該方法

此處的invoker是

3.如果呼叫的是服務提供方的方法,則invoker.invoke進行RPC遠端呼叫

MockClusterInvoker.invoke

public Result invoke(Invocation invocation) throws RpcException {
        Result result = null;


         // 獲取 mock 配置值
        String value = directory.getUrl().getMethodParameter(invocation.getMethodName(), Constants.MOCK_KEY, Boolean.FALSE.toString()).trim();
        if (value.length() == 0 || value.equalsIgnoreCase("false")) {

            //debug的時候走的這裡
            // 無 mock 邏輯,直接呼叫其他 Invoker 物件的 invoke 方法,
            // 比如 FailoverClusterInvoker
            //no mock
            result = this.invoker.invoke(invocation);
        } else if (value.startsWith("force")) {
            if (logger.isWarnEnabled()) {
                logger.info("force-mock: " + invocation.getMethodName() + " force-mock enabled , url : " + directory.getUrl());
            }

            // force:xxx 直接執行 mock 邏輯,不發起遠端呼叫
            //force:direct mock
            result = doMockInvoke(invocation, null);
        } else {

             // fail:xxx 表示消費方對呼叫服務失敗後,再執行 mock 邏輯,不丟擲異常
            //fail-mock
            try {

                 // 呼叫其他 Invoker 物件的 invoke 方法
                result = this.invoker.invoke(invocation);
            } catch (RpcException e) {
                if (e.isBiz()) {
                    throw e;
                } else {
                    if (logger.isWarnEnabled()) {
                        logger.warn("fail-mock: " + invocation.getMethodName() + " fail-mock enabled , url : " + directory.getUrl(), e);
                    }

                     // 呼叫失敗,執行 mock 邏輯
                    result = doMockInvoke(invocation, e);
                }
            }
        }
        return result;
    }

 主要做了幾件事:

1.獲取 mock 配置值

2.如果無 mock 邏輯,直接呼叫其他 Invoker 物件的 invoke 方法,比如 FailoverClusterInvoker.invoke

3.如果mock配置是 force:xxx 直接呼叫doMockInvoke方法執行 mock 邏輯,不發起遠端呼叫

4. 如果Mock配置是 fail:xxx 表示消費方使用Invoker.invoke呼叫服務失敗後,再使用doMockInvoke執行 mock 邏輯,不丟擲異常

其中 doMockInvoke跟服務降級相關,後面再專門寫一篇說這個

AbstractClusterInvoker.invoke

    @Override
    public Result invoke(final Invocation invocation) throws RpcException {
        checkWhetherDestroyed();
        LoadBalance loadbalance = null;

        //將RpcInvocation與attachments繫結
        // binding attachments into invocation.
        Map<String, String> contextAttachments = RpcContext.getContext().getAttachments();
        if (contextAttachments != null && contextAttachments.size() != 0) {
            ((RpcInvocation) invocation).addAttachments(contextAttachments);
        }


       //呼叫Directory的list方法,得到符合路由條件的invoker
        List<Invoker<T>> invokers = list(invocation);

        //得到負載均衡器LoadBalance
        if (invokers != null && !invokers.isEmpty()) {
            loadbalance = ExtensionLoader.getExtensionLoader(LoadBalance.class).getExtension(invokers.get(0).getUrl()
                    .getMethodParameter(RpcUtils.getMethodName(invocation), Constants.LOADBALANCE_KEY, Constants.DEFAULT_LOADBALANCE));
        }
        RpcUtils.attachInvocationIdIfAsync(getUrl(), invocation);

        //執行呼叫
        return doInvoke(invocation, invokers, loadbalance);
    }

做了幾件事:

1.將RpcInvocation與attachments繫結

執行後

2.呼叫Directory的list方法,得到符合路由條件的invoker

3.得到負載均衡器LoadBalance

debug的時候得到的是

估計預設就是RandomLoadBalance

4.RpcUtils.attachInvocationIdIfAsync方法

執行後

看起來沒什麼區別

4.執行呼叫failoverClusterInvoker.doInvoke

 FailoverClusterInvoker.doInvoke

 這個類的這個方法上一章分析過了

這個方法裡面再往後面呼叫,的呼叫棧是:

FailoverClusterInvoker.doInvoke-------->InvokerWrapper.invoke-------->ListenerInvoker.invoke-------->ProtocolFilterWrapper.invoke-------->ConsumerContextFilter.invoke-------->ProtocolFilterWrapper.invoke-------->FutureFilter.invoke-------->ProtocolFilter.invoke-------->MonitorFilter.invoke-------->AbstractInvoker.invoke

那下面我們來看 

AbstractInvoker.invoke

    public Result invoke(Invocation inv) throws RpcException {
        // if invoker is destroyed due to address refresh from registry, let's allow the current invoke to proceed
        if (destroyed.get()) {
            logger.warn("Invoker for service " + this + " on consumer " + NetUtils.getLocalHost() + " is destroyed, "
                    + ", dubbo version is " + Version.getVersion() + ", this invoker should not be used any longer");
        }

        RpcInvocation invocation = (RpcInvocation) inv;

         // 設定 Invoker
        invocation.setInvoker(this);
        if (attachment != null && attachment.size() > 0) {

            // 設定 attachment
            invocation.addAttachmentsIfAbsent(attachment);
        }
        Map<String, String> contextAttachments = RpcContext.getContext().getAttachments();
        if (contextAttachments != null && contextAttachments.size() != 0) {
            /**
             * invocation.addAttachmentsIfAbsent(context){@link RpcInvocation#addAttachmentsIfAbsent(Map)}should not be used here,
             * because the {@link RpcContext#setAttachment(String, String)} is passed in the Filter when the call is triggered
             * by the built-in retry mechanism of the Dubbo. The attachment to update RpcContext will no longer work, which is
             * a mistake in most cases (for example, through Filter to RpcContext output traceId and spanId and other information).
             */
            // 新增 contextAttachments 到 RpcInvocation#attachment 變數中
            invocation.addAttachments(contextAttachments);
        }
        if (getUrl().getMethodParameter(invocation.getMethodName(), Constants.ASYNC_KEY, false)) {
             // 設定非同步資訊到 RpcInvocation#attachment 中
            invocation.setAttachment(Constants.ASYNC_KEY, Boolean.TRUE.toString());
        }
        RpcUtils.attachInvocationIdIfAsync(getUrl(), invocation);


        try {

            // 抽象方法,由子類實現
            return doInvoke(invocation);
        } catch (InvocationTargetException e) { // biz exception
            Throwable te = e.getTargetException();
            if (te == null) {
                return new RpcResult(e);
            } else {
                if (te instanceof RpcException) {
                    ((RpcException) te).setCode(RpcException.BIZ_EXCEPTION);
                }
                return new RpcResult(te);
            }
        } catch (RpcException e) {
            if (e.isBiz()) {
                return new RpcResult(e);
            } else {
                throw e;
            }
        } catch (Throwable e) {
            return new RpcResult(e);
        }
    }

做了幾件事: 

 1.新增資訊到 RpcInvocation#attachment 變數中

新增完後

2.呼叫 子類doInvoke 執行後續的呼叫。debugg的時候就是呼叫的 DubboInvoker 類的doInvoke 

DubboInvoker.doInvoke

該方法是 Dubbo 對同步和非同步呼叫的處理邏輯 

同步呼叫模式下,由框架自身呼叫 ResponseFuture 的 get 方法。

非同步呼叫模式下,則由使用者呼叫該方法。

protected Result doInvoke(final Invocation invocation) throws Throwable {
        RpcInvocation inv = (RpcInvocation) invocation;
        final String methodName = RpcUtils.getMethodName(invocation);

        // 設定 path 和 version 到 attachment 中
        inv.setAttachment(Constants.PATH_KEY, getUrl().getPath());
        inv.setAttachment(Constants.VERSION_KEY, version);

        ExchangeClient currentClient;
        if (clients.length == 1) {

            // 從 clients 陣列中獲取 ExchangeClient
            currentClient = clients[0];
        } else {
            currentClient = clients[index.getAndIncrement() % clients.length];
        }
        try {

            // 獲取非同步配置
            boolean isAsync = RpcUtils.isAsync(getUrl(), invocation);

            // isOneway 為 true,表示“單向”通訊
            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);

                // 設定上下文中的 future 欄位為 null
                RpcContext.getContext().setFuture(null);

                // 返回一個空的 RpcResult
                return new RpcResult();


             // 非同步有返回值
            } else if (isAsync) {

                // 傳送請求,並得到一個 ResponseFuture 例項
                ResponseFuture future = currentClient.request(inv, timeout);

                // 設定 future 到上下文中
                RpcContext.getContext().setFuture(new FutureAdapter<Object>(future));

                // 暫時返回一個空結果
                return new RpcResult();

             // 同步呼叫
            } else {
                RpcContext.getContext().setFuture(null);

                // 傳送請求,得到一個 ResponseFuture 例項,並呼叫該例項的 get 方法進行等待
                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);
        }
    }

主要做了幾件事:

1.設定attachment

2.得到Exchange(debug的時候這個Exchange是ReferenceCountExchangeClient,這個底層傳送的邏輯後面專門寫一篇來說),呼叫它的send或者request方法進行遠端呼叫

它是做底層操作的,發起遠端呼叫就是它負責

3.根據標識執行對應的呼叫方式

比如是,同步呼叫、非同步有返回值呼叫、非同步無返回值呼叫

預設是同步呼叫,debug的時候也是走的同步呼叫

 而debug的時候這個currentClient.request得到的值是DefaultFuture

DefaultFuture

public class DefaultFuture implements ResponseFuture {

    private static final Logger logger = LoggerFactory.getLogger(DefaultFuture.class);

    private static final Map<Long, Channel> CHANNELS = new ConcurrentHashMap<Long, Channel>();

    private static final Map<Long, DefaultFuture> FUTURES = new ConcurrentHashMap<Long, DefaultFuture>();

    static {
        Thread th = new Thread(new RemotingInvocationTimeoutScan(), "DubboResponseTimeoutScanTimer");
        th.setDaemon(true);
        th.start();
    }

    // invoke id.
    private final long id;
    private final Channel channel;
    private final Request request;
    private final int timeout;
    private final Lock lock = new ReentrantLock();
    private final Condition done = lock.newCondition();
    private final long start = System.currentTimeMillis();
    private volatile long sent;
    private volatile Response response;
    private volatile ResponseCallback callback;

    public DefaultFuture(Channel channel, Request request, int timeout) {
        this.channel = channel;
        this.request = request;

        // 獲取請求 id,這個 id 很重要,後面還會見到
        this.id = request.getId();
        this.timeout = timeout > 0 ? timeout : channel.getUrl().getPositiveParameter(Constants.TIMEOUT_KEY, Constants.DEFAULT_TIMEOUT);
        

        // put into waiting map.

        // 儲存 <requestId, DefaultFuture> 對映關係到 FUTURES 中
        FUTURES.put(id, this);
        CHANNELS.put(id, channel);
    }

    @Override
    public Object get(int timeout) throws RemotingException {
        if (timeout <= 0) {
            timeout = Constants.DEFAULT_TIMEOUT;
        }

        // 檢測服務提供方是否成功返回了呼叫結果
        if (!isDone()) {
            long start = System.currentTimeMillis();
            lock.lock();
            try {

                // 迴圈檢測服務提供方是否成功返回了呼叫結果
                while (!isDone()) {

                    // 如果呼叫結果尚未返回,這裡等待一段時間
                    done.await(timeout, TimeUnit.MILLISECONDS);

                    // 如果呼叫結果成功返回,或等待超時,此時跳出 while 迴圈,執行後續的邏輯
                    if (isDone() || System.currentTimeMillis() - start > timeout) {
                        break;
                    }
                }
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            } finally {
                lock.unlock();
            }

           // 如果呼叫結果仍未返回,則丟擲超時異常
            if (!isDone()) {
                throw new TimeoutException(sent > 0, channel, getTimeoutMessage(false));
            }
        }

        // 返回撥用結果
        return returnFromResponse();
    }


    @Override
    public boolean isDone() {

        // 通過檢測 response 欄位為空與否,判斷是否收到了呼叫結果
        return response != null;
    }


    private Object returnFromResponse() throws RemotingException {
        Response res = response;
        if (res == null) {
            throw new IllegalStateException("response cannot be null");
        }

        // 如果呼叫結果的狀態為 Response.OK,則表示呼叫過程正常,服務提供方成功返回了呼叫結果
        if (res.getStatus() == Response.OK) {
            return res.getResult();
        }

        // 丟擲異常
        if (res.getStatus() == Response.CLIENT_TIMEOUT || res.getStatus() == Response.SERVER_TIMEOUT) {
            throw new TimeoutException(res.getStatus() == Response.SERVER_TIMEOUT, channel, res.getErrorMessage());
        }
        throw new RemotingException(channel, res.getErrorMessage());
    }

}

主要做了1件事

當服務消費者還未接收到呼叫結果時,使用者執行緒呼叫 get 方法會被阻塞住。

同步呼叫模式下,框架獲得 DefaultFuture 物件後,會立即呼叫 get 方法進行等待。

而非同步模式下則是將該物件封裝到 FutureAdapter 例項中,並將 FutureAdapter 例項設定到 RpcContext 中,供使用者使用。

FutureAdapter 是一個介面卡,用於將 Dubbo 中的 ResponseFuture 與 JDK 中的 Future 進行適配。

這樣當使用者執行緒呼叫 Future 的 get 方法時,經過 FutureAdapter 適配,最終會呼叫 ResponseFuture 實現類物件的 get 方法,也就是 DefaultFuture 的 get 方法。

 

相關文章