Dubbo服務呼叫過程原始碼解析④

耶low發表於2020-12-27

Dubbo SPI原始碼解析①

Dubbo服務暴露原始碼解析②

Dubbo服務引用原始碼解析③

​ 經過前面三章的分析,瞭解了Dubbo的基礎:Dubbo SPI,瞭解了Provider的服務暴露和Consumer的服務引用。最後我們需要學習一下服務完整的呼叫過程。Dubbo服務呼叫過程雖然複雜,比如包含傳送請求、編解碼、服務降級、過濾器、序列化、執行緒派發以及響應請求等步驟。但是先理解其大概的邏輯過程,再重點看一下主要的幾個類,其實也非常好理解。

​ 分析之前放一張官網的呼叫過程圖:

​ 首先消費者通過代理物件發起請求,通過網路通訊客戶端將編碼後的請求傳送給Provider的Server。Server收到後進行解碼。解碼後的請求交給Dispatcher分發器,再由分發器分配到指定的執行緒池上,最後由執行緒池執行具體的服務。還有回發響應的過程這張圖並沒有體現出來。在正式開始分析之前,最好開啟自己的IDE,一起跟蹤原始碼,看得更清楚。

0.服務的呼叫

​ 由上面那個圖可以看到,呼叫源於代理物件Proxy。代理類是動態生成的,直接操作的位元組碼,我們需要把它反編譯一下,看一下它到底長什麼樣。Dubbo用的是Javassist,我們使用也是阿里開源的診斷工具Arthas反編譯看一下。首先去它的官網下載軟體包:https://arthas.aliyun.com/doc/download.html

​ 解壓後,進入到軟體根目錄,執行如下命令啟動:

java -jar arthas-boot.jar

​ 啟動後,終端上會顯示Java程式列表,比如這樣:(注意這時候需要你啟動消費者,保持執行)。

​ 接著輸入Consumer對應編號,比如4。Arthas就會關聯到這個程式。由於我這個Demo只有一個服務介面,所以生成的代理類也只有一個,我們直接根據字尾名搜尋一下:

sc *.proxy0

​ 記住這個路徑,最後用jad命令反編譯:

jad com.alibaba.dubbo.common.bytecode.proxy0

​ 編譯完成後,終端上就會顯示對應的代理類:

public class proxy0 implements ClassGenerator.DC,ServiceAPI,EchoService {
    //方法陣列
    public static Method[] methods;
    private InvocationHandler handler;

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

    public proxy0() {
    }

    public String sendMessage(String string) {
        //把引數存到Object陣列
        Object[] arrobject = new Object[]{string};
        //呼叫InvocationHandler的invoke方法
        Object object = this.handler.invoke(this, methods[0], arrobject);
        return (String)object;
    }
    //測試方法
    @Override
    public Object $echo(Object object) {
        Object[] arrobject = new Object[]{object};
        Object object2 = this.handler.invoke(this, methods[1], arrobject);
        return object2;
    }
}

​ 整個代理類比較簡單,主要就是呼叫了InvocationHandler的invoke方法。我麼找到它的實現類,在Dubbo中,它的實現類是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]);
        }
        return invoker.invoke(new RpcInvocation(method, args)).recreate();
    }

}

​ 通過除錯我們發現,這個invoker變數的型別是MockClusterInvoker,也就是最後會呼叫這個類的invoke方法。MockClusterInvoker#invoke會呼叫AbstractClusterInvoker#invoke方法,接著執行一些服務降級的邏輯。接下來又是一連串呼叫,我們直接看關鍵方法:DubboInvoker#doInvoke

protected Result doInvoke(final Invocation invocation) throws Throwable {
    //它會記錄呼叫方法、介面、引數等資訊
    RpcInvocation inv = (RpcInvocation) invocation;
    final String methodName = RpcUtils.getMethodName(invocation);
    //設定path和version到inv的attachments
    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);
        //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);
            //設定上下文futrue為null
            RpcContext.getContext().setFuture(null);
            //返回空的結果
            return new RpcResult();
        //非同步有返回值
        } else if (isAsync) {
            //傳送請求,並得到一個future
            ResponseFuture future = currentClient.request(inv, timeout);
            //把future設定到上下文中
            RpcContext.getContext().setFuture(new FutureAdapter<Object>(future));
            //暫時返回一個空的結果
            return new RpcResult();
        //同步呼叫
        } else {
            RpcContext.getContext().setFuture(null);
            //雖然也有future,但是這裡就呼叫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);
    }
}

