【Dubbo】--服務調用原理分析


dubbo的服務調用過程主要包括髮送請求,編解碼,服務降級,過濾器鏈處理,序列化,線程派發以及響應請求等步驟。本文主要分析請求的發送與接收,編解碼,線程派發以及響應的發送與接收。

1. 服務調用過程:

image.png
遠程調用請求的發送與接收的過程:

  1. 消費者通過代理對象Proxy發起遠程調用,
  2. 接着通過網絡客戶端Client將編碼後的請求發送給服務提供方的網絡層,也就是Server。
  3. Server收到請求後對數據包進行解碼,將解碼後的請求發送至分發器Dispatcher,
  4. 再由分發器將請求派發到制定的線程池上,
  5. 最後由線程池調用具體的服務。

2.源碼分析

2.1 服務調用方式

  • Dubbo支持同步和異步兩種調用方式,
  • 其中異步調用還可以細分爲“有返回值”和“無返回值”的異步調用。
  • 無返回值的異步調用,是指消費者不用關心調用結果,Dubbo會直接返回一個空的RpcResult。
  • 默認情況下Dubbo使用同步調用方式,使用異步調用需要消費方手動配置;

在服務引入的過程分析到客戶端已經生成了代理類,下面是一個代理類的示例:

public class proxy0 implements ClassGenerator.DC, EchoService, DemoService {
    // 方法數組
    public static Method[] methods;
    private InvocationHandler handler;

    public proxy0(InvocationHandler invocationHandler) {
        this.handler = invocationHandler;
    }

    public proxy0() {
    }

    public String sayHello(String string) {
        // 將參數存儲到 Object 數組中
        Object[] arrobject = new Object[]{string};
        // 調用 InvocationHandler 實現類的 invoke 方法得到調用結果
        Object object = this.handler.invoke(this, methods[0], arrobject);
        // 返回調用結果
        return (String)object;
    }

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

代理類的邏輯比較簡單,

  1. 將運行時的參數存儲到數組中,
  2. 然後調用InvocationHandler接口實現類的invoke方法,得到調用結果,最後將結果轉型並返回給調用方;

下面看一下invoke方法走的邏輯:

public class InvokerInvocationHandler implements InvocationHandler {

    private final Invoker<?> invoker;

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

    @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();
    }
}

InvokerInvocationHandler 中的 invoker 成員變量類型爲 MockClusterInvoker,MockClusterInvoker 內部封裝了服務降級邏輯。

public class MockClusterInvoker<T> implements Invoker<T> {
    
    private final Invoker<T> invoker;
    
    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")) {
            // 無 mock 邏輯,直接調用其他 Invoker 對象的 invoke 方法,
            // 比如 FailoverClusterInvoker
            result = this.invoker.invoke(invocation);
        } else if (value.startsWith("force")) {
            // force:xxx 直接執行 mock 邏輯,服務降級,不發起遠程調用
            result = doMockInvoke(invocation, null);
        } else {
            // fail:xxx 表示消費方對調用服務失敗後,再執行 mock 邏輯,不拋出異常
            try {
                // 調用其他 Invoker 對象的 invoke 方法
                result = this.invoker.invoke(invocation);
            } catch (RpcException e) {
                if (e.isBiz()) {
                    throw e;
                } else {
                    // 調用失敗,執行服務降級 mock 邏輯
                    result = doMockInvoke(invocation, e);
                }
            }
        }
        return result;
    }
    
    // 省略其他方法
}

這裏不分析服務降級部分的doMockInvoke ,所以mock部分的邏輯不在分析;
this.invoker.invoke(invocation);這裏調用Invoker對象的invoke方法,這塊的Invoker我們直接分析默認的DubboInvoker內的Invoke實現

public abstract class AbstractInvoker<T> implements Invoker<T> {
    
    public Result invoke(Invocation inv) throws RpcException {
        if (destroyed.get()) {
            throw new RpcException("Rpc invoker for service ...");
        }
        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) {
            // 添加 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) {
            // ...
        } catch (RpcException e) {
            // ...
        } catch (Throwable e) {
            return new RpcResult(e);
        }
    }

    protected abstract Result doInvoke(Invocation invocation) throws Throwable;
    
    // 省略其他方法
}

上面是AbstractInvoker抽象類,doInvoke是一個抽象方法,需要由其子類實現,這裏看一下DubboInvoker的實現;

public class DubboInvoker<T> extends AbstractInvoker<T> {
    
    private final ExchangeClient[] clients;
    
    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(..., "Invoke remote method timeout....");
        } catch (RemotingException e) {
            throw new RpcException(..., "Failed to invoke remote method: ...");
        }
    }
    
    // 省略其他方法
}

以上時Dubbo對同步和異步調用的處理邏輯,關於在於誰調用ResponseFuture的get方法;
默認同步模式下,由框架自身調用ResponseFuture的get方法,
異步調用模式下,由用戶調用該方法。
ResponseFuture是一個接口,默認實現類是DefaultFuture

public class DefaultFuture implements ResponseFuture {
    
