前言
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
對象。在這裏,比較重要的是獲取客戶端實例。比如NettyClient
,Dubbo
要依靠它來進行網絡通信。
然後,還需要將多個服務提供者實例合併成一個,這是集羣容錯機制的實現。
最後,通過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;
}
}