dubbo原始碼分析之服務呼叫方發起呼叫(入口InvokerInvocationHandler.invoke)
一、簡介
dubbo的服務呼叫主要包括幾個大的步驟
1.傳送請求
2.編解碼
3.服務降級
4.過濾器鏈處理
5.序列化
6.執行緒派發以及響應請求
二、服務消費者發起呼叫
dubbo服務呼叫支援的方式
同步呼叫(預設)
非同步呼叫
1.有返回值
2.無返回值(不關心結果。直接返回一個空的 RpcResult)
觸發的呼叫鏈
能夠看到先是通過ReferenceAnnotationBeanPostProcessor$ReferenceBeanInvocationHandler.invoke-------->反射呼叫-------->DelegatingMethodAccessorImpl.invoke-------->NativeMethodAccessorImpl.invoke-------->代理類proxy0.sayHello--------> InvokerInvocationHandler.invoke
代理類的程式碼
Dubbo 預設使用 Javassist 框架為服務介面生成動態代理類,因此我們需要先將代理類進行反編譯才能看到原始碼
反編譯後看一下代理類的程式碼
(反編譯的方式看這章:dubbo原始碼分析之服務呼叫方refer(服務引用、建立invoker、建立代理、檢視動態生成的.class檔案))
package com.alibaba.dubbo.common.bytecode;
import com.alibaba.dubbo.common.bytecode.ClassGenerator.DC;
import com.alibaba.dubbo.rpc.service.EchoService;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import org.apache.dubbo.samples.api.client.HelloService;
public class proxy0 implements DC, HelloService, EchoService {
// 方法陣列
public static Method[] methods;
private InvocationHandler handler;
public proxy0(InvocationHandler var1) {
this.handler = var1;
}
public proxy0() {
}
public String sayHello(String var1) {
// 將引數儲存到 Object 陣列中
Object[] var2 = new Object[]{var1};
// 呼叫 InvocationHandler 實現類的 invoke 方法得到呼叫結果
Object var3 = this.handler.invoke(this, methods[0], var2);
// 返回撥用結果
return (String)var3;
}
/** 回聲測試方法 */
public Object $echo(Object var1) {
Object[] var2 = new Object[]{var1};
Object var3 = this.handler.invoke(this, methods[1], var2);
return (Object)var3;
}
主要做了幾件事:
1.將執行時引數儲存到陣列中
2.呼叫 InvocationHandler 介面實現類的 invoke 方法,得到呼叫結果
3.將結果轉型並返回給呼叫方。
跟進去看一下這個invoke方法
InvokerInvocationHandler.invoke
@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();
}
主要做了幾件事:
1.如果呼叫的方法是屬於Object的,比如wait/notify,那麼直接呼叫AnnotationHelloServiceConsumer類的該方法
2.如果呼叫的方法是toString hashCode equals,那麼呼叫invoker對應的該方法
此處的invoker是
3.如果呼叫的是服務提供方的方法,則invoker.invoke進行RPC遠端呼叫
MockClusterInvoker.invoke
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")) {
//debug的時候走的這裡
// 無 mock 邏輯,直接呼叫其他 Invoker 物件的 invoke 方法,
// 比如 FailoverClusterInvoker
//no mock
result = this.invoker.invoke(invocation);
} else if (value.startsWith("force")) {
if (logger.isWarnEnabled()) {
logger.info("force-mock: " + invocation.getMethodName() + " force-mock enabled , url : " + directory.getUrl());
}
// force:xxx 直接執行 mock 邏輯,不發起遠端呼叫
//force:direct mock
result = doMockInvoke(invocation, null);
} else {
// fail:xxx 表示消費方對呼叫服務失敗後,再執行 mock 邏輯,不丟擲異常
//fail-mock
try {
// 呼叫其他 Invoker 物件的 invoke 方法
result = this.invoker.invoke(invocation);
} catch (RpcException e) {
if (e.isBiz()) {
throw e;
} else {
if (logger.isWarnEnabled()) {
logger.warn("fail-mock: " + invocation.getMethodName() + " fail-mock enabled , url : " + directory.getUrl(), e);
}
// 呼叫失敗,執行 mock 邏輯
result = doMockInvoke(invocation, e);
}
}
}
return result;
}
主要做了幾件事:
1.獲取 mock 配置值
2.如果無 mock 邏輯,直接呼叫其他 Invoker 物件的 invoke 方法,比如 FailoverClusterInvoker.invoke
3.如果mock配置是 force:xxx 直接呼叫doMockInvoke方法執行 mock 邏輯,不發起遠端呼叫
4. 如果Mock配置是 fail:xxx 表示消費方使用Invoker.invoke呼叫服務失敗後,再使用doMockInvoke執行 mock 邏輯,不丟擲異常
其中 doMockInvoke跟服務降級相關,後面再專門寫一篇說這個
AbstractClusterInvoker.invoke
@Override
public Result invoke(final Invocation invocation) throws RpcException {
checkWhetherDestroyed();
LoadBalance loadbalance = null;
//將RpcInvocation與attachments繫結
// binding attachments into invocation.
Map<String, String> contextAttachments = RpcContext.getContext().getAttachments();
if (contextAttachments != null && contextAttachments.size() != 0) {
((RpcInvocation) invocation).addAttachments(contextAttachments);
}
//呼叫Directory的list方法,得到符合路由條件的invoker
List<Invoker<T>> invokers = list(invocation);
//得到負載均衡器LoadBalance
if (invokers != null && !invokers.isEmpty()) {
loadbalance = ExtensionLoader.getExtensionLoader(LoadBalance.class).getExtension(invokers.get(0).getUrl()
.getMethodParameter(RpcUtils.getMethodName(invocation), Constants.LOADBALANCE_KEY, Constants.DEFAULT_LOADBALANCE));
}
RpcUtils.attachInvocationIdIfAsync(getUrl(), invocation);
//執行呼叫
return doInvoke(invocation, invokers, loadbalance);
}
做了幾件事:
1.將RpcInvocation與attachments繫結
執行後
2.呼叫Directory的list方法,得到符合路由條件的invoker
3.得到負載均衡器LoadBalance
debug的時候得到的是
估計預設就是RandomLoadBalance
4.RpcUtils.attachInvocationIdIfAsync方法
執行後
看起來沒什麼區別
4.執行呼叫failoverClusterInvoker.doInvoke
FailoverClusterInvoker.doInvoke
這個類的這個方法上一章分析過了
這個方法裡面再往後面呼叫,的呼叫棧是:
FailoverClusterInvoker.doInvoke-------->InvokerWrapper.invoke-------->ListenerInvoker.invoke-------->ProtocolFilterWrapper.invoke-------->ConsumerContextFilter.invoke-------->ProtocolFilterWrapper.invoke-------->FutureFilter.invoke-------->ProtocolFilter.invoke-------->MonitorFilter.invoke-------->AbstractInvoker.invoke
那下面我們來看
AbstractInvoker.invoke
public Result invoke(Invocation inv) throws RpcException {
// if invoker is destroyed due to address refresh from registry, let's allow the current invoke to proceed
if (destroyed.get()) {
logger.warn("Invoker for service " + this + " on consumer " + NetUtils.getLocalHost() + " is destroyed, "
+ ", dubbo version is " + Version.getVersion() + ", this invoker should not be used any longer");
}
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) {
/**
* invocation.addAttachmentsIfAbsent(context){@link RpcInvocation#addAttachmentsIfAbsent(Map)}should not be used here,
* because the {@link RpcContext#setAttachment(String, String)} is passed in the Filter when the call is triggered
* by the built-in retry mechanism of the Dubbo. The attachment to update RpcContext will no longer work, which is
* a mistake in most cases (for example, through Filter to RpcContext output traceId and spanId and other information).
*/
// 新增 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) { // biz exception
Throwable te = e.getTargetException();
if (te == null) {
return new RpcResult(e);
} else {
if (te instanceof RpcException) {
((RpcException) te).setCode(RpcException.BIZ_EXCEPTION);
}
return new RpcResult(te);
}
} catch (RpcException e) {
if (e.isBiz()) {
return new RpcResult(e);
} else {
throw e;
}
} catch (Throwable e) {
return new RpcResult(e);
}
}
做了幾件事:
1.新增資訊到 RpcInvocation#attachment 變數中
新增完後
2.呼叫 子類doInvoke 執行後續的呼叫。debugg的時候就是呼叫的 DubboInvoker 類的doInvoke
DubboInvoker.doInvoke
該方法是 Dubbo 對同步和非同步呼叫的處理邏輯
同步呼叫模式下,由框架自身呼叫 ResponseFuture 的 get 方法。
非同步呼叫模式下,則由使用者呼叫該方法。
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(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);
}
}
主要做了幾件事:
1.設定attachment
2.得到Exchange(debug的時候這個Exchange是ReferenceCountExchangeClient,這個底層傳送的邏輯後面專門寫一篇來說),呼叫它的send或者request方法進行遠端呼叫
它是做底層操作的,發起遠端呼叫就是它負責
3.根據標識執行對應的呼叫方式
比如是,同步呼叫、非同步有返回值呼叫、非同步無返回值呼叫
預設是同步呼叫,debug的時候也是走的同步呼叫
而debug的時候這個currentClient.request得到的值是DefaultFuture
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>();
static {
Thread th = new Thread(new RemotingInvocationTimeoutScan(), "DubboResponseTimeoutScanTimer");
th.setDaemon(true);
th.start();
}
// invoke id.
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 final long start = System.currentTimeMillis();
private volatile long sent;
private volatile Response response;
private volatile ResponseCallback callback;
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);
// put into waiting map.
// 儲存 <requestId, DefaultFuture> 對映關係到 FUTURES 中
FUTURES.put(id, this);
CHANNELS.put(id, channel);
}
@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());
}
}
主要做了1件事
當服務消費者還未接收到呼叫結果時,使用者執行緒呼叫 get 方法會被阻塞住。
同步呼叫模式下,框架獲得 DefaultFuture 物件後,會立即呼叫 get 方法進行等待。
而非同步模式下則是將該物件封裝到 FutureAdapter 例項中,並將 FutureAdapter 例項設定到 RpcContext 中,供使用者使用。
FutureAdapter 是一個介面卡,用於將 Dubbo 中的 ResponseFuture 與 JDK 中的 Future 進行適配。
這樣當使用者執行緒呼叫 Future 的 get 方法時,經過 FutureAdapter 適配,最終會呼叫 ResponseFuture 實現類物件的 get 方法,也就是 DefaultFuture 的 get 方法。
相關文章
- Dubbo原始碼分析(五)Dubbo呼叫鏈-服務端原始碼服務端
- Dubbo原始碼解析之服務呼叫過程原始碼
- Dubbo服務呼叫過程原始碼解析④原始碼
- Dubbo原始碼分析(十)同步呼叫與非同步呼叫原始碼非同步
- Dubbo原始碼解析之客戶端初始化及服務呼叫原始碼客戶端
- 5.原始碼分析---SOFARPC呼叫服務原始碼RPC
- Dubbo原始碼分析之服務引用原始碼
- Dubbo原始碼分析之服務暴露原始碼
- Dubbo原始碼之服務引用原始碼
- dubbo原始碼分析02:服務引用原始碼
- Dubbo原始碼分析十一、服務路由原始碼路由
- SpringCloud之服務呼叫SpringGCCloud
- Dubbo原始碼分析(三)Dubbo的服務引用Refer原始碼
- Dubbo服務消費者呼叫過程
- Spring原始碼分析之`BeanFactoryPostProcessor`呼叫過程Spring原始碼Bean
- Dubbo原始碼分析(七)服務目錄原始碼
- Node 呼叫 dubbo 服務的探索及實踐
- Dubbo原始碼解析之服務叢集原始碼
- Dubbo原始碼分析(四)Dubbo呼叫鏈-消費端(叢集容錯機制)原始碼
- 服務與服務之間的呼叫
- WindowManager呼叫流程原始碼分析原始碼
- Spring原始碼分析之AOP從解析到呼叫Spring原始碼
- ②SpringCloud 實戰:引入Feign元件,發起服務間呼叫SpringGCCloud元件
- Dubbo原始碼解析之服務引入過程原始碼
- Dubbo原始碼之服務端的釋出原始碼服務端
- Dubbo原始碼學習之-服務匯出原始碼
- Dubbo原理和原始碼解析之服務引用原始碼
- eureka服務之間呼叫(3)
- SOFA 原始碼分析 — 泛化呼叫原始碼
- dubbo原始碼解析(三十)遠端呼叫——rest協議原始碼REST協議
- Dubbo之SPI原始碼分析原始碼
- Dubbo原始碼解析之服務端接收訊息原始碼服務端
- Dubbo原始碼解析之服務匯出過程原始碼
- Dapr初體驗之服務呼叫
- dubbo服務者原始碼分期原始碼
- Dubbo服務暴露原始碼解析②原始碼
- Dubbo原始碼分析(六)服務引用的具體流程原始碼
- dubbo原始碼解析(三十一)遠端呼叫——rmi協議原始碼協議