​ 上面的方法,對Dubbo如果非同步、同步呼叫寫的非常清晰。關鍵的區別就在於由誰來呼叫這個get方法,非同步模式下又使用者呼叫。Dubbo中非同步的返回值型別是ResponseFuture,它預設的實現類是DefaultFuture,我們來看幾個關鍵方法:

//屬性略。。。
public DefaultFuture(Channel channel, Request request, int timeout) {
    this.channel = channel;
    this.request = request;
    //獲取請求id,非常重要,由於是非同步請求,響應資訊的匹配就是靠這個
    this.id = request.getId();
    this.timeout = timeout > 0 ? timeout : channel.getUrl().getPositiveParameter(Constants.TIMEOUT_KEY, Constants.DEFAULT_TIMEOUT);
    //儲存請求ID和future到Map中
    FUTURES.put(id, this);
    CHANNELS.put(id, channel);
}
public Object get() throws RemotingException {
    return get(timeout);
}
public Object get(int timeout) throws RemotingException {
    if (timeout <= 0) {
        timeout = Constants.DEFAULT_TIMEOUT;
    }
    //檢測Provider是否返回撥用結果
    if (!isDone()) {
        long start = System.currentTimeMillis();
        lock.lock();
        try {
            //迴圈檢測
            while (!isDone()) {
                //如果結果尚未返回就等一會在while,免得浪費資源
                done.await(timeout, TimeUnit.MILLISECONDS);
                //如果返回結果或者超時,就跳出while
                if (isDone() || System.currentTimeMillis() - start > timeout) {
                    break;
                }
            }
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        } finally {
            lock.unlock();
        }
        //如果跳出while還沒有結果,就丟擲異常
        if (!isDone()) {
            throw new TimeoutException(sent > 0, channel, getTimeoutMessage(false));
        }
    }
    //返回撥用結果
    return returnFromResponse();
}
public boolean isDone() {
    return response != null;
}
private Object returnFromResponse() throws RemotingException {
    Response res = response;
    if (res == null) {
        throw new IllegalStateException("response cannot be null");
    }
    //如果響應狀態為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方法如果沒有收到結果就會被阻塞。至此,代理類的請求如果一步步傳送出去的解析就結束了,接下來接著分析請求資料是如何傳送與接收的,以及響應資料的傳送與接收。

1.傳送請求

​ 接著上面的DubboInvoker,我們深入分析一下它是怎麼發出請求的,即currentClient.request。通過除錯我們找到它的實現類,是ReferenceCountExchangeClient:

final class ReferenceCountExchangeClient implements ExchangeClient {

    private final URL url;
    private final AtomicInteger refenceCount = new AtomicInteger(0);
    //其他屬性略。。。
    public ReferenceCountExchangeClient(ExchangeClient client, ConcurrentMap<String, LazyConnectExchangeClient> ghostClientMap) {
        this.client = client;
        //引用計數自增
        refenceCount.incrementAndGet();
        this.url = client.getUrl();
        //略。。。
    }
    public ResponseFuture request(Object request) throws RemotingException {
        //呼叫HeaderExchangeClient#request
        return client.request(request);
    }
    public ResponseFuture request(Object request, int timeout) throws RemotingException {
        //帶有超時的請求
        return client.request(request, timeout);
    }
    public void close(int timeout) {
        //引用計數自減
        if (refenceCount.decrementAndGet() <= 0) {
            if (timeout == 0) {
                client.close();
            } else {
                client.close(timeout);
            }
            client = replaceWithLazyClient();
        }
    }
    public void incrementAndGetCount() {
        //引用計數自增,該方法由外部呼叫
        refenceCount.incrementAndGet();
    }
    //其他方法略。。。
}

​ refenceCount為內部定義的引用計數變數,每當該物件被引用一次refenceCount就會自增,每當被close一次就會自減。其他省略的方法都是些簡單的工具方法,我們接著分析HeaderExchangeClient,即request呼叫的同名方法所在類。

public class HeaderExchangeClient implements ExchangeClient {
    private static final Logger logger = LoggerFactory.getLogger(HeaderExchangeClient.class);
    private static final ScheduledThreadPoolExecutor scheduled = new ScheduledThreadPoolExecutor(2, new NamedThreadFactory("dubbo-remoting-client-heartbeat", true));
    private final Client client;
    private final ExchangeChannel channel;
    // heartbeat timer
    private ScheduledFuture<?> heartbeatTimer;
    private int heartbeat;
    // heartbeat timeout (ms), default value is 0 , won't execute a heartbeat.
    private int heartbeatTimeout;

    public HeaderExchangeClient(Client client, boolean needHeartbeat) {
        if (client == null) {
            throw new IllegalArgumentException("client == null");
        }
        this.client = client;
        //建立HeaderExchangeChannel物件
        this.channel = new HeaderExchangeChannel(client);
        //心跳檢測
        String dubbo = client.getUrl().getParameter(Constants.DUBBO_VERSION_KEY);
        this.heartbeat = client.getUrl().getParameter(Constants.HEARTBEAT_KEY, dubbo != null && dubbo.startsWith("1.0.") ? Constants.DEFAULT_HEARTBEAT : 0);
        this.heartbeatTimeout = client.getUrl().getParameter(Constants.HEARTBEAT_TIMEOUT_KEY, heartbeat * 3);
        if (heartbeatTimeout < heartbeat * 2) {
            throw new IllegalStateException("heartbeatTimeout < heartbeatInterval * 2");
        }
        if (needHeartbeat) {
            //開啟心跳檢測定時器
            startHeatbeatTimer();
        }
    }
    public ResponseFuture request(Object request) throws RemotingException {
        //呼叫HeaderExchangeChannel#request
        return channel.request(request);
    }
    public ResponseFuture request(Object request, int timeout) throws RemotingException {
        //帶超時的request
        return channel.request(request, timeout);
    }
    public void close() {
        doClose();
        channel.close();
    }
    public void close(int timeout) {
        // Mark the client into the closure process
        startClose();
        doClose();
        channel.close(timeout);
    }
    //開始心跳檢測計時器
    private void startHeatbeatTimer() {
        stopHeartbeatTimer();
        if (heartbeat > 0) {
            heartbeatTimer = scheduled.scheduleWithFixedDelay(
                    new HeartBeatTask(new HeartBeatTask.ChannelProvider() {
                        public Collection<Channel> getChannels() {
                            return Collections.<Channel>singletonList(HeaderExchangeClient.this);
                        }
                    }, heartbeat, heartbeatTimeout),
                    heartbeat, heartbeat, TimeUnit.MILLISECONDS);
        }
    }
    //關閉心跳檢測計時器
    private void stopHeartbeatTimer() {
        if (heartbeatTimer != null && !heartbeatTimer.isCancelled()) {
            try {
                heartbeatTimer.cancel(true);
                scheduled.purge();
            } catch (Throwable e) {
                if (logger.isWarnEnabled()) {
                    logger.warn(e.getMessage(), e);
                }
            }
        }
        heartbeatTimer = null;
    }
    //關閉心跳檢測計時器
    private void doClose() {
        stopHeartbeatTimer();
    }
    //其他方法略。。。
}

​ 上面省略的很多方法,都只是呼叫了HeaderExchangeChannel同名方法,作用也比較簡單,比如設定屬性,獲取地址,心跳檢測等等,這些不是關注的重點,我們看一下request相關的方法:

final class HeaderExchangeChannel implements ExchangeChannel {
    private final Channel channel;
    private volatile boolean closed = false;
    //其他屬性略。。。
    HeaderExchangeChannel(Channel channel) {
        if (channel == null) {
            throw new IllegalArgumentException("channel == null");
        }
        //這個channel指向Netty客戶端,建立Netty客戶端時會呼叫這個建構函式進行賦值
        this.channel = channel;
    }
    public ResponseFuture request(Object request) throws RemotingException {
        return request(request, channel.getUrl().getPositiveParameter(Constants.TIMEOUT_KEY, Constants.DEFAULT_TIMEOUT));
    }
    public ResponseFuture request(Object request, int timeout) throws RemotingException {
        if (closed) {
            throw new RemotingException(this.getLocalAddress(), null, "Failed to send request " + request + ", cause: The channel " + this + " is closed!");
        }
        // 建立request物件,包含著呼叫的方法名、引數型別、invoker等資訊,在之前我們都分析過了
        Request req = new Request();
        req.setVersion("2.0.0");
        //雙向通訊
        req.setTwoWay(true);
        //這個request型別為RpcInvocation
        req.setData(request);
        //建立futrue,即非同步請求的接收物件
        DefaultFuture future = new DefaultFuture(channel, req, timeout);
        try {
            //最終會呼叫Netty的send
            channel.send(req);
        } catch (RemotingException e) {
            future.cancel();
            throw e;
        }
        返回futrue
        return future;
    }
}

​ 上面的方法中,我們終於知道了request是在哪建立的了。這個Request的結構大家感興趣可以自己看一下,比較簡單,就是一些屬性加上一些工具方法而已。重點看一下最終的send方法在哪。通過除錯發現還需要通過幾次呼叫才能真正到達Netty,如圖:

​ NettyChannel前的兩個抽象類只是對通訊客戶端的一些抽象,因為Dubbo不止支援Netty一個通訊框架的,所以不可能直接由HeaderExchangeChannel跳到Netty。比如AbstractClient的實現類之一就是NettyClient,NettyClient才會緊接著呼叫NettyChannel。我們直接看NettyChannel#send方法:

public void send(Object message, boolean sent) throws RemotingException {
    super.send(message, sent);
    boolean success = true;
    int timeout = 0;
    try {
        //傳送訊息
        ChannelFuture future = channel.write(message);
        //sent源於<dubbo:method sent="true/false">
        //true代表等待訊息發出,訊息發出失敗丟擲異常
       //false代表不等待訊息發出,將訊息放入IO對了,立即返回
        if (sent) {
            timeout = getUrl().getPositiveParameter(Constants.TIMEOUT_KEY, Constants.DEFAULT_TIMEOUT);
            //等待訊息發出,如果超時success就設定為false
            success = future.await(timeout);
        }
        Throwable cause = future.getCause();
        if (cause != null) {
            throw cause;
        }
    } catch (Throwable e) {
        throw new RemotingException(this, "Failed to send message " + message + " to " + getRemoteAddress() + ", cause: " + e.getMessage(), e);
    }
    //success為false就丟擲異常
    if (!success) {
        throw new RemotingException(this, "Failed to send message " + message + " to " + getRemoteAddress()
                + "in timeout(" + timeout + "ms) limit");
    }
}

​ 到這裡,訊息終於真正的發出了。上面方法中的channel是真正的Netty的channel,而不是Dubbo封裝的。當然,在發出訊息前一步還有編碼,我們可以通過NettyServer的初始化來找到對應的編解碼器。我們來到NettyServer類中,熟悉Netty的朋友應該都熟悉,這個類就是Netty的啟動類,裡面會進行相關Pipeline的配置,我們可以看到:

pipeline.addLast("decoder", adapter.getDecoder());
pipeline.addLast("encoder", adapter.getEncoder());

​ 這個就是進行編解碼的處理方法,adapter物件的類就是進行編解碼的地方。

2.請求編碼

​ 上面我們一路分析到了發出訊息的原始碼,但是還有重要一步,就是編碼。我們也找到了編解碼對應的類,即NettyCodecAdapter。在分析之前我們有必要了解一下Dubbo的資料包結構。Dubbo資料包結構包含訊息頭和訊息體,訊息頭包含一些元資訊,比如魔數、資料包型別、訊息體長度等。訊息體包含具體的呼叫資訊,比如方法名、引數列表等。下面我放一張官網的訊息頭內容截圖:

​ 瞭解了Dubbo資料包結構,接著我們進入編解碼方法進行分析。首先進入到NettyCodecAdapter類。這裡就不貼它的原始碼了,可以發現它又引用了一個Codec2介面,呼叫了其encode和decode方法。我們知道雖然Dubbo預設選擇Netty當通訊工具,但是其不止支援一種通訊框架,所以針對每種框架都會有一個對應的編解碼介面卡。那麼實現了Codec2介面的實現類才是編解碼的主要邏輯。我們直接通過除錯找到了最終的邏輯所在類:ExchangeCodec。

public class ExchangeCodec extends TelnetCodec {
    // 訊息頭長度
    protected static final int HEADER_LENGTH = 16;
    // 魔數內容
    protected static final short MAGIC = (short) 0xdabb;
    protected static final byte MAGIC_HIGH = Bytes.short2bytes(MAGIC)[0];
    protected static final byte MAGIC_LOW = Bytes.short2bytes(MAGIC)[1];
    protected static final byte FLAG_REQUEST = (byte) 0x80;
    protected static final byte FLAG_TWOWAY = (byte) 0x40;
    protected static final byte FLAG_EVENT = (byte) 0x20;
    protected static final int SERIALIZATION_MASK = 0x1f;
    private static final Logger logger = LoggerFactory.getLogger(ExchangeCodec.class);

    public Short getMagicCode() {
        return MAGIC;
    }

    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 encodeRequest(Channel channel, ChannelBuffer buffer, Request req) throws IOException {
    Serialization serialization = getSerialization(channel);
    // 建立訊息頭位元組陣列,長度為16
    byte[] header = new byte[HEADER_LENGTH];
    // 設定魔數
    Bytes.short2bytes(MAGIC, header);
    // 設定資料包型別和序列化器
    header[2] = (byte) (FLAG_REQUEST | serialization.getContentTypeId());
    //設定通訊方式(單向/雙向)
    if (req.isTwoWay()) header[2] |= FLAG_TWOWAY;
    //設定事件標識
    if (req.isEvent()) header[2] |= FLAG_EVENT;
    // 設定請求id
    Bytes.long2bytes(req.getId(), header, 4);
    //獲取buffer當前寫的位置
    int savedWriteIndex = buffer.writerIndex();
    //更新witerIndex,為訊息頭保留16個位元組的空間
    buffer.writerIndex(savedWriteIndex + HEADER_LENGTH);
    ChannelBufferOutputStream bos = new ChannelBufferOutputStream(buffer);
    //建立序列化器
    ObjectOutput out = serialization.serialize(channel.getUrl(), bos);
    if (req.isEvent()) {
        //對事件資料進行序列化操作
        encodeEventData(channel, out, req.getData());
    } else {
        //對請求資料進行序列化操作
        encodeRequestData(channel, out, req.getData());
    }
    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);
    //寫入訊息頭
    buffer.writeBytes(header); 
    //將指標移到原寫下標+訊息頭長度+訊息體長度
    buffer.writerIndex(savedWriteIndex + HEADER_LENGTH + len);
}
    //其他方法略。。。。比如解碼,我們按照資料傳送的順序來分析,在這裡就不分析了
}