    private static final Map<Long, Channel> CHANNELS = 
        new ConcurrentHashMap<Long, Channel>();

    private static final Map<Long, DefaultFuture> FUTURES = 
        new ConcurrentHashMap<Long, DefaultFuture>();
    
    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 volatile Response response;
    
    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);
        // 存儲 <requestId, DefaultFuture> 映射關係到 FUTURES 中
        FUTURES.put(id, this);
        CHANNELS.put(id, channel);
    }
    
    @Override
    public Object get() throws RemotingException {
        return get(timeout);
    }

    @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());
    }
    
    // 省略其他方法
}

當服務消費者還會受到調用結果事後,用戶線程調用get方法會被阻塞住。
同步調用模式下,框架獲得返回的DefaultFuture對象後,會立即調用get方法進行等待。
異步模式下,將對象封裝到FutureAdapter實例中,並將FutureAdapter實例設置到RpcContext中,供用戶使用。

FutureAdapter是一個適配器,用於將Dubbo中的ResponseFuture與JDK中的Future進行適配,當用戶線程調用Future的get方法時,經過FutureAdapter適配,最終會調用ResponseFuture實現類對象的get方法,也就是DefaultFuture的get方法;

2.2 服務消費方發送請求

2.2.1 消費方請求調用路徑示例:

proxy0#sayHello(String)
// 確定調用方式
—> InvokerInvocationHandler#invoke(Object, Method, Object[])
—> MockClusterInvoker#invoke(Invocation)
—> AbstractClusterInvoker#invoke(Invocation)
—> FailoverClusterInvoker#doInvoke(Invocation, List<Invoker>, LoadBalance)
—> Filter#invoke(Invoker, Invocation) // 包含多個 Filter 調用
—> ListenerInvokerWrapper#invoke(Invocation)
—> AbstractInvoker#invoke(Invocation)
—> DubboInvoker#doInvoke(Invocation)
// ReferenceCountExchangeClient維護一個引用計數變量,根據對象被引用和close進行增減
// 其他方法均是調用被裝飾對象的相關方法
—> ReferenceCountExchangeClient#request(Object, int)
// HeaderExchangeClient的很多方法都是調用HeaderExchangeChannel對象的同簽名方法,
// HeaderExchangeClient主要用於封裝一些關於心跳檢測的邏輯
—> HeaderExchangeClient#request(Object, int)
—> HeaderExchangeChannel#request(Object, int)
// 定義了一個 Request 對象,然後再將該對象傳給 NettyClient 的 send 方法
// NettyClient 的 send 方法繼承自父類AbstractPeer
—> AbstractPeer#send(Object)
—> AbstractClient#send(Object, boolean)
—> NettyChannel#send(Object, boolean)
—> NioClientSocketChannel#write(Object)

> 

