上圖是服務消費的主過程:
首先通過ReferenceConfig
類的private void init()
方法會先檢查初始化所有的配置資訊後,呼叫private T createProxy(Map<String, String> map)
建立代理,消費者最終得到的是服務的代理, 在createProxy
接著呼叫Protocol
介面實現的<T> Invoker<T> refer(Class<T> type, URL url)
方法生成Invoker
例項(如上圖中的紅色部分),這是服務消費的關鍵。接下來把Invoker
通過ProxyFactory
代理工廠轉換為客戶端需要的介面(如:HelloWorld
),建立服務代理並返回。
消費端的初始化過程
1、把服務引用的資訊封裝成URL並註冊到zk註冊中心;
2、監聽註冊中心的服務的上下線;
3、連線服務提供端,建立NettyClient物件;
4、將這些資訊包裝成DubboInvoker消費端的呼叫鏈,建立消費端Invoker例項的服務代理並返回;
消費端的服務引用過程
1、經過負載均衡策略,呼叫提供者;
2、選擇其中一個服務的URL與提供者netty建立連線,使用ProxyFactory 建立遠端通訊,或者本地通訊的,Invoker發到netty服務端;
3、伺服器端接收到該Invoker資訊後,找到對應的本地Invoker,處理Invocation請求;
4、獲取非同步,或同步處理結果;
- 非同步 不需要返回值:直接呼叫ExchangeClient.send()方法;
- 同步 需要返回值:使用ExchangeClient.request()方法,返回一個ResponseFuture,一直阻塞到服務端返回響應結果;
案例介紹
先看一個簡單的客戶端引用服務的例子,HelloService
,dubbo
配置如下:
<dubbo:application name="consumer-of-helloService" />
<dubbo:registry protocol="zookeeper" address="127.0.0.1:2181" />
<dubbo:protocol name="dubbo" port="20880" />
<dubbo:reference id="helloService" interface="com.demo.dubbo.service.HelloService" />
- 使用
Zookeeper
作為註冊中心 - 引用遠端的
HelloService
介面服務
根據之前的介紹,在Spring啟動的時候,根據<dubbo:reference>
配置會建立一個ReferenceBean,該bean又實現了Spring的FactoryBean介面,所以我們如下方式使用時:
@Autowired
private HelloService helloService;
使用的不是ReferenceBean物件,而是ReferenceBean的getObject()方法返回的物件,該物件通過代理實現了HelloService介面,所以要看服務引用的整個過程就需要從ReferenceBean.getObject()方法開始入手。
服務引用過程
將ReferenceConfig.init()中的內容拆成具體的步驟,如下:
第一步:收集配置引數
methods=hello,
timestamp=1443695417847,
dubbo=2.5.3
application=consumer-of-helloService
side=consumer
pid=7748
interface=com.demo.dubbo.service.HelloService
第二步:從註冊中心獲取服務地址,返回Invoker物件
如果是單個註冊中心,程式碼如下:
Protocol refprotocol = ExtensionLoader.getExtensionLoader(Protocol.class).getAdaptiveExtension();
invoker = refprotocol.refer(interfaceClass, url);
上述url的內容如下:
registry://127.0.0.1:2181/com.alibaba.dubbo.registry.RegistryService?
application=consumer-of-helloService&
dubbo=2.5.6&
pid=8292&
registry=zookeeper&
timestamp=1443707173909&
refer=
application=consumer-of-helloService&
dubbo=2.5.6&
interface=com.demo.dubbo.service.HelloService&
methods=hello&
pid=8292&
side=consumer&
timestamp=1443707173884&
第三步:使用ProxyFactory建立出Invoker的代理物件
ProxyFactory proxyFactory = ExtensionLoader.getExtensionLoader(ProxyFactory.class).getAdaptiveExtension();
proxyFactory.getProxy(invoker);
下面就詳細說明下上述提到的幾個概念:Protocol、Invoker、ProxyFactory
概念介紹
Invoker
Invoker是一個可執行物件,有三種型別的Invoker:
- 本地執行的Invoker(服務端使用)
- 遠端通訊執行的Invoker(客戶端使用)
- 多個型別2的Invoker聚合成的叢集版Invoker(客戶端使用)
Invoker的實現情況如下:
先來看服務引用的第2個步驟,返回Invoker物件
對於客戶端來說,Invoker應該是2、3這兩種型別。先來說第2種型別,即遠端通訊的Invoker,看DubboInvoker的原始碼,呼叫過程AbstractInvoker.invoke()->doInvoke():
protected Result doInvoke(final Invocation invocation) throws Throwable {
RpcInvocation inv = (RpcInvocation) invocation;
final String methodName = RpcUtils.getMethodName(invocation);
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);
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);
RpcContext.getContext().setFuture(null);
return new RpcResult();
} else if (isAsync) {
ResponseFuture future = currentClient.request(inv, timeout) ;
RpcContext.getContext().setFuture(new FutureAdapter<Object>(future));
return new RpcResult();
} else {
RpcContext.getContext().setFuture(null);
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);
}
}
大致內容就是:
將通過遠端通訊將Invocation資訊傳遞給伺服器端,伺服器端接收到該Invocation資訊後,找到對應的本地Invoker,然後通過反射執行相應的方法,將方法的返回值再通過遠端通訊將結果傳遞給客戶端。
這裡分3種情況:
- 執行方法不需要返回值:直接呼叫
ExchangeClient.send()
方法。 - 執行方法的結果需要非同步返回:使用
ExchangeClient.request()
方法返回一個ResponseFuture
物件,通過RpcContext
中的ThreadLocal
使ResponseFuture
和當前執行緒繫結,未等服務端響應結果就直接返回,然後服務端通過ProtocolFilterWrapper.buildInvokerChain()
方法會呼叫Filter.invoke()
方法,即FutureFilter.invoker()->asyncCallback()
,會獲取RpcContext
的ResponseFuture
物件,非同步返回結果。 - 執行方法的結果需要同步返回:使用
ExchangeClient.request()
方法,返回一個ResponseFuture
,一直阻塞到服務端返回響應結果
Protocol
服務引用的第二步就是:
Protocol refprotocol = ExtensionLoader.getExtensionLoader(Protocol.class).getAdaptiveExtension();
invoker = refprotocol.refer(interfaceClass, url);
使用協議Protocol根據上述的url和服務介面來引用服務,建立出一個Invoker物件
預設實現的DubboProtocol也會經過ProtocolFilterWrapper、ProtocolListenerWrapper、RegistryProtocol的包裝
首先看下RegistryProtocol.refer()方法,它幹了哪些事呢?
- 將客戶端的資訊註冊到註冊中心上
- 建立一個RegistryDirectory,從註冊中心中訂閱自己引用的服務,將訂閱到的url在RegistryDirectory內部轉換成Invoker。
- RegistryDirectory是Directory的實現,Directory代表多個Invoker,可以把它看成List型別的Invoker,但與List不同的是,它的值可能是動態變化的,比如註冊中心推送變更RegistryDirectory內部含有兩者重要屬性:
- 註冊中心服務Registry
- Protocol 它會利用註冊中心服務Registry來獲取最新的伺服器端註冊的url地址,然後再利用協議Protocol將這些url地址轉換成一個具有遠端通訊功能的Invoker物件,如DubboInvoker
- 在Directory的基礎上使用Cluster將上述多個Invoker物件聚合成一個叢集版的Invoker物件
Directory和Cluster都是服務治理的重點,接下去會單獨拿一章出來講
ProxyFactory
服務引用的第三步就是:
proxyFactory.getProxy(invoker);
對於Server端,ProxyFactory主要負責將服務如HelloServiceImpl統一進行包裝成一個Invoker,這些Invoker通過反射來執行具體的HelloServiceImpl物件的方法
對於client端,則是將上述建立的叢集版Invoker(Cluster)建立出代理物件
程式碼如下:
public class JavassistProxyFactory extends AbstractProxyFactory {
@SuppressWarnings("unchecked")
public <T> T getProxy(Invoker<T> invoker, Class<?>[] interfaces) {
return (T) Proxy.getProxy(interfaces).newInstance(new InvokerInvocationHandler(invoker));
}
可以看到是利用jdk自帶的Proxy來動態代理目標物件Invoker,所以我們呼叫建立出來的代理物件如HelloService的方法時,會執行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]);
}
//AbstractClusterInvoker.invoke()
return invoker.invoke(new RpcInvocation(method, args)).recreate();
}
}
參考:http://dubbo.apache.org/books/dubbo-dev-book/implementation.html
參考:https://my.oschina.net/xiaominmin/blog/1599378
Contact
- 作者:鵬磊
- 出處:http://www.ymq.io/2018/06/13/dubbo_rpc_refer
- 版權歸作者所有,轉載請註明出處
- Wechat:關注公眾號,搜雲庫,專注於開發技術的研究與知識分享