​ 上面就是請求物件的編碼過程,整體工作流程就是通過位運算將訊息頭寫入header。然後對請求物件的data進行序列化,序列化後的資料存到ChannelBuffer中。接著得到資料長度len,將len寫入訊息頭。最後再將訊息頭也寫入到ChannelBuffer中。

3.請求的解碼

​ 當資料編碼好,發出去之後。Netty服務端收到訊息,進行解碼。還是在ExchangeCodec中,我們分析一下解碼方法:

public class ExchangeCodec extends TelnetCodec {
    
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);
}
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;
            }
        }
        //如果不相等就呼叫TelnetCodec的decode進行解碼
        return super.decode(channel, buffer, readable, header);
    }
    //檢查可讀資料是否小於訊息頭長度
    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;
    }

    // limit input stream.
    ChannelBufferInputStream is = new ChannelBufferInputStream(buffer, len);
    try {
        //繼續進行編碼工作
        return decodeBody(channel, is, header);
    } finally {
        if (is.available() > 0) {
            try {
                if (logger.isWarnEnabled()) {
                    logger.warn("Skip input stream " + is.available());
                }
                StreamUtils.skipUnusedStream(is);
            } catch (IOException e) {
                logger.warn(e.getMessage(), e);
            }
        }
    }
}
}

