1.概述
Dubbo 服务调用过程比较复杂,包含很多步骤,比如:发送请求、编解码/报文协议、服务降级、过滤器链处理、序列化、线程派发以及响应请求等步骤。本篇文章将会重点分析请求的发送与接收、编解码、线程派发以及响应的发送与接收等过程,至于服务降级、过滤器链和序列化后续在其他文章进行分析,暂时忽略。
2.源码
2.1 服务调用方式
2.2 消费者请求数据发送过程
proxy0#sayHello(String)
—> InvokerInvocationHandler#invoke(Object, Method, Object[])
—> MockClusterInvoker#invoke(Invocation)
—> AbstractClusterInvoker#invoke(Invocation)
—> FailoverClusterInvoker#doInvoke(Invocation, List<Invoker<T>>, LoadBalance)
—> Filter#invoke(Invoker, Invocation) // 包含多个 Filter 调用
—> ListenerInvokerWrapper#invoke(Invocation)
—> AbstractInvoker#invoke(Invocation)
—> DubboInvoker#doInvoke(Invocation)
—> ReferenceCountExchangeClient#request(Object, int)
—> HeaderExchangeClient#request(Object, int)
—> HeaderExchangeChannel#request(Object, int)
—> AbstractPeer#send(Object)
—> AbstractClient#send(Object, boolean)
—> NettyChannel#send(Object, boolean)
—> NioClientSocketChannel#write(Object)
2.2.1 协议
Dubbo 数据包结构
| 偏移量(Bit) | 字段 | 取值 |
|---|---|---|
| 0 ~ 7 | 魔数高位 | 0xda00 |
| 8 ~ 15 | 魔数低位 | 0xbb |
| 16 | 数据包类型 | 0 - Response, 1 - Request |
| 17 | 调用方式 | 仅在第16位被设为1的情况下有效,0 - 单向调用,1 - 双向调用 |
| 18 | 事件标识 | 0 - 当前数据包是请求或响应包,1 - 当前数据包是心跳包 |
| 19 ~ 23 | 序列化器编号 | 2 - Hessian2Serialization 3 - JavaSerialization 4 - CompactedJavaSerialization 6 - FastJsonSerialization 7 - NativeJavaSerialization 8 - KryoSerialization 9 - FstSerialization |
| 24 ~ 31 | 状态 | 20 - OK 30 - CLIENT_TIMEOUT 31 - SERVER_TIMEOUT 40 - BAD_REQUEST 50 - BAD_RESPONSE …… |
| 32 ~ 95 | 请求编号 | 共8字节,运行时生成 |
| 96 ~ 127 | 消息体长度 | 运行时计算 |
请求过程首先会通过位运算将消息头写入到 header 数组中。然后对 Request 对象的 data 字段执行序列化操作,序列化后的数据最终会存储到 ChannelBuffer 中。序列化操作执行完后,可得到数据序列化后的长度 len,紧接着将 len 写入到 header 指定位置处。最后再将消息头字节数组 header 写入到 ChannelBuffer 中,整个编码过程就结束了。
/**
* ExchangeCodec.
* 第1字节:
* 0 ~ 7 魔数高位 0xda00
* 第2字节:
* 8 ~ 15 魔数低位 0xbb
* 第3字节:
* 16 数据包类型 0 - Response, 1 - Request
* 17 调用方式 仅在第16位被设为1的情况下有效,0 - 单向调用,1 - 双向调用
* 18 事件标识 0 - 当前数据包是请求或响应包,1 - 当前数据包是心跳包
* 19 ~ 23 序列化器编号
* 2 - Hessian2Serialization
* 3 - JavaSerialization
* 4 - CompactedJavaSerialization
* 6 - FastJsonSerialization
* 7 - NativeJavaSerialization
* 8 - KryoSerialization
* 9 - FstSerialization
* 第4字节:
* 24 ~ 31 状态
* 20 - OK
* 30 - CLIENT_TIMEOUT
* 31 - SERVER_TIMEOUT
* 40 - BAD_REQUEST
* 50 - BAD_RESPONSE
* 第5-12字节:
* 32 ~ 95 请求编号 共8字节,运行时生成
* 第13-16字节:
* 96 ~ 127 消息体长度 运行时计算
*/
public class ExchangeCodec extends TelnetCodec {
protected void encodeRequest(Channel channel, ChannelBuffer buffer, Request req) throws IOException {
Serialization serialization = getSerialization(channel);
// header.
byte[] header = new byte[HEADER_LENGTH];
// set magic number.
Bytes.short2bytes(MAGIC, header);
// set request and serialization flag.
header[2] = (byte) (FLAG_REQUEST | serialization.getContentTypeId());
if (req.isTwoWay()) {
header[2] |= FLAG_TWOWAY;
}
if (req.isEvent()) {
header[2] |= FLAG_EVENT;
}
// set request id.
Bytes.long2bytes(req.getId(), header, 4);
// encode request data.
int savedWriteIndex = buffer.writerIndex();
// 更新 writerIndex,先预留头部的16个字节空间
buffer.writerIndex(savedWriteIndex + HEADER_LENGTH);
ChannelBufferOutputStream bos = new ChannelBufferOutputStream(buffer);
ObjectOutput out = serialization.serialize(channel.getUrl(), bos);
if (req.isEvent()) {
encodeEventData(channel, out, req.getData());
} else {
// 对请求数据进行序列化操作
encodeRequestData(channel, out, req.getData(), req.getVersion());
}
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);
// write
buffer.writerIndex(savedWriteIndex);
// 写入头信息
buffer.writeBytes(header); // write header.
buffer.writerIndex(savedWriteIndex + HEADER_LENGTH + len);
}
}
2.2.2 序列化
我们再来看一下 Request 对象的 data 字段序列化过程,也就是 encodeRequestData 方法的逻辑,如下:
public class DubboCodec extends ExchangeCodec implements Codec2 {
protected void encodeRequestData(Channel channel, ObjectOutput out, Object data, String version) throws IOException {
RpcInvocation inv = (RpcInvocation) data;
// 依次序列化 dubbo version、path、version
out.writeUTF(version);
out.writeUTF(inv.getAttachment(Constants.PATH_KEY));
out.writeUTF(inv.getAttachment(Constants.VERSION_KEY));
// 序列化调用方法名
out.writeUTF(inv.getMethodName());
// 将参数类型转换为字符串,并进行序列化
out.writeUTF(ReflectUtils.getDesc(inv.getParameterTypes()));
Object[] args = inv.getArguments();
if (args != null)
for (int i = 0; i < args.length; i++) {
// 对运行时参数进行序列化
out.writeObject(encodeInvocationArgument(channel, inv, i));
}
// 序列化 attachments
out.writeObject(inv.getAttachments());
}
}
2.3 提供者数据接受过程
3.总结
TODO
本文深入探讨了Dubbo服务调用的过程,从消费者请求的发送,包括代理调用、过滤器处理、序列化,到提供者的接收。详细解析了协议构造、序列化操作,并对数据发送的各个环节进行了拆解。
2235

被折叠的 条评论
为什么被折叠?



