文章目錄
dubbo的服務調用過程主要包括髮送請求,編解碼,服務降級,過濾器鏈處理,序列化,線程派發以及響應請求等步驟。本文主要分析請求的發送與接收,編解碼,線程派發以及響應的發送與接收。
1. 服務調用過程:
遠程調用請求的發送與接收的過程:
- 消費者通過代理對象Proxy發起遠程調用,
- 接着通過網絡客戶端Client將編碼後的請求發送給服務提供方的網絡層,也就是Server。
- Server收到請求後對數據包進行解碼,將解碼後的請求發送至分發器Dispatcher,
- 再由分發器將請求派發到制定的線程池上,
- 最後由線程池調用具體的服務。
2.源碼分析
2.1 服務調用方式
- Dubbo支持同步和異步兩種調用方式,
- 其中異步調用還可以細分爲“有返回值”和“無返回值”的異步調用。
- 無返回值的異步調用,是指消費者不用關心調用結果,Dubbo會直接返回一個空的RpcResult。
- 默認情況下Dubbo使用同步調用方式,使用異步調用需要消費方手動配置;
在服務引入的過程分析到客戶端已經生成了代理類,下面是一個代理類的示例:
public class proxy0 implements ClassGenerator.DC, EchoService, DemoService {
// 方法數組
public static Method[] methods;
private InvocationHandler handler;
public proxy0(InvocationHandler invocationHandler) {
this.handler = invocationHandler;
}
public proxy0() {
}
public String sayHello(String string) {
// 將參數存儲到 Object 數組中
Object[] arrobject = new Object[]{string};
// 調用 InvocationHandler 實現類的 invoke 方法得到調用結果
Object object = this.handler.invoke(this, methods[0], arrobject);
// 返回調用結果
return (String)object;
}
/** 回聲測試方法 */
public Object $echo(Object object) {
Object[] arrobject = new Object[]{object};
Object object2 = this.handler.invoke(this, methods[1], arrobject);
return object2;
}
}
代理類的邏輯比較簡單,
- 將運行時的參數存儲到數組中,
- 然後調用InvocationHandler接口實現類的invoke方法,得到調用結果,最後將結果轉型並返回給調用方;
下面看一下invoke方法走的邏輯:
public class InvokerInvocationHandler implements InvocationHandler {
private final Invoker<?> invoker;
public InvokerInvocationHandler(Invoker<?> handler) {
this.invoker = handler;
}
@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();
}
}
InvokerInvocationHandler 中的 invoker 成員變量類型爲 MockClusterInvoker,MockClusterInvoker 內部封裝了服務降級邏輯。
public class MockClusterInvoker<T> implements Invoker<T> {
private final Invoker<T> invoker;
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")) {
// 無 mock 邏輯,直接調用其他 Invoker 對象的 invoke 方法,
// 比如 FailoverClusterInvoker
result = this.invoker.invoke(invocation);
} else if (value.startsWith("force")) {
// force:xxx 直接執行 mock 邏輯,服務降級,不發起遠程調用
result = doMockInvoke(invocation, null);
} else {
// fail:xxx 表示消費方對調用服務失敗後,再執行 mock 邏輯,不拋出異常
try {
// 調用其他 Invoker 對象的 invoke 方法
result = this.invoker.invoke(invocation);
} catch (RpcException e) {
if (e.isBiz()) {
throw e;
} else {
// 調用失敗,執行服務降級 mock 邏輯
result = doMockInvoke(invocation, e);
}
}
}
return result;
}
// 省略其他方法
}
這裏不分析服務降級部分的doMockInvoke ,所以mock部分的邏輯不在分析;
this.invoker.invoke(invocation);這裏調用Invoker對象的invoke方法,這塊的Invoker我們直接分析默認的DubboInvoker內的Invoke實現
public abstract class AbstractInvoker<T> implements Invoker<T> {
public Result invoke(Invocation inv) throws RpcException {
if (destroyed.get()) {
throw new RpcException("Rpc invoker for service ...");
}
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) {
// 添加 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) {
// ...
} catch (RpcException e) {
// ...
} catch (Throwable e) {
return new RpcResult(e);
}
}
protected abstract Result doInvoke(Invocation invocation) throws Throwable;
// 省略其他方法
}
上面是AbstractInvoker抽象類,doInvoke是一個抽象方法,需要由其子類實現,這裏看一下DubboInvoker的實現;
public class DubboInvoker<T> extends AbstractInvoker<T> {
private final ExchangeClient[] clients;
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(..., "Invoke remote method timeout....");
} catch (RemotingException e) {
throw new RpcException(..., "Failed to invoke remote method: ...");
}
}
// 省略其他方法
}
以上時Dubbo對同步和異步調用的處理邏輯,關於在於誰調用ResponseFuture的get方法;
默認同步模式下,由框架自身調用ResponseFuture的get方法,
異步調用模式下,由用戶調用該方法。
ResponseFuture是一個接口,默認實現類是DefaultFuture
public class DefaultFuture implements ResponseFuture {
private static final Map<Long, Channel> CHANNELS =
new ConcurrentHashMap<Long, Channel>();
private static final Map<Long, DefaultFuture> FUTURES =
new ConcurrentHashMap<Long, DefaultFuture>();
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 volatile Response response;
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);
// 存儲 <requestId, DefaultFuture> 映射關係到 FUTURES 中
FUTURES.put(id, this);
CHANNELS.put(id, channel);
}
@Override
public Object get() throws RemotingException {
return get(timeout);
}
@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());
}
// 省略其他方法
}
當服務消費者還會受到調用結果事後,用戶線程調用get方法會被阻塞住。
同步調用模式下,框架獲得返回的DefaultFuture對象後,會立即調用get方法進行等待。
異步模式下,將對象封裝到FutureAdapter實例中,並將FutureAdapter實例設置到RpcContext中,供用戶使用。
FutureAdapter是一個適配器,用於將Dubbo中的ResponseFuture與JDK中的Future進行適配,當用戶線程調用Future的get方法時,經過FutureAdapter適配,最終會調用ResponseFuture實現類對象的get方法,也就是DefaultFuture的get方法;
2.2 服務消費方發送請求
2.2.1 消費方請求調用路徑示例:
proxy0#sayHello(String)
// 確定調用方式
—> InvokerInvocationHandler#invoke(Object, Method, Object[])
—> MockClusterInvoker#invoke(Invocation)
—> AbstractClusterInvoker#invoke(Invocation)
—> FailoverClusterInvoker#doInvoke(Invocation, List<Invoker>, LoadBalance)
—> Filter#invoke(Invoker, Invocation) // 包含多個 Filter 調用
—> ListenerInvokerWrapper#invoke(Invocation)
—> AbstractInvoker#invoke(Invocation)
—> DubboInvoker#doInvoke(Invocation)
// ReferenceCountExchangeClient維護一個引用計數變量,根據對象被引用和close進行增減
// 其他方法均是調用被裝飾對象的相關方法
—> ReferenceCountExchangeClient#request(Object, int)
// HeaderExchangeClient的很多方法都是調用HeaderExchangeChannel對象的同簽名方法,
// HeaderExchangeClient主要用於封裝一些關於心跳檢測的邏輯
—> HeaderExchangeClient#request(Object, int)
—> HeaderExchangeChannel#request(Object, int)
// 定義了一個 Request 對象,然後再將該對象傳給 NettyClient 的 send 方法
// NettyClient 的 send 方法繼承自父類AbstractPeer
—> AbstractPeer#send(Object)
—> AbstractClient#send(Object, boolean)
—> NettyChannel#send(Object, boolean)
—> NioClientSocketChannel#write(Object)
>
<a name="SlO2i"></a>
### 2.2.2 請求編碼
![image.png](https://cdn.nlark.com/yuque/0/2019/png/198536/1564897427419-03e22233-58fd-4c82-ae4a-72b282c76058.png#align=left&display=inline&height=361&name=image.png&originHeight=496&originWidth=1451&size=287609&status=done&width=1055.2727272727273)<br />數據包分爲消息頭和消息體,
- 消息頭(header):用於存儲一些元信息
- 魔數(Magic)
- 數據包類型(Request/Response)
- 消息體長度(Data Length)
- 消息體(body):用於存儲具體的調用信息
- 方法名
- 參數列表
請求對象的編碼過程主要包含以下邏輯:
1. 首先會通過位運算符將**消息頭寫入到header數組、**
1. 然後對Request對象的data字段執行**序列化操作**,序列化後的數據最終會存儲到ChannelBuffer中。
1. 序列化操作執行完後,可得到數據序列化後的長度len,將len寫入到header指定位置處。
1. 將消息頭字節數組header寫入到ChannelBuffer中,整個編碼過程結束。
<a name="LC11V"></a>
## 2.3 服務提供方接收請求
默認情況下Dubbo使用Netty作爲底層的通信框架,Netty檢測到有數據入站後,首先會通過解碼器對數據進行解碼,然後將解碼後的數據傳遞給下一個入棧處理器的制定方法,下面來分析數據解碼的過程
<a name="LQO3l"></a>
### 2.3.1 請求解碼
ExchangeCodec類decode方法解碼<br />
```java
public class ExchangeCodec extends TelnetCodec {
@Override
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);
}
@Override
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;
}
}
// 通過 telnet 命令行發送的數據包不包含消息頭,所以這裏
// 調用 TelnetCodec 的 decode 方法對數據包進行解碼
return super.decode(channel, buffer, readable, header);
}
// 檢測可讀數據量是否少於消息頭長度,若小於則立即返回 DecodeResult.NEED_MORE_INPUT
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;
}
ChannelBufferInputStream is = new ChannelBufferInputStream(buffer, len);
try {
// 繼續進行解碼工作-- 子類DubboCodec 重寫了該方法
// 在運行時子類DubboCodec的decodeBody方法會被調用
return decodeBody(channel, is, header);
} finally {
if (is.available() > 0) {
try {
StreamUtils.skipUnusedStream(is);
} catch (IOException e) {
logger.warn(e.getMessage(), e);
}
}
}
}
}
解碼部分的主要邏輯:
- 檢測消息頭的魔數與規定的是否相等,提前攔截掉非常規數據包
- 對消息體長度以及刻度字節數進行檢測。
- 調用子類 DubboCodec 的decodeBody方法進行後續解碼工作
- DubboCodec.decodeBody只對部分字段解碼,將解碼得到的字段封裝到Request中。
- DecodeableRpcInvocation.decode 後續解碼工作,通過反序列化將調用方法名,path,version,調用方法名,參數列表等依次解析出來,並設置到相應的字段彙總,最後得到一個完整的ecodeableRpcInvocation 對象。
- 最後得到一個完整的Request對象,該對象將被傳送到下一個入站處理器中。
### 2.3.2 調用服務 NettyHandler的messageReceived方法會接收上面解碼後的Request對象,並依次往下傳遞對象,這期間會經歷NettyServer,MultiMessageHandler,HeartbeatHandler,以及AllChannelHandler。最後由AllChannelHandler將該對象封裝到Runnable實現類對象中,並將Runnable放入線程池中進行後續調用邏輯。調用棧如下:
NettyHandler#messageReceived(ChannelHandlerContext, MessageEvent)
—> AbstractPeer#received(Channel, Object)
—> MultiMessageHandler#received(Channel, Object)
—> HeartbeatHandler#received(Channel, Object)
—> AllChannelHandler#received(Channel, Object)
—> ExecutorService#execute(Runnable) // 由線程池執行後續的調用邏輯
2.3.2.1 線程派發模型
線程派發器所處位置:
線程派發的背景:
Dubbo將底層通信框架中接受請求的線程成爲IO線程,如果一些事件處理邏輯可以很快執行完,比如只在內存打個標記,此時直接在IO線程上執行。但如果事件的處理比較耗時(需要發起數據庫查詢或者http請求等),我們就不應該讓事件處理邏輯在IO線程上執行,而是派發到線程池中執行。
爲什麼要派發到線程池呢?
IO線程主要用於接收請求,如果IO線程被佔滿,將導致它不能接收新的請求。
線程派發器Dispatcher的職責:
Dispatcher本身不具備線程派發能力,他真正的職責是創建具有線程派發能力的ChannelHandler,比如AllChannelHandler,MessageOnlyChannelHandler等,這些不同的XXHandler分別對應着不同的派發策略。
策略 | 用途 |
---|---|
all(默認策略) | 所有消息都派發到線程池,包括請求,響應,連接事件,斷開事件等 |
direct | 所有消息都不派發到線程池,全部在 IO 線程上直接執行 |
message | 只有請求和響應消息派發到線程池,其它消息均在 IO 線程上執行 |
execution | 只有請求消息派發到線程池,不含響應。其它消息均在 IO 線程上執行 |
connection | 在 IO 線程上,將連接斷開事件放入隊列,有序逐個執行,其它消息派發到線程池 |
Dubbo默認的線程派發策略是all,也就是默認創建AllChannelHandler將所有的消息都派發到線程池去執行。下面看一下AllChannelHandler的實現細節。
public class AllChannelHandler extends WrappedChannelHandler {
public AllChannelHandler(ChannelHandler handler, URL url) {
super(handler, url);
}
/** 處理連接事件 */
@Override
public void connected(Channel channel) throws RemotingException {
// 獲取線程池
ExecutorService cexecutor = getExecutorService();
try {
// 將連接事件派發到線程池中處理
cexecutor.execute(new ChannelEventRunnable(channel, handler, ChannelState.CONNECTED));
} catch (Throwable t) {
throw new ExecutionException(..., " error when process connected event .", t);
}
}
/** 處理斷開事件 */
@Override
public void disconnected(Channel channel) throws RemotingException {
ExecutorService cexecutor = getExecutorService();
try {
cexecutor.execute(new ChannelEventRunnable(channel, handler, ChannelState.DISCONNECTED));
} catch (Throwable t) {
throw new ExecutionException(..., "error when process disconnected event .", t);
}
}
/** 處理請求和響應消息,這裏的 message 變量類型可能是 Request,也可能是 Response */
@Override
public void received(Channel channel, Object message) throws RemotingException {
ExecutorService cexecutor = getExecutorService();
try {
// 將請求和響應消息派發到線程池中處理
cexecutor.execute(new ChannelEventRunnable(channel, handler, ChannelState.RECEIVED, message));
} catch (Throwable t) {
if(message instanceof Request && t instanceof RejectedExecutionException){
Request request = (Request)message;
// 如果通信方式爲雙向通信,此時將 Server side ... threadpool is exhausted
// 錯誤信息封裝到 Response 中,並返回給服務消費方。
if(request.isTwoWay()){
String msg = "Server side(" + url.getIp() + "," + url.getPort()
+ ") threadpool is exhausted ,detail msg:" + t.getMessage();
Response response = new Response(request.getId(), request.getVersion());
response.setStatus(Response.SERVER_THREADPOOL_EXHAUSTED_ERROR);
response.setErrorMessage(msg);
// 返回包含錯誤信息的 Response 對象
channel.send(response);
return;
}
}
throw new ExecutionException(..., " error when process received event .", t);
}
}
/** 處理異常信息 */
@Override
public void caught(Channel channel, Throwable exception) throws RemotingException {
ExecutorService cexecutor = getExecutorService();
try {
// 請求對象被封裝在ChannelEventRunnable中
cexecutor.execute(new ChannelEventRunnable(channel, handler, ChannelState.CAUGHT, exception));
} catch (Throwable t) {
throw new ExecutionException(..., "error when process caught event ...");
}
}
}
請求對象被封裝在ChannelEventRunnable中,ChannelEventRunnable也是服務調用過程的下一個起點。
2.3.2.2 調用服務
從起點ChannelEventRunnable 開始分析:
public class ChannelEventRunnable implements Runnable {
private final ChannelHandler handler;
private final Channel channel;
private final ChannelState state;
private final Throwable exception;
private final Object message;
@Override
public void run() {
// 檢測通道狀態,對於請求或響應消息,此時 state = RECEIVED
if (state == ChannelState.RECEIVED) {
try {
// 將 channel 和 message 傳給 ChannelHandler 對象,進行後續的調用
handler.received(channel, message);
} catch (Exception e) {
logger.warn("... operation error, channel is ... message is ...");
}
}
// 其他消息類型通過 switch 進行處理
else {
switch (state) {
case CONNECTED:
try {
handler.connected(channel);
} catch (Exception e) {
logger.warn("... operation error, channel is ...");
}
break;
case DISCONNECTED:
// ...
case SENT:
// ...
case CAUGHT:
// ...
default:
logger.warn("unknown state: " + state + ", message is " + message);
}
}
}
}
ChannelEventRunnable僅僅是一箇中轉站,他的run方法並不包含具體的調用邏輯,只是根據消息的類型將消息傳遞給其他ChannelHandler對象進行處理。
DecodeHandler的處理如下:
public class DecodeHandler extends AbstractChannelHandlerDelegate {
public DecodeHandler(ChannelHandler handler) {
super(handler);
}
@Override
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);
}
private void decode(Object message) {
// Decodeable 接口目前有兩個實現類,
// 分別爲 DecodeableRpcInvocation 和 DecodeableRpcResult
if (message != null && message instanceof Decodeable) {
try {
// 執行解碼邏輯
((Decodeable) message).decode();
} catch (Throwable e) {
if (log.isWarnEnabled()) {
log.warn("Call Decodeable.decode failed: " + e.getMessage(), e);
}
}
}
}
}
DecodeHandler 主要是包含了一些解碼邏輯,請求解碼可在IO線程上執行,也可在線程池中執行,取決運行時配置的線程派發策略,DecodeHandler就是用來保證請求或響應對象刻在線程池中被解碼,解碼完畢後的Request對象會被傳遞給下一站HeaderExchangeHandler.
public class HeaderExchangeHandler implements ChannelHandlerDelegate {
private final ExchangeHandler handler;
public HeaderExchangeHandler(ExchangeHandler handler) {
if (handler == null) {
throw new IllegalArgumentException("handler == null");
}
this.handler = handler;
}
@Override
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()) {
// 向後調用服務,並得到調用結果
Response response = handleRequest(exchangeChannel, request);
// 將調用結果返回給服務消費端
channel.send(response);
}
// 如果是單向通信,僅向後調用指定服務即可,無需返回調用結果
else {
handler.received(exchangeChannel, request.getData());
}
}
}
// 處理響應對象,服務消費方會執行此處邏輯,後面分析
else if (message instanceof Response) {
handleResponse(channel, (Response) message);
} else if (message instanceof String) {
// telnet 相關,忽略
} else {
handler.received(exchangeChannel, message);
}
} finally {
HeaderExchangeChannel.removeChannelIfDisconnected(channel);
}
}
Response handleRequest(ExchangeChannel channel, Request req) throws RemotingException {
Response res = new Response(req.getId(), req.getVersion());
// 檢測請求是否合法,不合法則返回狀態碼爲 BAD_REQUEST 的響應
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 字段值,也就是 RpcInvocation 對象
Object msg = req.getData();
try {
// 繼續向下調用
Object result = handler.reply(channel, msg);
// 設置 OK 狀態碼
res.setStatus(Response.OK);
// 設置調用結果
res.setResult(result);
} catch (Throwable e) {
// 若調用過程出現異常,則設置 SERVICE_ERROR,表示服務端異常
res.setStatus(Response.SERVICE_ERROR);
res.setErrorMessage(StringUtils.toString(e));
}
return res;
}
}
HeaderExchangeHandler 裏面包含了清晰的請求和響應邏輯,主要邏輯如下:
- 首先向後進行調用,得到調用結果。
- 然後將調用結果封裝到Response對象中,最後返回給服務消費方。
- 如果請求不合法或調用失敗,將錯誤信息封裝在Response對象中,並返回給消費方。
調用要走的邏輯還是根據協議轉發到DubboProtocol中,主要是獲取到Invoker對象調用Invoke方法執行業務邏輯。
public class DubboProtocol extends AbstractProtocol {
public static final String NAME = "dubbo";
private ExchangeHandler requestHandler = new ExchangeHandlerAdapter() {
@Override
public Object reply(ExchangeChannel channel, Object message) throws RemotingException {
if (message instanceof Invocation) {
Invocation inv = (Invocation) message;
// 獲取 Invoker 實例
Invoker<?> invoker = getInvoker(channel, inv);
if (Boolean.TRUE.toString().equals(inv.getAttachments().get(IS_CALLBACK_SERVICE_INVOKE))) {
// 回調相關,忽略
}
RpcContext.getContext().setRemoteAddress(channel.getRemoteAddress());
// 通過 Invoker 調用具體的服務
return invoker.invoke(inv);
}
throw new RemotingException(channel, "Unsupported request: ...");
}
// 忽略其他方法
}
Invoker<?> getInvoker(Channel channel, Invocation inv) throws RemotingException {
// 忽略回調和本地存根相關邏輯
// ...
int port = channel.getLocalAddress().getPort();
// 計算 service key,格式爲 groupName/serviceName:serviceVersion:port。比如:
// dubbo/com.alibaba.dubbo.demo.DemoService:1.0.0:20880
String serviceKey = serviceKey(port, path, inv.getAttachments().get(Constants.VERSION_KEY), inv.getAttachments().get(Constants.GROUP_KEY));
// 從 exporterMap 查找與 serviceKey 相對應的 DubboExporter 對象,
// 服務導出過程中會將 <serviceKey, DubboExporter> 映射關係存儲到 exporterMap 集合中
DubboExporter<?> exporter = (DubboExporter<?>) exporterMap.get(serviceKey);
if (exporter == null)
throw new RemotingException(channel, "Not found exported service ...");
// 獲取 Invoker 對象,並返回
return exporter.getInvoker();
}
// 忽略其他方法
}
根據指定的服務獲取到指定的Invoker實例之後,需要調用對應的invoke方法,invoke方法被定義在抽象類AbstractProxyInvoker中:
public abstract class AbstractProxyInvoker<T> implements Invoker<T> {
@Override
public Result invoke(Invocation invocation) throws RpcException {
try {
// 調用 doInvoke 執行後續的調用,並將調用結果封裝到 RpcResult 中,並
return new RpcResult(doInvoke(proxy, invocation.getMethodName(), invocation.getParameterTypes(), invocation.getArguments()));
} catch (InvocationTargetException e) {
return new RpcResult(e.getTargetException());
} catch (Throwable e) {
throw new RpcException("Failed to invoke remote proxy method ...");
}
}
protected abstract Object doInvoke(T proxy, String methodName, Class<?>[] parameterTypes, Object[] arguments) throws Throwable;
}
doInvoke依舊是一個抽象方法,具體的實現需要對應的Invoker實例來實現,Invoker實例就是在服務引用過程中通過JavassitProxyFactory創建的,回顧一下,創建邏輯如下:
public class JavassistProxyFactory extends AbstractProxyFactory {
// 省略其他方法
@Override
public <T> Invoker<T> getInvoker(T proxy, Class<T> type, URL url) {
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 {
// 調用 invokeMethod 方法進行後續的調用
return wrapper.invokeMethod(proxy, methodName, parameterTypes, arguments);
}
};
}
}
Wrapper是一個抽象類,調用的invokeMethod是一個抽象方法,dubbo會在運行時候利用Javassist爲wrapper生成一個實現類,並實現其invokeMethod方法,該方法會根據調用信息調用具體的服務。
整個調用過程的調用鏈如下:
ChannelEventRunnable#run()
—> DecodeHandler#received(Channel, Object)
—> HeaderExchangeHandler#received(Channel, Object)
—> HeaderExchangeHandler#handleRequest(ExchangeChannel, Request)
—> DubboProtocol.requestHandler#reply(ExchangeChannel, Object)
—> Filter#invoke(Invoker, Invocation)
—> AbstractProxyInvoker#invoke(Invocation)
—> Wrapper0#invokeMethod(Object, String, Class[], Object[])
—> DemoServiceImpl#sayHello(String)
2.4 服務端返回調用結果
服務端返回調用結果主要是調用Netty的send方法將調用結果response對象返回,但response對象在返回前也需要進行編碼,這塊的編碼和2.3.1中分析的請求編碼的邏輯是比較像的。結合着看比較容易理解。
public class ExchangeCodec extends TelnetCodec {
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 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);
// 更新 writerIndex,爲消息頭預留 16 個字節的空間
buffer.writerIndex(savedWriteIndex + HEADER_LENGTH);
ChannelBufferOutputStream bos = new ChannelBufferOutputStream(buffer);
ObjectOutput out = serialization.serialize(channel.getUrl(), bos);
if (status == Response.OK) {
if (res.isHeartbeat()) {
// 對心跳響應結果進行序列化,已廢棄
encodeHeartbeatData(channel, out, res.getResult());
} else {
// 對調用結果進行序列化
encodeResponseData(channel, out, res.getResult(), res.getVersion());
}
} 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);
// 將 buffer 指針移動到 savedWriteIndex,爲寫消息頭做準備
buffer.writerIndex(savedWriteIndex);
// 從 savedWriteIndex 下標處寫入消息頭
buffer.writeBytes(header);
// 設置新的 writerIndex,writerIndex = 原寫下標 + 消息頭長度 + 消息體長度
buffer.writerIndex(savedWriteIndex + HEADER_LENGTH + len);
} catch (Throwable t) {
// 異常處理邏輯不是很難理解,但是代碼略多,這裏忽略了
}
}
}
public class DubboCodec extends ExchangeCodec implements Codec2 {
protected void encodeResponseData(Channel channel, ObjectOutput out, Object data, String version) throws IOException {
Result result = (Result) data;
// 檢測當前協議版本是否支持帶有 attachment 集合的 Response 對象
boolean attach = Version.isSupportResponseAttachment(version);
Throwable th = result.getException();
// 異常信息爲空
if (th == null) {
Object ret = result.getValue();
// 調用結果爲空
if (ret == null) {
// 序列化響應類型
out.writeByte(attach ? RESPONSE_NULL_VALUE_WITH_ATTACHMENTS : RESPONSE_NULL_VALUE);
}
// 調用結果非空
else {
// 序列化響應類型
out.writeByte(attach ? RESPONSE_VALUE_WITH_ATTACHMENTS : RESPONSE_VALUE);
// 序列化調用結果
out.writeObject(ret);
}
}
// 異常信息非空
else {
// 序列化響應類型
out.writeByte(attach ? RESPONSE_WITH_EXCEPTION_WITH_ATTACHMENTS : RESPONSE_WITH_EXCEPTION);
// 序列化異常對象
out.writeObject(th);
}
if (attach) {
// 記錄 Dubbo 協議版本
result.getAttachments().put(Constants.DUBBO_VERSION_KEY, Version.getProtocolVersion());
// 序列化 attachments 集合
out.writeObject(result.getAttachments());
}
}
}
2.5 服務消費方接收調用結果
消費方收到服務方返回的數據包之後首先要進行解碼得到Response對象,然後再將該對象傳遞給NettyHandler處理器,NettyHandler處理器會依次往下傳遞,最後AllChannelHandler會接收到這個響應對象將其派發到線程池中,這個過程與服務端的過程是一致的,不再贅述。這塊重點分析一下如何對數據包解碼成Response對象,和如何將結果返回給用戶線程。
2.5.1 對響應數據解碼
這塊的邏輯主要封裝在DubboCodec中,和請求解碼部分的邏輯基本是一致的。
public class DubboCodec extends ExchangeCodec implements Codec2 {
@Override
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);
// 檢測消息類型,若下面的條件成立,表明消息類型爲 Response
if ((flag & FLAG_REQUEST) == 0) {
// 創建 Response 對象
Response res = new Response(id);
// 檢測事件標誌位
if ((flag & FLAG_EVENT) != 0) {
// 設置心跳事件
res.setEvent(Response.HEARTBEAT_EVENT);
}
// 獲取響應狀態
byte status = header[3];
// 設置響應狀態
res.setStatus(status);
// 如果響應狀態爲 OK,表明調用過程正常
if (status == Response.OK) {
try {
Object data;
if (res.isHeartbeat()) {
// 反序列化心跳數據,已廢棄
data = decodeHeartbeatData(channel, deserialize(s, channel.getUrl(), is));
} else if (res.isEvent()) {
// 反序列化事件數據
data = decodeEventData(channel, deserialize(s, channel.getUrl(), is));
} else {
DecodeableRpcResult result;
// 根據 url 參數決定是否在 IO 線程上執行解碼邏輯
if (channel.getUrl().getParameter(
Constants.DECODE_IN_IO_THREAD_KEY,
Constants.DEFAULT_DECODE_IN_IO_THREAD)) {
// 創建 DecodeableRpcResult 對象
result = new DecodeableRpcResult(channel, res, is,
(Invocation) getRequestData(id), proto);
// 進行後續的解碼工作
result.decode();
} else {
// 創建 DecodeableRpcResult 對象
result = new DecodeableRpcResult(channel, res,
new UnsafeByteArrayInputStream(readMessageData(is)),
(Invocation) getRequestData(id), proto);
}
data = result;
}
// 設置 DecodeableRpcResult 對象到 Response 對象中
res.setResult(data);
} catch (Throwable t) {
// 解碼過程中出現了錯誤,此時設置 CLIENT_ERROR 狀態碼到 Response 對象中
res.setStatus(Response.CLIENT_ERROR);
res.setErrorMessage(StringUtils.toString(t));
}
}
// 響應狀態非 OK,表明調用過程出現了異常
else {
// 反序列化異常信息,並設置到 Response 對象中
res.setErrorMessage(deserialize(s, channel.getUrl(), is).readUTF());
}
return res;
} else {
// 對請求數據進行解碼,前面已分析過,此處忽略
}
}
}
解碼完畢後需要將調用結果進行反序列化;
2.5.2 向用戶線程傳遞調用結果
Dubbo會將上一步解碼和反序列化完畢的調用結果派發到線程池上,但線程池的線程並不是用戶線程,用戶的線程此時是阻塞在DefaultFuture的get方法上,因爲用戶發送完請求後一直在等待get返回的結果Response,在Response到來後用戶線程將被喚醒,並通過調用編號獲取到自己的響應對象。
public class HeaderExchangeHandler implements ChannelHandlerDelegate {
@Override
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) {
// 處理請求,前面已分析過,省略
} else if (message instanceof Response) {
// 處理響應
handleResponse(channel, (Response) message);
} else if (message instanceof String) {
// telnet 相關,忽略
} else {
handler.received(exchangeChannel, message);
}
} finally {
HeaderExchangeChannel.removeChannelIfDisconnected(channel);
}
}
static void handleResponse(Channel channel, Response response) throws RemotingException {
if (response != null && !response.isHeartbeat()) {
// 繼續向下調用
DefaultFuture.received(channel, response);
}
}
}
public class DefaultFuture implements ResponseFuture {
private final Lock lock = new ReentrantLock();
private final Condition done = lock.newCondition();
private volatile Response response;
public static void received(Channel channel, Response response) {
try {
// 根據調用編號從 FUTURES 集合中查找指定的 DefaultFuture 對象
DefaultFuture future = FUTURES.remove(response.getId());
if (future != null) {
// 繼續向下調用
future.doReceived(response);
} else {
logger.warn("The timeout response finally returned at ...");
}
} 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);
}
}
}
上述邏輯是將響應對象Response保存到了DefaultFuture實例中,然後再喚醒用戶線程,隨後用戶線程可從DefaultFuture 實例中獲取結果對象.下面分析一下具體的邏輯:
- 消費方併發調用多個服務,每個用戶線程發出請求後都生成一個DefaultFuture對象,
- 每個DefaultFuture創建時都會傳入Request對象,
- DefaultFuture獲取到Request對象的調用編號。
- 將<調用編號, DefaultFuture 對象> 存入到一個靜態map集和中,即FUTURES.
- 然後調用DefaultFuture的get方法進行等待服務端響應結果Response的到來。
- 線程池的線程在收到Respondse對象時會通過Response對象的調用編號,從FUTURES集和中get到對應key的DefaultFuture對象。
- 將Response對象設置到DefaultFuture對象中。
- 喚醒用戶線程,從DefaultFuture的get方法中取到DefaultFuture對象獲取調用結果。
** 整個流程圖如下:**