​ 上面的解碼方法主要是對請求的資料進行一系列檢查。接著看一下decodeBody方法,雖然在這個類中也實現了這個方法,但是ExchangeCodec的子類DubboCodec覆蓋了這個方法,所以接著分析一下DubboCodec#decodeBody:

public class DubboCodec extends ExchangeCodec implements Codec2 {
  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);
    //通過邏輯與運算得到呼叫型別,0是響應,1是請求
    if ((flag & FLAG_REQUEST) == 0) {
      //略。。。
      //對響應結果進行解碼,得到Response物件。前面說過我們按照資料發出的順序類分析,故先不分析這部分程式碼
    } else {
        //建立request物件
        Request req = new Request(id);
        req.setVersion("2.0.0");
        //通過邏輯與計算出通訊方式
        req.setTwoWay((flag & FLAG_TWOWAY) != 0);
        //檢查是否位事件型別
        if ((flag & FLAG_EVENT) != 0) {
            //將心跳事件設定到request中
            req.setEvent(Request.HEARTBEAT_EVENT);
        }
        try {
            Object data;
            if (req.isHeartbeat()) {
                //對心跳包進行解碼
                data = decodeHeartbeatData(channel, deserialize(s, channel.getUrl(), is));
            } else if (req.isEvent()) {
                //對事件資料進行解碼
                data = decodeEventData(channel, deserialize(s, channel.getUrl(), is));
            } else {
                DecodeableRpcInvocation inv;
                //根據url引數判斷是否在當前執行緒上對訊息體進行解碼
                if (channel.getUrl().getParameter(
                        Constants.DECODE_IN_IO_THREAD_KEY,
                        Constants.DEFAULT_DECODE_IN_IO_THREAD)) {
                    inv = new DecodeableRpcInvocation(channel, req, is, proto);
                    inv.decode();
                } else {
                    //不在當前執行緒上解碼
                    inv = new DecodeableRpcInvocation(channel, req,
                            new UnsafeByteArrayInputStream(readMessageData(is)), proto);
                }
                data = inv;
            }
            //設定資料
            req.setData(data);
        } catch (Throwable t) {
            if (log.isWarnEnabled()) {
                log.warn("Decode request failed: " + t.getMessage(), t);
            }
            // bad request
            req.setBroken(true);
            req.setData(t);
        }
        return req;
    }
}
}