<a name="SlO2i"></a>
### 2.2.2 請求編碼
![image.png](https://cdn.nlark.com/yuque/0/2019/png/198536/1564897427419-03e22233-58fd-4c82-ae4a-72b282c76058.png#align=left&display=inline&height=361&name=image.png&originHeight=496&originWidth=1451&size=287609&status=done&width=1055.2727272727273)<br />數據包分爲消息頭和消息體,

- 消息頭(header):用於存儲一些元信息
  - 魔數(Magic)
  - 數據包類型(Request/Response)
  - 消息體長度(Data Length)
- 消息體(body):用於存儲具體的調用信息
  - 方法名
  - 參數列表

請求對象的編碼過程主要包含以下邏輯:

1. 首先會通過位運算符將**消息頭寫入到header數組、**
1. 然後對Request對象的data字段執行**序列化操作**,序列化後的數據最終會存儲到ChannelBuffer中。
1. 序列化操作執行完後,可得到數據序列化後的長度len,將len寫入到header指定位置處。
1. 將消息頭字節數組header寫入到ChannelBuffer中,整個編碼過程結束。

<a name="LC11V"></a>
## 2.3 服務提供方接收請求
默認情況下Dubbo使用Netty作爲底層的通信框架,Netty檢測到有數據入站後,首先會通過解碼器對數據進行解碼,然後將解碼後的數據傳遞給下一個入棧處理器的制定方法,下面來分析數據解碼的過程
<a name="LQO3l"></a>
### 2.3.1 請求解碼
ExchangeCodec類decode方法解碼<br />
```java
public class ExchangeCodec extends TelnetCodec {
    
    @Override
    public Object decode(Channel channel, ChannelBuffer buffer) throws IOException {
        int readable = buffer.readableBytes();
        // 創建消息頭字節數組
        byte[] header = new byte[Math.min(readable, HEADER_LENGTH)];
        // 讀取消息頭數據
        buffer.readBytes(header);
        // 調用重載方法進行後續解碼工作
        return decode(channel, buffer, readable, header);
    }

    @Override
    protected Object decode(Channel channel, ChannelBuffer buffer, int readable, byte[] header) throws IOException {
        // 檢查魔數是否相等
        if (readable > 0 && header[0] != MAGIC_HIGH
                || readable > 1 && header[1] != MAGIC_LOW) {
            int length = header.length;
            if (header.length < readable) {
                header = Bytes.copyOf(header, readable);
                buffer.readBytes(header, length, readable - length);
            }
            for (int i = 1; i < header.length - 1; i++) {
                if (header[i] == MAGIC_HIGH && header[i + 1] == MAGIC_LOW) {
                    buffer.readerIndex(buffer.readerIndex() - header.length + i);
                    header = Bytes.copyOf(header, i);
                    break;
                }
            }
            // 通過 telnet 命令行發送的數據包不包含消息頭,所以這裏
            // 調用 TelnetCodec 的 decode 方法對數據包進行解碼
            return super.decode(channel, buffer, readable, header);
        }
        
        // 檢測可讀數據量是否少於消息頭長度,若小於則立即返回 DecodeResult.NEED_MORE_INPUT
        if (readable < HEADER_LENGTH) {
            return DecodeResult.NEED_MORE_INPUT;
        }

        // 從消息頭中獲取消息體長度
        int len = Bytes.bytes2int(header, 12);
        // 檢測消息體長度是否超出限制,超出則拋出異常
        checkPayload(channel, len);

        int tt = len + HEADER_LENGTH;
        // 檢測可讀的字節數是否小於實際的字節數
        if (readable < tt) {
            return DecodeResult.NEED_MORE_INPUT;
        }
        
        ChannelBufferInputStream is = new ChannelBufferInputStream(buffer, len);

        try {
            // 繼續進行解碼工作-- 子類DubboCodec 重寫了該方法
            // 在運行時子類DubboCodec的decodeBody方法會被調用
            return decodeBody(channel, is, header);
        } finally {
            if (is.available() > 0) {
                try {
                    StreamUtils.skipUnusedStream(is);
                } catch (IOException e) {
                    logger.warn(e.getMessage(), e);
                }
            }
        }
    }
}

解碼部分的主要邏輯:

  1. 檢測消息頭的魔數與規定的是否相等,提前攔截掉非常規數據包
  2. 對消息體長度以及刻度字節數進行檢測。
  3. 調用子類 DubboCodec 的decodeBody方法進行後續解碼工作
  4. DubboCodec.decodeBody只對部分字段解碼,將解碼得到的字段封裝到Request中。
  5. DecodeableRpcInvocation.decode 後續解碼工作,通過反序列化將調用方法名,path,version,調用方法名,參數列表等依次解析出來,並設置到相應的字段彙總,最後得到一個完整的ecodeableRpcInvocation 對象。
  6. 最後得到一個完整的Request對象,該對象將被傳送到下一個入站處理器中。

### 2.3.2 調用服務 NettyHandler的messageReceived方法會接收上面解碼後的Request對象,並依次往下傳遞對象,這期間會經歷NettyServer,MultiMessageHandler,HeartbeatHandler,以及AllChannelHandler。最後由AllChannelHandler將該對象封裝到Runnable實現類對象中,並將Runnable放入線程池中進行後續調用邏輯。調用棧如下:
NettyHandler#messageReceived(ChannelHandlerContext, MessageEvent)> AbstractPeer#received(Channel, Object)> MultiMessageHandler#received(Channel, Object)> HeartbeatHandler#received(Channel, Object)> AllChannelHandler#received(Channel, Object)> ExecutorService#execute(Runnable)    // 由線程池執行後續的調用邏輯

2.3.2.1 線程派發模型

線程派發器所處位置:
image.png
線程派發的背景:
Dubbo將底層通信框架中接受請求的線程成爲IO線程,如果一些事件處理邏輯可以很快執行完,比如只在內存打個標記,此時直接在IO線程上執行。但如果事件的處理比較耗時(需要發起數據庫查詢或者http請求等),我們就不應該讓事件處理邏輯在IO線程上執行,而是派發到線程池中執行。
爲什麼要派發到線程池呢?
IO線程主要用於接收請求,如果IO線程被佔滿,將導致它不能接收新的請求。
線程派發器Dispatcher的職責:
Dispatcher本身不具備線程派發能力,他真正的職責是創建具有線程派發能力的ChannelHandler,比如AllChannelHandler,MessageOnlyChannelHandler等,這些不同的XXHandler分別對應着不同的派發策略。

策略 用途
all(默認策略) 所有消息都派發到線程池,包括請求,響應,連接事件,斷開事件等
direct 所有消息都不派發到線程池,全部在 IO 線程上直接執行
message 只有請求響應消息派發到線程池,其它消息均在 IO 線程上執行
execution 只有請求消息派發到線程池,不含響應。其它消息均在 IO 線程上執行
connection 在 IO 線程上,將連接斷開事件放入隊列,有序逐個執行,其它消息派發到線程池

Dubbo默認的線程派發策略是all,也就是默認創建AllChannelHandler將所有的消息都派發到線程池去執行。下面看一下AllChannelHandler的實現細節。

public class AllChannelHandler extends WrappedChannelHandler {

    public AllChannelHandler(ChannelHandler handler, URL url) {
        super(handler, url);
    }

    /** 處理連接事件 */
    @Override
    public void connected(Channel channel) throws RemotingException {
        // 獲取線程池
        ExecutorService cexecutor = getExecutorService();
        try {
            // 將連接事件派發到線程池中處理
            cexecutor.execute(new ChannelEventRunnable(channel, handler, ChannelState.CONNECTED));
        } catch (Throwable t) {
            throw new ExecutionException(..., " error when process connected event .", t);
        }
    }

    /** 處理斷開事件 */
    @Override
    public void disconnected(Channel channel) throws RemotingException {
        ExecutorService cexecutor = getExecutorService();
        try {
            cexecutor.execute(new ChannelEventRunnable(channel, handler, ChannelState.DISCONNECTED));
        } catch (Throwable t) {
            throw new ExecutionException(..., "error when process disconnected event .", t);
        }
    }

    /** 處理請求和響應消息,這裏的 message 變量類型可能是 Request,也可能是 Response */
    @Override
    public void received(Channel channel, Object message) throws RemotingException {
        ExecutorService cexecutor = getExecutorService();
        try {
            // 將請求和響應消息派發到線程池中處理
            cexecutor.execute(new ChannelEventRunnable(channel, handler, ChannelState.RECEIVED, message));
        } catch (Throwable t) {
            if(message instanceof Request && t instanceof RejectedExecutionException){
                Request request = (Request)message;
                // 如果通信方式爲雙向通信,此時將 Server side ... threadpool is exhausted 
                // 錯誤信息封裝到 Response 中,並返回給服務消費方。
                if(request.isTwoWay()){
                    String msg = "Server side(" + url.getIp() + "," + url.getPort() 
                        + ") threadpool is exhausted ,detail msg:" + t.getMessage();
                    Response response = new Response(request.getId(), request.getVersion());
                    response.setStatus(Response.SERVER_THREADPOOL_EXHAUSTED_ERROR);
                    response.setErrorMessage(msg);
                    // 返回包含錯誤信息的 Response 對象
                    channel.send(response);
                    return;
                }
            }
            throw new ExecutionException(..., " error when process received event .", t);
        }
    }

    /** 處理異常信息 */
    @Override
    public void caught(Channel channel, Throwable exception) throws RemotingException {
        ExecutorService cexecutor = getExecutorService();
        try {
            // 請求對象被封裝在ChannelEventRunnable中
            cexecutor.execute(new ChannelEventRunnable(channel, handler, ChannelState.CAUGHT, exception));
        } catch (Throwable t) {
            throw new ExecutionException(..., "error when process caught event ...");
        }
    }
}

請求對象被封裝在ChannelEventRunnable中,ChannelEventRunnable也是服務調用過程的下一個起點。

2.3.2.2 調用服務

從起點ChannelEventRunnable 開始分析:

public class ChannelEventRunnable implements Runnable {
    
    private final ChannelHandler handler;
    private final Channel channel;
    private final ChannelState state;
    private final Throwable exception;
    private final Object message;
    
    @Override
    public void run() {
        // 檢測通道狀態,對於請求或響應消息,此時 state = RECEIVED
        if (state == ChannelState.RECEIVED) {
            try {
                // 將 channel 和 message 傳給 ChannelHandler 對象,進行後續的調用
                handler.received(channel, message);
            } catch (Exception e) {
                logger.warn("... operation error, channel is ... message is ...");
            }
        } 
        
        // 其他消息類型通過 switch 進行處理
        else {
            switch (state) {
            case CONNECTED:
                try {
                    handler.connected(channel);
                } catch (Exception e) {
                    logger.warn("... operation error, channel is ...");
                }
                break;
            case DISCONNECTED:
                // ...
            case SENT:
                // ...
            case CAUGHT:
                // ...
            default:
                logger.warn("unknown state: " + state + ", message is " + message);
            }
        }

    }
}

ChannelEventRunnable僅僅是一箇中轉站,他的run方法並不包含具體的調用邏輯,只是根據消息的類型將消息傳遞給其他ChannelHandler對象進行處理。
DecodeHandler的處理如下:

public class DecodeHandler extends AbstractChannelHandlerDelegate {

    public DecodeHandler(ChannelHandler handler) {
        super(handler);
    }

    @Override
    public void received(Channel channel, Object message) throws RemotingException {
        if (message instanceof Decodeable) {
            // 對 Decodeable 接口實現類對象進行解碼
            decode(message);
        }

        if (message instanceof Request) {
            // 對 Request 的 data 字段進行解碼
            decode(((Request) message).getData());
        }

        if (message instanceof Response) {
            // 對 Request 的 result 字段進行解碼
            decode(((Response) message).getResult());
        }

        // 執行後續邏輯
        handler.received(channel, message);
    }

    private void decode(Object message) {
        // Decodeable 接口目前有兩個實現類,
        // 分別爲 DecodeableRpcInvocation 和 DecodeableRpcResult
        if (message != null && message instanceof Decodeable) {
            try {
                // 執行解碼邏輯
                ((Decodeable) message).decode();
            } catch (Throwable e) {
                if (log.isWarnEnabled()) {
                    log.warn("Call Decodeable.decode failed: " + e.getMessage(), e);
                }
            }
        }
    }
}

DecodeHandler 主要是包含了一些解碼邏輯,請求解碼可在IO線程上執行,也可在線程池中執行,取決運行時配置的線程派發策略,DecodeHandler就是用來保證請求或響應對象刻在線程池中被解碼,解碼完畢後的Request對象會被傳遞給下一站HeaderExchangeHandler.

public class HeaderExchangeHandler implements ChannelHandlerDelegate {

    private final ExchangeHandler handler;

    public HeaderExchangeHandler(ExchangeHandler handler) {
        if (handler == null) {
            throw new IllegalArgumentException("handler == null");
        }
        this.handler = handler;
    }

    @Override
    public void received(Channel channel, Object message) throws RemotingException {
        channel.setAttribute(KEY_READ_TIMESTAMP, System.currentTimeMillis());
        ExchangeChannel exchangeChannel = HeaderExchangeChannel.getOrAddChannel(channel);
        try {
            // 處理請求對象
            if (message instanceof Request) {
                Request request = (Request) message;
                if (request.isEvent()) {
                    // 處理事件
                    handlerEvent(channel, request);
                } 
                // 處理普通的請求
                else {
                    // 雙向通信
                    if (request.isTwoWay()) {
                        // 向後調用服務,並得到調用結果
                        Response response = handleRequest(exchangeChannel, request);
                        // 將調用結果返回給服務消費端
                        channel.send(response);
                    } 
                    // 如果是單向通信,僅向後調用指定服務即可,無需返回調用結果
                    else {
                        handler.received(exchangeChannel, request.getData());
                    }
                }
            }      
            // 處理響應對象,服務消費方會執行此處邏輯,後面分析
            else if (message instanceof Response) {
                handleResponse(channel, (Response) message);
            } else if (message instanceof String) {
                // telnet 相關,忽略
            } else {
                handler.received(exchangeChannel, message);
            }
        } finally {
            HeaderExchangeChannel.removeChannelIfDisconnected(channel);
        }
    }

    Response handleRequest(ExchangeChannel channel, Request req) throws RemotingException {
        Response res = new Response(req.getId(), req.getVersion());
        // 檢測請求是否合法,不合法則返回狀態碼爲 BAD_REQUEST 的響應
        if (req.isBroken()) {
            Object data = req.getData();

            String msg;
            if (data == null)
                msg = null;
            else if
                (data instanceof Throwable) msg = StringUtils.toString((Throwable) data);
            else
                msg = data.toString();
            res.setErrorMessage("Fail to decode request due to: " + msg);
            // 設置 BAD_REQUEST 狀態
            res.setStatus(Response.BAD_REQUEST);

            return res;
        }
        
        // 獲取 data 字段值,也就是 RpcInvocation 對象
        Object msg = req.getData();
        try {
            // 繼續向下調用
            Object result = handler.reply(channel, msg);
            // 設置 OK 狀態碼
            res.setStatus(Response.OK);
            // 設置調用結果
            res.setResult(result);
        } catch (Throwable e) {
            // 若調用過程出現異常,則設置 SERVICE_ERROR,表示服務端異常
            res.setStatus(Response.SERVICE_ERROR);
            res.setErrorMessage(StringUtils.toString(e));
        }
        return res;
    }
}

HeaderExchangeHandler 裏面包含了清晰的請求和響應邏輯,主要邏輯如下:

  1. 首先向後進行調用,得到調用結果。
  2. 然後將調用結果封裝到Response對象中,最後返回給服務消費方。
  3. 如果請求不合法或調用失敗,將錯誤信息封裝在Response對象中,並返回給消費方。

調用要走的邏輯還是根據協議轉發到DubboProtocol中,主要是獲取到Invoker對象調用Invoke方法執行業務邏輯。

public class DubboProtocol extends AbstractProtocol {

    public static final String NAME = "dubbo";
    
    private ExchangeHandler requestHandler = new ExchangeHandlerAdapter() {

        @Override
        public Object reply(ExchangeChannel channel, Object message) throws RemotingException {
            if (message instanceof Invocation) {
                Invocation inv = (Invocation) message;
                // 獲取 Invoker 實例
                Invoker<?> invoker = getInvoker(channel, inv);
                if (Boolean.TRUE.toString().equals(inv.getAttachments().get(IS_CALLBACK_SERVICE_INVOKE))) {
                    // 回調相關,忽略
                }
                RpcContext.getContext().setRemoteAddress(channel.getRemoteAddress());
                // 通過 Invoker 調用具體的服務
                return invoker.invoke(inv);
            }
            throw new RemotingException(channel, "Unsupported request: ...");
        }
        
        // 忽略其他方法
    }
    
    Invoker<?> getInvoker(Channel channel, Invocation inv) throws RemotingException {
        // 忽略回調和本地存根相關邏輯
        // ...
        
        int port = channel.getLocalAddress().getPort();
        
        // 計算 service key,格式爲 groupName/serviceName:serviceVersion:port。比如:
        //   dubbo/com.alibaba.dubbo.demo.DemoService:1.0.0:20880
        String serviceKey = serviceKey(port, path, inv.getAttachments().get(Constants.VERSION_KEY), inv.getAttachments().get(Constants.GROUP_KEY));

        // 從 exporterMap 查找與 serviceKey 相對應的 DubboExporter 對象,
        // 服務導出過程中會將 <serviceKey, DubboExporter> 映射關係存儲到 exporterMap 集合中
        DubboExporter<?> exporter = (DubboExporter<?>) exporterMap.get(serviceKey);

        if (exporter == null)
            throw new RemotingException(channel, "Not found exported service ...");

        // 獲取 Invoker 對象,並返回
        return exporter.getInvoker();
    }
    
    // 忽略其他方法
}

根據指定的服務獲取到指定的Invoker實例之後,需要調用對應的invoke方法,invoke方法被定義在抽象類AbstractProxyInvoker中:

public abstract class AbstractProxyInvoker<T> implements Invoker<T> {

    @Override
    public Result invoke(Invocation invocation) throws RpcException {
        try {
            // 調用 doInvoke 執行後續的調用,並將調用結果封裝到 RpcResult 中,並
            return new RpcResult(doInvoke(proxy, invocation.getMethodName(), invocation.getParameterTypes(), invocation.getArguments()));
        } catch (InvocationTargetException e) {
            return new RpcResult(e.getTargetException());
        } catch (Throwable e) {
            throw new RpcException("Failed to invoke remote proxy method ...");
        }
    }
    
    protected abstract Object doInvoke(T proxy, String methodName, Class<?>[] parameterTypes, Object[] arguments) throws Throwable;
}

doInvoke依舊是一個抽象方法,具體的實現需要對應的Invoker實例來實現,Invoker實例就是在服務引用過程中通過JavassitProxyFactory創建的,回顧一下,創建邏輯如下:

public class JavassistProxyFactory extends AbstractProxyFactory {
    
    // 省略其他方法

    @Override
    public <T> Invoker<T> getInvoker(T proxy, Class<T> type, URL url) {
        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 {
                // 調用 invokeMethod 方法進行後續的調用
                return wrapper.invokeMethod(proxy, methodName, parameterTypes, arguments);
            }
        };
    }
}

Wrapper是一個抽象類,調用的invokeMethod是一個抽象方法,dubbo會在運行時候利用Javassist爲wrapper生成一個實現類,並實現其invokeMethod方法,該方法會根據調用信息調用具體的服務。

整個調用過程的調用鏈如下:

ChannelEventRunnable#run()> DecodeHandler#received(Channel, Object)> HeaderExchangeHandler#received(Channel, Object)> HeaderExchangeHandler#handleRequest(ExchangeChannel, Request)> DubboProtocol.requestHandler#reply(ExchangeChannel, Object)> Filter#invoke(Invoker, Invocation)> AbstractProxyInvoker#invoke(Invocation)> Wrapper0#invokeMethod(Object, String, Class[], Object[])> DemoServiceImpl#sayHello(String)

2.4 服務端返回調用結果

服務端返回調用結果主要是調用Netty的send方法將調用結果response對象返回,但response對象在返回前也需要進行編碼,這塊的編碼和2.3.1中分析的請求編碼的邏輯是比較像的。結合着看比較容易理解。

public class ExchangeCodec extends TelnetCodec {
	public void encode(Channel channel, ChannelBuffer buffer, Object msg) throws IOException {
        if (msg instanceof Request) {
            encodeRequest(channel, buffer, (Request) msg);
        } else if (msg instanceof Response) {
            // 對響應對象進行編碼
            encodeResponse(channel, buffer, (Response) msg);
        } else {
            super.encode(channel, buffer, msg);
        }
    }
    
    protected void encodeResponse(Channel channel, ChannelBuffer buffer, Response res) throws IOException {
        int savedWriteIndex = buffer.writerIndex();
        try {
            Serialization serialization = getSerialization(channel);
            // 創建消息頭字節數組
            byte[] header = new byte[HEADER_LENGTH];
            // 設置魔數
            Bytes.short2bytes(MAGIC, header);
            // 設置序列化器編號
            header[2] = serialization.getContentTypeId();
            if (res.isHeartbeat()) header[2] |= FLAG_EVENT;
            // 獲取響應狀態
            byte status = res.getStatus();
            // 設置響應狀態
            header[3] = status;
            // 設置請求編號
            Bytes.long2bytes(res.getId(), header, 4);

            // 更新 writerIndex,爲消息頭預留 16 個字節的空間
            buffer.writerIndex(savedWriteIndex + HEADER_LENGTH);
            ChannelBufferOutputStream bos = new ChannelBufferOutputStream(buffer);
            ObjectOutput out = serialization.serialize(channel.getUrl(), bos);
           
            if (status == Response.OK) {
                if (res.isHeartbeat()) {
                    // 對心跳響應結果進行序列化,已廢棄
                    encodeHeartbeatData(channel, out, res.getResult());
                } else {
                    // 對調用結果進行序列化
                    encodeResponseData(channel, out, res.getResult(), res.getVersion());
                }
            } else { 
                // 對錯誤信息進行序列化
                out.writeUTF(res.getErrorMessage())
            };
            out.flushBuffer();
            if (out instanceof Cleanable) {
                ((Cleanable) out).cleanup();
            }
            bos.flush();
            bos.close();

            // 獲取寫入的字節數,也就是消息體長度
            int len = bos.writtenBytes();
            checkPayload(channel, len);
            
            // 將消息體長度寫入到消息頭中
            Bytes.int2bytes(len, header, 12);
            // 將 buffer 指針移動到 savedWriteIndex,爲寫消息頭做準備
            buffer.writerIndex(savedWriteIndex);
            // 從 savedWriteIndex 下標處寫入消息頭
            buffer.writeBytes(header); 
            // 設置新的 writerIndex,writerIndex = 原寫下標 + 消息頭長度 + 消息體長度
            buffer.writerIndex(savedWriteIndex + HEADER_LENGTH + len);
        } catch (Throwable t) {
            // 異常處理邏輯不是很難理解,但是代碼略多,這裏忽略了
        }
    }
}

public class DubboCodec extends ExchangeCodec implements Codec2 {
    
	protected void encodeResponseData(Channel channel, ObjectOutput out, Object data, String version) throws IOException {
        Result result = (Result) data;
        // 檢測當前協議版本是否支持帶有 attachment 集合的 Response 對象
        boolean attach = Version.isSupportResponseAttachment(version);
        Throwable th = result.getException();
        
        // 異常信息爲空
        if (th == null) {
            Object ret = result.getValue();
            // 調用結果爲空
            if (ret == null) {
                // 序列化響應類型
                out.writeByte(attach ? RESPONSE_NULL_VALUE_WITH_ATTACHMENTS : RESPONSE_NULL_VALUE);
            } 
            // 調用結果非空
            else {
                // 序列化響應類型
                out.writeByte(attach ? RESPONSE_VALUE_WITH_ATTACHMENTS : RESPONSE_VALUE);
                // 序列化調用結果
                out.writeObject(ret);
            }
        } 
        // 異常信息非空
        else {
            // 序列化響應類型
            out.writeByte(attach ? RESPONSE_WITH_EXCEPTION_WITH_ATTACHMENTS : RESPONSE_WITH_EXCEPTION);
            // 序列化異常對象
            out.writeObject(th);
        }

        if (attach) {
            // 記錄 Dubbo 協議版本
            result.getAttachments().put(Constants.DUBBO_VERSION_KEY, Version.getProtocolVersion());
            // 序列化 attachments 集合
            out.writeObject(result.getAttachments());
        }
    }
}

2.5 服務消費方接收調用結果

消費方收到服務方返回的數據包之後首先要進行解碼得到Response對象,然後再將該對象傳遞給NettyHandler處理器,NettyHandler處理器會依次往下傳遞,最後AllChannelHandler會接收到這個響應對象將其派發到線程池中,這個過程與服務端的過程是一致的,不再贅述。這塊重點分析一下如何對數據包解碼成Response對象,和如何將結果返回給用戶線程。

2.5.1 對響應數據解碼

這塊的邏輯主要封裝在DubboCodec中,和請求解碼部分的邏輯基本是一致的。

public class DubboCodec extends ExchangeCodec implements Codec2 {

    @Override
    protected Object decodeBody(Channel channel, InputStream is, byte[] header) throws IOException {
        byte flag = header[2], proto = (byte) (flag & SERIALIZATION_MASK);
        Serialization s = CodecSupport.getSerialization(channel.getUrl(), proto);
        // 獲取請求編號
        long id = Bytes.bytes2long(header, 4);
        // 檢測消息類型,若下面的條件成立,表明消息類型爲 Response
        if ((flag & FLAG_REQUEST) == 0) {
            // 創建 Response 對象
            Response res = new Response(id);
            // 檢測事件標誌位
            if ((flag & FLAG_EVENT) != 0) {
                // 設置心跳事件
                res.setEvent(Response.HEARTBEAT_EVENT);
            }
            // 獲取響應狀態
            byte status = header[3];
            // 設置響應狀態
            res.setStatus(status);
            
            // 如果響應狀態爲 OK,表明調用過程正常
            if (status == Response.OK) {
                try {
                    Object data;
                    if (res.isHeartbeat()) {
                        // 反序列化心跳數據,已廢棄
                        data = decodeHeartbeatData(channel, deserialize(s, channel.getUrl(), is));
                    } else if (res.isEvent()) {
                        // 反序列化事件數據
                        data = decodeEventData(channel, deserialize(s, channel.getUrl(), is));
                    } else {
                        DecodeableRpcResult result;
                        // 根據 url 參數決定是否在 IO 線程上執行解碼邏輯
                        if (channel.getUrl().getParameter(
                                Constants.DECODE_IN_IO_THREAD_KEY,
                                Constants.DEFAULT_DECODE_IN_IO_THREAD)) {
                            // 創建 DecodeableRpcResult 對象
                            result = new DecodeableRpcResult(channel, res, is,
                                    (Invocation) getRequestData(id), proto);
                            // 進行後續的解碼工作
                            result.decode();
                        } else {
                            // 創建 DecodeableRpcResult 對象
                            result = new DecodeableRpcResult(channel, res,
                                    new UnsafeByteArrayInputStream(readMessageData(is)),
                                    (Invocation) getRequestData(id), proto);
                        }
                        data = result;
                    }
                    
                    // 設置 DecodeableRpcResult 對象到 Response 對象中
                    res.setResult(data);
                } catch (Throwable t) {
                    // 解碼過程中出現了錯誤,此時設置 CLIENT_ERROR 狀態碼到 Response 對象中
                    res.setStatus(Response.CLIENT_ERROR);
                    res.setErrorMessage(StringUtils.toString(t));
                }
            } 
            // 響應狀態非 OK,表明調用過程出現了異常
            else {
                // 反序列化異常信息,並設置到 Response 對象中
                res.setErrorMessage(deserialize(s, channel.getUrl(), is).readUTF());
            }
            return res;
        } else {
            // 對請求數據進行解碼,前面已分析過,此處忽略
        }
    }
}

解碼完畢後需要將調用結果進行反序列化;

2.5.2 向用戶線程傳遞調用結果

Dubbo會將上一步解碼和反序列化完畢的調用結果派發到線程池上,但線程池的線程並不是用戶線程,用戶的線程此時是阻塞在DefaultFuture的get方法上,因爲用戶發送完請求後一直在等待get返回的結果Response,在Response到來後用戶線程將被喚醒,並通過調用編號獲取到自己的響應對象。

public class HeaderExchangeHandler implements ChannelHandlerDelegate {
    
    @Override
    public void received(Channel channel, Object message) throws RemotingException {
        channel.setAttribute(KEY_READ_TIMESTAMP, System.currentTimeMillis());
        ExchangeChannel exchangeChannel = HeaderExchangeChannel.getOrAddChannel(channel);
        try {
            if (message instanceof Request) {
                // 處理請求,前面已分析過,省略
            } else if (message instanceof Response) {
                // 處理響應
                handleResponse(channel, (Response) message);
            } else if (message instanceof String) {
                // telnet 相關,忽略
            } else {
                handler.received(exchangeChannel, message);
            }
        } finally {
            HeaderExchangeChannel.removeChannelIfDisconnected(channel);
        }
    }

    static void handleResponse(Channel channel, Response response) throws RemotingException {
        if (response != null && !response.isHeartbeat()) {
            // 繼續向下調用
            DefaultFuture.received(channel, response);
        }
    }
}

public class DefaultFuture implements ResponseFuture {  
    
    private final Lock lock = new ReentrantLock();
    private final Condition done = lock.newCondition();
    private volatile Response response;
    
	public static void received(Channel channel, Response response) {
        try {
            // 根據調用編號從 FUTURES 集合中查找指定的 DefaultFuture 對象
            DefaultFuture future = FUTURES.remove(response.getId());
            if (future != null) {
                // 繼續向下調用
                future.doReceived(response);
            } else {
                logger.warn("The timeout response finally returned at ...");
            }
        } finally {
            CHANNELS.remove(response.getId());
        }
    }

	private void doReceived(Response res) {
        lock.lock();
        try {
            // 保存響應對象
            response = res;
            if (done != null) {
                // 喚醒用戶線程
                done.signal();
            }
        } finally {
            lock.unlock();
        }
        if (callback != null) {
            invokeCallback(callback);
        }
    }
}

上述邏輯是將響應對象Response保存到了DefaultFuture實例中,然後再喚醒用戶線程,隨後用戶線程可從DefaultFuture 實例中獲取結果對象.下面分析一下具體的邏輯:

  1. 消費方併發調用多個服務,每個用戶線程發出請求後都生成一個DefaultFuture對象,
  2. 每個DefaultFuture創建時都會傳入Request對象,
  3. DefaultFuture獲取到Request對象的調用編號。
  4. 將<調用編號, DefaultFuture 對象> 存入到一個靜態map集和中,即FUTURES.
  5. 然後調用DefaultFuture的get方法進行等待服務端響應結果Response的到來。
  6. 線程池的線程在收到Respondse對象時會通過Response對象的調用編號,從FUTURES集和中get到對應key的DefaultFuture對象。
  7. 將Response對象設置到DefaultFuture對象中。
  8. 喚醒用戶線程,從DefaultFuture的get方法中取到DefaultFuture對象獲取調用結果。

** 整個流程圖如下:**

image.png

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章