1 概述
当网络传输使用 Netty 时,实际上是把请求转换成任务并投递到异步队列中,Netty会使用I/O线程去异步执行该任务。上述所说的异步队列指 NettyClient 对应的 Channel 管理的异步队列。
在 Netty 执行异步任务时,会通过 DubboCodec 对请求信息进行编码,此处即服务消费方对请求信息进行编码的地方。同理,服务提供方也通过 DubboCodec 对响应信息进行编码。
由上可知,Dubbo的编码主要指服务消费方对请求信息进行编码和服务提供方对响应信息进行编码。而Dubbo 的编码过程实际上包含了两部分内容:设置 Dubbo 协议头(header) 的内容,以及对协议 body 数据进行编码。
2 编码原理解析
编码的入口为 DubboCodec 的父类 ExchangeCodec 的 encode 方法,代码如下所示。
@Override
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);
}
}
2.1 对请求信息进行编码
上述代码中的 encodeRequest 即服务消费方对请求信息进行编码,具体实现如下所示。
protected void encodeRequest(Channel channel, ChannelBuffer buffer, Request req) throws IOException {
Serialization serialization = getSerialization(channel, req);
// header. -16个字节
byte[] header = new byte[HEADER_LENGTH];
// 1、set magic number.
Bytes.short2bytes(MAGIC, header);
// 2、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;
}
// 3 请求数据不用设置响应结果码
// 4、set request id.
Bytes.long2bytes(req.getId(), header, 4);
// encode request data.
int savedWriteIndex = buffer.writerIndex();
buffer.writerIndex(savedWriteIndex + HEADER_LENGTH);
ChannelBufferOutputStream bos = new ChannelBufferOutputStream(buffer);
if (req.isHeartbeat()) {
// heartbeat request data is always null
bos.write(CodecSupport.getNullBytesOf(serialization));
} else {
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();
// 检查协议body(即协议数据)大小是否超过设置的限定值(payload-默认8M)
checkPayload(channel, req.getPayload(), len);
// 5、在header中设置协议body的大小(最后四个字节)
Bytes.int2bytes(len, header, 12);
// write
buffer.writerIndex(savedWriteIndex);
buffer.writeBytes(header); // write header.
buffer.writerIndex(savedWriteIndex + HEADER_LENGTH + len);
}
通过上述方法设置了 Dubbo 协议头(header)的内容,以及对请求数据进行了编码。
通过获取到的数据序列化方式对请求数据进行编码,其中默认的序列化方式为 hessian2(即 Hessian2Serialization),最终的获取方法如下所示。
public class DefaultSerializationSelector {
private final static String DEFAULT_REMOTING_SERIALIZATION_PROPERTY_KEY = "DUBBO_DEFAULT_SERIALIZATION";
private final static String DEFAULT_REMOTING_SERIALIZATION_PROPERTY = "hessian2";
private final static String DEFAULT_REMOTING_SERIALIZATION;
static {
String fromProperty = System.getProperty(DEFAULT_REMOTING_SERIALIZATION_PROPERTY_KEY);
if (fromProperty != null) {
DEFAULT_REMOTING_SERIALIZATION = fromProperty;
} else {
String fromEnv = System.getenv(DEFAULT_REMOTING_SERIALIZATION_PROPERTY_KEY);
if (fromEnv != null) {
DEFAULT_REMOTING_SERIALIZATION = fromEnv;
} else {
DEFAULT_REMOTING_SERIALIZATION = DEFAULT_REMOTING_SERIALIZATION_PROPERTY;
}
}
}
public static String getDefaultRemotingSerialization() {
return DEFAULT_REMOTING_SERIALIZATION;
}
}
对请求数据进行编码的实现是通过 DubboCodec 的 encodeRequestData 方法 ,具体如下所示。
protected void encodeRequestData(Channel channel, ObjectOutput out, Object data, String version) throws IOException {
RpcInvocation inv = (RpcInvocation) data;
out.writeUTF(version);
// https://github.com/apache/dubbo/issues/6138
String serviceName = inv.getAttachment(INTERFACE_KEY);
if (serviceName == null) {
serviceName = inv.getAttachment(PATH_KEY);
}
out.writeUTF(serviceName);
out.writeUTF(inv.getAttachment(VERSION_KEY));
out.writeUTF(inv.getMethodName());
out.writeUTF(inv.getParameterTypesDesc());
Object[] args = inv.getArguments();
if (args != null) {
for (int i = 0; i < args.length; i++) {
out.writeObject(callbackServiceCodec.encodeInvocationArgument(channel, inv, i));
}
}
out.writeAttachments(inv.getObjectAttachments());
}
2.2 对响应信息进行编码
上述代码中的 encodeResponse 即服务提供方对响应信息进行编码,具体实现如下所示。
protected void encodeResponse(Channel channel, ChannelBuffer buffer, Response res) throws IOException {
int savedWriteIndex = buffer.writerIndex();
try {
Serialization serialization = getSerialization(channel, res);
// header.
byte[] header = new byte[HEADER_LENGTH];
// 1、set magic number.
Bytes.short2bytes(MAGIC, header);
// 2、set request and serialization flag.
header[2] = serialization.getContentTypeId();
if (res.isHeartbeat()) {
header[2] |= FLAG_EVENT;
}
// 3、set response status.
byte status = res.getStatus();
header[3] = status;
// 4、set request id.
Bytes.long2bytes(res.getId(), header, 4);
buffer.writerIndex(savedWriteIndex + HEADER_LENGTH);
ChannelBufferOutputStream bos = new ChannelBufferOutputStream(buffer);
// encode response data or error message.
if (status == Response.OK) {
if (res.isHeartbeat()) {
// heartbeat response data is always null
bos.write(CodecSupport.getNullBytesOf(serialization));
} else {
ObjectOutput out = serialization.serialize(channel.getUrl(), bos);
if (res.isEvent()) {
encodeEventData(channel, out, res.getResult());
} else {
encodeResponseData(channel, out, res.getResult(), res.getVersion());
}
out.flushBuffer();
if (out instanceof Cleanable) {
((Cleanable) out).cleanup();
}
}
} else {
ObjectOutput out = serialization.serialize(channel.getUrl(), bos);
out.writeUTF(res.getErrorMessage());
out.flushBuffer();
if (out instanceof Cleanable) {
((Cleanable) out).cleanup();
}
}
bos.flush();
bos.close();
int len = bos.writtenBytes();
// 检查协议body(即协议数据)大小是否超过设置的限定值(payload-默认8M)
checkPayload(channel, len);
// 5、设置协议body-响应数据的大小
Bytes.int2bytes(len, header, 12);
// write
buffer.writerIndex(savedWriteIndex);
buffer.writeBytes(header); // write header.
buffer.writerIndex(savedWriteIndex + HEADER_LENGTH + len);
} catch (Throwable t) {
...
}
}
通过上述方法设置了 Dubbo 协议头(header)的内容,以及对响应数据进行了编码。
对响应数据进行编码的实现是通过 DubboCodec 的 encodeResponseData 方法 ,具体如下所示。
protected void encodeResponseData(Channel channel, ObjectOutput out, Object data, String version) throws IOException {
Result result = (Result) data;
// currently, the version value in Response records the version of Request
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.writeThrowable(th);
}
if (attach) {
// returns current version of Response to consumer side.
result.getObjectAttachments().put(DUBBO_VERSION_KEY, Version.getProtocolVersion());
out.writeAttachments(result.getObjectAttachments());
}
}
3 总结
由上可知,不管是服务消费方对请求信息进行编码,还是服务提供方对响应信息进行编码,Dubbo的编码过程本质上就是将要发送的数据封装为 Dubbo 协议帧,并对数据进行序列化处理。
本文详细解析了Netty在处理网络传输时如何通过DubboCodec进行请求和响应的编码,包括设置Dubbo协议头、序列化过程,以及服务消费方和服务提供方在请求和响应编码中的角色和操作。
1397

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