​ 以上方法只對部分欄位進行了解碼,並將解碼欄位封裝到Request物件中。隨後會呼叫DecodeableRpcInvocation的decode方法進行後續的解碼工作。此工作可以解碼出呼叫的方法名、attachment、引數。我們看一下這個方法:

public Object decode(Channel channel, InputStream input) throws IOException {
    //建立序列化器
    ObjectInput in = CodecSupport.getSerialization(channel.getUrl(), serializationType)
            .deserialize(channel.getUrl(), input);
    //通過序列化獲取dubbo version、path、version,並儲存到attachments中
    setAttachment(Constants.DUBBO_VERSION_KEY, in.readUTF());
    setAttachment(Constants.PATH_KEY, in.readUTF());
    setAttachment(Constants.VERSION_KEY, in.readUTF());
    //獲取方法名
    setMethodName(in.readUTF());
    try {
        Object[] args;
        Class<?>[] pts;
        //獲取引數型別
        String desc = in.readUTF();
        if (desc.length() == 0) {
            pts = DubboCodec.EMPTY_CLASS_ARRAY;
            args = DubboCodec.EMPTY_OBJECT_ARRAY;
        } else {
            //將desc解析為型別陣列
            pts = ReflectUtils.desc2classArray(desc);
            args = new Object[pts.length];
            for (int i = 0; i < args.length; i++) {
                try {
                    args[i] = in.readObject(pts[i]);
                } catch (Exception e) {
                    if (log.isWarnEnabled()) {
                        log.warn("Decode argument failed: " + e.getMessage(), e);
                    }
                }
            }
        }
        //設定引數型別陣列
        setParameterTypes(pts);
        //通過反序列化得到原attachment
        Map<String, String> map = (Map<String, String>) in.readObject(Map.class);
        if (map != null && map.size() > 0) {
            Map<String, String> attachment = getAttachments();
            if (attachment == null) {
                attachment = new HashMap<String, String>();
            }
            //將原attachment與現在的attachment融合
            attachment.putAll(map);
            setAttachments(attachment);
        }
        for (int i = 0; i < args.length; i++) {
            args[i] = decodeInvocationArgument(channel, this, pts, i, args[i]);
        }
        //設定引數列表
        setArguments(args);

    } catch (ClassNotFoundException e) {
        throw new IOException(StringUtils.toString("Read invocation data failed.", e));
    } finally {
        if (in instanceof Cleanable) {
            ((Cleanable) in).cleanup();
        }
    }
    return this;
}

