Dubbo調用流程一覽

前言

Apache Dubbo作爲一款高性能的Java RPC框架,在國內服務化體系的演進過程中扮演了一個非常重要的角色,被大量公司廣泛使用。

臨近年關,或許有小夥伴有着尋找新機會的想法,那麼在面試過程中很可能會常常見到這樣一個問題:

你瞭解Dubbo嗎 ? 能不能講一講它的調用流程。

對於不瞭解的盆友而言,無疑會降低印象分;如果僅僅會使用,其實也不太夠,最起碼我們要了解它的基本原理。

本文試圖從Dubbo使用者的角度上,結合流程圖和關鍵代碼把相應知識點串聯起來,回答我們上面的問題。

一、服務提供者

從程序開發者的角度來看,我們要先有服務提供者。通常,我們在具體接口的實現上標註Dubbo的Service註解。

package com.viewscenes.producer.dubbo;
import org.apache.dubbo.config.annotation.Service;
import com.viewscenes.common.service.DubboUserService;
@Service
public class DubboUserServiceImpl implements DubboUserService {
}

這個實現類在Dubbo中對應的解析類爲ServiceBean,它負責將這個實現對外暴露成一個服務。過程如下:

結合上圖來看,我們可以說在提供者端,暴露一個服務的過程如下:

首先,ServiceConfig類引用對外提供服務的實現類ref (如DubboUserServiceImpl) , 然後通過ProxyFactoty接口的擴展實現類的getInvoker()方法使用ref生成一個AbstractProxyInvoker實例,到此就完成了具體服務到Invoker的轉化。

接下來,通過Dubbo協議的export()方法,將Invoker轉化爲Exporter。那麼在這裏,就會先啓動Netty Server的監聽,然後將服務註冊到服務註冊中心。

在這裏,我們必須要注意的是,作爲服務提供者端,已經通過Netty開啓了TCP端口的監聽。那麼,當消費者調用的時候,通過一系列Netty Handler處理器,就會調用到DubboProtocol > ExchangeHandler.reply()

在這個方法裏,就是一個反推的過程。通過要調用的服務接口名稱,找到Exporter ,然後再獲取到Invoker對象。

Invoker<?> getInvoker(Channel channel, Invocation inv) throws RemotingException {
    int port = channel.getLocalAddress().getPort();
    String path = inv.getAttachments().get(PATH_KEY);
    String serviceKey = serviceKey(port, path, inv.getAttachments().get(VERSION_KEY),  inv.getAttachments().get(GROUP_KEY));
    DubboExporter<?> exporter = (DubboExporter<?>) exporterMap.get(serviceKey);
    return exporter.getInvoker();
}

從上面的分析中我們已經知道,這裏的Invoker對象是根據服務實現類生成的一個AbstractProxyInvoker實例。它最終會調用到wrapper.invokeMethod()方法。這裏的wrapper類是通過Javassist生成的,在內存中的類,它的核心方法長這樣:

Dubbo會給每個服務提供者的實現生成一個Wrapper類。當接收到消費方的請求後,根據傳遞的方法名和參數,Wrapper類調用服務提供者的接口類實現即可。這樣做的目的主要是爲了減少反射的調用。

二、服務消費者

在服務消費者端,我們直接引用一個接口即可。

@Reference
DubboUserService userService;

或許你可能要問,爲啥只注入了這麼一個普通的接口,就可以調用到遠端的服務呢 ?

我們想想在 Mybatis中的Dao接口和XML文件裏的SQL是如何建立關係的? 這個問題中,它們是怎麼關聯起來的呢 ?

說穿了還是Spring的功勞,或者說是Spring FactoryBean的功勞。

Dubbo中,標註了@Reference的接口,都會被當成一個Factory Bean ,這個Bean一般都會返回一個代理對象,來屏蔽底層一些複雜的操作。比如Mybatis裏的mapper接口和xml文件關聯,Dubbo中的網絡通信等。

我們還是先來通過一張圖看看消費者端的具體過程:

結合上圖來看,我們總結下服務引用的過程:

Reference註解標註的Dubbo接口,會被註冊成FactoryBean,並最終返回一個代理對象。

在創建代理的過程中,會調用其他方法構建以及合併 Invoker 實例。

首先,調用DubboProtocol的refer方法,返回DubboInvoker對象。在這裏,比較重要的是獲取客戶端實例。比如NettyClientDubbo要依靠它來進行網絡通信。

然後,還需要將多個服務提供者實例合併成一個,這是集羣容錯機制的實現。

最後,通過JavassistProxyFactory創建代理並返回。在這裏,它的處理器是InvokerInvocationHandler ,這就意味着,當我們在消費者端調用一個Dubbo接口的時候,實際上會調用到InvokerInvocationHandler.invoke()方法,在這裏面Dubbo完成了譬如集羣容錯、負載均衡、調用遠程方法的一系列動作。

Dubbo消費者發送請求的時候,最終會調用到DubboInvoker中的方法。在這裏,會完成具體的請求邏輯,比如發送請求數據。

final class HeaderExchangeChannel implements ExchangeChannel {
    //創建請求消息對象
    Request req = new Request();
    req.setVersion(Version.getProtocolVersion());
    req.setTwoWay(true);
    req.setData(request);
    
    //創建Future,用於獲取返回結果
    DefaultFuture future = DefaultFuture.newFuture(this.channel, req, timeout, executor);
    try {
        //通過Netty客戶端發送數據
        this.channel.send(req);
        return future;
    } catch (RemotingException var7) {
        future.cancel();
        throw var7;
    }
}

三、請求-響應過程

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