​ 上面的方法通過反序列化,得到了方法名、引數列表等資訊。到這裡,請求資料的解碼過程就完成了,接下來就可以呼叫實際的服務了。

4.呼叫具體服務

​ 前面解碼了請求資料,並封裝到了Request物件中。我們回到NettyServer中,找到Pipeline新增的邏輯處理類,即NettyHandler。

​ 不瞭解Netty的話,可以把Pipeline看作一個邏輯處理鏈路,一個雙向鏈路,鏈路上不是每個處理類都必須執行,但是相對順序不能變。傳入的資料會根據Pipeline新增的邏輯處理類的順序進行相應的處理。比如圖中,nettyHandler的主要作用是收發訊息,收訊息前,必須經過解碼,發訊息後必須經過編碼。部落格寫到這裡內容已經非常多了,為了節約篇幅就不再展示比較簡單的原始碼了,大家可以自己點進去看一下NettyHandler的原始碼。解碼完後,會進入到NettyHandler#messageReceived。主要邏輯就是獲取NettyChannel例項,然後通過ChannelHandler#received繼續向下傳遞。

​ 我們現在回顧一下開頭貼出的Dubbo呼叫圖,Server收到請求並解碼完後,有一個執行緒派發器。一般情況下,很少會拿Netty接收請求的執行緒去執行實際的服務邏輯。而是通過執行緒派發器派發到執行緒池中執行。Dubbo支援5種不同型別的執行緒派發策略(IO執行緒就是通訊框架接收請求的執行緒):

​ Dubbo預設使用all派發策略,其實現類是AllChannelHandler,這個類實現了ChannelHandler。所以上面的NettyHandler#messageReceived中呼叫的ChannelHandler#received,會進入到這個實現類裡面,進行執行緒派發。

​ AllChannelHandler#received比較簡單,就不貼了。方法一開始就新建了一個執行緒池,意圖也很明顯。關鍵在於,它把請求物件封裝到了ChannelEventRunnable中:

​ ChannelEventRunnable類也比較簡單,僅是一箇中轉站的作用。主要是在run方法裡面,對不同的訊息型別,呼叫不同的處理方法。

​ 我們主要是分析received方法,像連線等方法我們就不跟進了。在ChannelEventRunnable#run方法裡經過中轉後,進入到了DecodeHandler類,看一下received方法:

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

​ 我們前面說過,解碼可以在IO執行緒,也可以線上程池裡執行。這裡就體現執行緒池解碼的邏輯。完成解碼後,後續邏輯在HeaderExchangeHandler:

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()) {
                    //呼叫handleRequest
                    Response response = handleRequest(exchangeChannel, request);
                    //將呼叫結果返回給消費者
                    channel.send(response);
                } else {
                    //如果是單向通訊,不需要返回結果
                    handler.received(exchangeChannel, request.getData());
                }
            }
        //處理響應物件,消費者會執行此邏輯,後面分析
        } else if (message instanceof Response) {
            handleResponse(channel, (Response) message);
        } //略。。。
    } finally {
        HeaderExchangeChannel.removeChannelIfDisconnected(channel);
    }
}
Response handleRequest(ExchangeChannel channel, Request req) throws RemotingException {
    Response res = new Response(req.getId(), req.getVersion());
    //檢查請求是否合法
    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欄位
    Object msg = req.getData();
    try {
        //呼叫後續邏輯
        Object result = handler.reply(channel, msg);
        res.setStatus(Response.OK);
        res.setResult(result);
    } catch (Throwable e) {
        res.setStatus(Response.SERVICE_ERROR);
        res.setErrorMessage(StringUtils.toString(e));
    }
    return res;
}

​ HeaderExchangeHandler#received方法邏輯比較清晰,如果是雙向通訊,就繼續後續的邏輯並返回結果。單向通訊不返回結果,僅向下接著執行。我們接著分析,進入到DubboProtocol#reply。就不貼程式碼了,主要邏輯就是獲取Invoker例項物件,通過invoker呼叫具體服務:

return invoker.invoke(inv);

​ 這個invoke方法的實現在AbstractProxyInvoker,中間會經過一堆過濾器,大家可以直接把斷點打在這個抽象類裡。而AbstractProxyInvoker#invoke主要就是呼叫了doInvoke方法,而這個方法是個抽象方法。它需要具體的Invoker例項實現。Invoker是通過JavassistProxyFactory建立的,第二章提到過:

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

​ Wrapper是一個抽象類,Dubbo會在執行時通過Javassist框架為其生成實現類,並實現invokeMethod方法。同樣的,我們利用Arthas反編譯一下。進入到Provider的程式,搜尋*.Wrapper0,再用jad反編譯:

public class Wrapper0 extends Wrapper implements ClassGenerator.DC {
    public static String[] pns;
    public static Map pts;
    public static String[] mns;
    public static String[] dmns;
    public static Class[] mts0;

    public Object invokeMethod(Object object, String string, Class[] arrclass, Object[] arrobject) throws InvocationTargetException {
        //這就是我們需要呼叫的服務介面
        ServiceAPI serviceAPI;
        try {
            //型別轉換
            serviceAPI = (ServiceAPI)object;
        }
        catch (Throwable throwable) {
            throw new IllegalArgumentException(throwable);
        }
        try {
            //sendMessage就是我們呼叫的方法名,根據方法名找到指定方法
            if ("sendMessage".equals(string) && arrclass.length == 1) {
                return serviceAPI.sendMessage((String)arrobject[0]);
            }
        }
        catch (Throwable throwable) {
            throw new InvocationTargetException(throwable);
        }
        throw new NoSuchMethodException(new StringBuffer().append("Not found method \"").append(string).append("\" in class com.yelow.springboot.dubbo.ServiceAPI.").toString());
    }
    //其他方法略。。。
}

​ 到這裡,終於看到了呼叫具體方法的程式碼。

5.返回撥用結果

​ 獲取到執行結果後,我們就需要返回了,詳細的呼叫鏈就不再重複了,大家可以自己debug一下。這裡只看一下Response的編碼。在請求編碼那一節中,我們分析了ExchangeCodec,其中,對響應物件進行編碼沒有分析,我們現在來看看:

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);

        buffer.writerIndex(savedWriteIndex + HEADER_LENGTH);
        ChannelBufferOutputStream bos = new ChannelBufferOutputStream(buffer);
        ObjectOutput out = serialization.serialize(channel.getUrl(), bos);
        // encode response data or error message.
        if (status == Response.OK) {
            if (res.isHeartbeat()) {
                //對心跳響應結果進行序列化
                encodeHeartbeatData(channel, out, res.getResult());
            } else {
                //對呼叫結果進行序列化
                encodeResponseData(channel, out, res.getResult());
            }
        } 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);
        // write
        buffer.writerIndex(savedWriteIndex);
        buffer.writeBytes(header); // write header.
        buffer.writerIndex(savedWriteIndex + HEADER_LENGTH + len);
    } catch (Throwable t) {
        //異常處理略。。。
    }
}

6.接收呼叫結果

​ 終於到了最後一步,前面經歷了發起服務呼叫-傳送請求-請求編碼-請求解碼-呼叫具體服務-返回請求結果(請求結果編碼)。

​ 接收呼叫結果後,同樣需要解碼。這一塊不想再重複了,具體程式碼在DubboCodec#decodeBody中,有了前面的經驗,大家可以自己debug看一下。

​ 響應資料解碼完成後,Dubbo會將響應物件派發到執行緒池上,執行緒池會把呼叫的結果傳遞到使用者執行緒。前面說到過,請求傳送後,會用DefaultFuture的get方法等待響應結果。當響應物件來了後,使用者執行緒會被喚醒,並通過請求編號獲取自己的響應結果。我們來分析下,首先解碼完成後,肯定是要在Netty的邏輯處理類裡面進行後續邏輯的呼叫,如handler.received。這個received就會進入到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>();
    
public static void received(Channel channel, Response response) {
    try {
        DefaultFuture future = FUTURES.remove(response.getId());
        if (future != null) {
            //繼續往下呼叫
            future.doReceived(response);
        } else {
            logger.warn("The timeout response finally returned at "
                    + (new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS").format(new Date()))
                    + ", response " + response
                    + (channel == null ? "" : ", channel: " + channel.getLocalAddress()
                    + " -> " + channel.getRemoteAddress()));
        }
    } 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);
    }
}
}

​ 以上邏輯就是把物件儲存到DefaultFuture中,然後喚醒使用者執行緒。隨後使用者執行緒呼叫get方法獲取結果。

完整的呼叫過程就分析到這裡了,更多用法和原始碼分析可以看官網文件:http://dubbo.apache.org/zh/docs/

相關文章