最新源码分析Dubbo编码解码实现原理---Dubbo协议编码(1),Java面试心得必备技能储备详解

Java核心架构进阶知识点

面试成功其实都是必然发生的事情,因为在此之前我做足了充分的准备工作,不单单是纯粹的刷题,更多的还会去刷一些Java核心架构进阶知识点,比如:JVM、高并发、多线程、缓存、Spring相关、分布式、微服务、RPC、网络、设计模式、MQ、Redis、MySQL、设计模式、负载均衡、算法、数据结构、kafka、ZK、集群等。而这些也全被整理浓缩到了一份pdf——《Java核心架构进阶知识点整理》,全部都是精华中的精华,本着共赢的心态,好东西自然也是要分享的

image

image

image

内容颇多,篇幅却有限,这就不在过多的介绍了,大家可根据以上截图自行脑补

本文已被CODING开源项目:【一线大厂Java面试题解析+核心总结学习笔记+最新讲解视频+实战项目源码】收录

需要这份系统化的资料的朋友,可以点击这里获取

header[2] = (byte) (FLAG_REQUEST | serialization.getContentTypeId()); // @4

if (req.isTwoWay()) header[2] |= FLAG_TWOWAY;

if (req.isEvent()) header[2] |= FLAG_EVENT;

// set request id.

Bytes.long2bytes(req.getId(), header, 4); // @5

Step1:初始化协议头,同时填充部分字段。header[0]、header[1]、header[2]、header[4-11],注意,header[3]未填充。

代码@1:获取通道的序列化实现类。

代码@2:构建请求头部,header数组,长度为16个字节。

代码@3:首先填充头部的前两个字节,协议的魔数。header[0] = 魔数的高8个字节,header[1] = 魔数的低8个字节。

代码@4:头部的第3个字节存储的是消息请求标识与序列化器类别,那这8位是如何存储的呢?

首先看一下消息请求标志的定义:

protected static final byte FLAG_REQUEST = (byte) 0x80; // 其二进制为 1000 0000

protected static final byte FLAG_TWOWAY = (byte) 0x40; // 其二进制为 0100 0000

protected static final byte FLAG_EVENT = (byte) 0x20; // 其二进制为 0010 0000

protected static final int SERIALIZATION_MASK = 0x1f; // 其序列化的掩码,为什么是这样的呢?

serialization.getContentTypeId() 返回的类型如下:

CompactedJavaSerialization : 4 二进制为0000 0010

FastJsonSerialization : 6 二进制为0000 0110

FstSerialization : 9 二进制为0000 1001

Hessian2Serialization : 2 二进制为0000 0010

JavaSerialization : 3 二进制为0000 0011

KryoSerialization : 8 二进制为0000 1000

NativeJavaSerialization : 7 二进制为0000 0111

结合代码header[2] = (byte) (FLAG_REQUEST | serialization.getContentTypeId()) 可以得出一个结论,header[2]为8字节标志位,前4位,表示消息请求类型,依次为:请求、twoway、event,保留位。后4为:序列化的类型,也就是说dubbo协议只支持16中序列化协议。

代码@5:head[4]- head[11] 共8个字节为请求ID。Dubbo传输使用大端字节序列,也就说在接受端,首先读到的字节是高位字节。

public static void long2bytes(long v, byte[] b, int off) {

b[off + 7] = (byte) v;

b[off + 6] = (byte) (v >>> 8);

b[off + 5] = (byte) (v >>> 16);

b[off + 4] = (byte) (v >>> 24);

b[off + 3] = (byte) (v >>> 32);

b[off + 2] = (byte) (v >>> 40);

b[off + 1] = (byte) (v >>> 48);

b[off + 0] = (byte) (v >>> 56);

}

ExchangeCodec#encodeRequest

//encode request data.

int savedWriteIndex = buffer.writerIndex();

buffer.writerIndex(savedWriteIndex + HEADER_LENGTH);

ChannelBufferOutputStream bos = new ChannelBufferOutputStream(buffer); // @1

ObjectOutput out = serialization.serialize(channel.getUrl(), bos); // @2

if (req.isEvent()) { // @3

encodeEventData(channel, out, req.getData());

} else {

encodeRequestData(channel, out, req.getData());

}

out.flushBuffer();

if (out instanceof Cleanable) {

((Cleanable) out).cleanup();

}

bos.flush();

bos.close();

int len = bos.writtenBytes(); //@4

checkPayload(channel, len);

Bytes.int2bytes(len, header, 12); //@5

Step2:编码请求体(body),协议的设计,一般是基于 请求头部+请求体构成。

代码@1:对buffer做一个简单封装,返回ChannelBufferOutputStream实例。

代码@2:根据序列化器,将通道的URL进行序列化,变存入buffer中。

代码@3:根据请求类型,事件或请求对Request.getData()请求体进行编码,encodeEventData、encodeRequestData不同的编码器会重写该方法,下文详细看一下DubboCode的实现。

代码@4:最后得到bos的总长度,该长度等于 (header+body)的总长度,也就是一个完整请求包的长度。

代码@5:将包总长度写入到header的header[12-15]中。

从ExchangeCodec#encodeRequest这个方法可以得知,Dubbo的整体传输协议由下图所示:

这里写图片描述

2.3 Dubbo协议体编码

2.3.1 Dubbo协议请求体(body)编码规则

在ExchangeCodec#encodeRequest中,将会调用encodeRequestData对body进行编码

protected void encodeRequestData(Channel channel, ObjectOutput out, Object data) throws IOException {

RpcInvocation inv = (RpcInvocation) data;

out.writeUTF(inv.getAttachment(Constants.DUBBO_VERSION_KEY, DUBBO_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));

}

out.writeObject(inv.getAttachments());

}

该方法,依次将 dubbo、服务path(interface name)、版本号、方法名、方法参数类型描述,参数值、附加属性(例如参数回调等,该部分会在服务调用相关章节重点分析)。上述内容,根据不同的序列化实现,其组织方式不同,当然,其基本组织方式(标记位、长度 、 具体内容),将在下节中重点分析序列化的实现。

2.3.2 Dubbo响应数据包编码规则

protected void encodeResponseData(Channel channel, ObjectOutput out, Object data) throws IOException {

Result result = (Result) data;

Throwable th = result.getException();

if (th == null) {

Object ret = result.getValue();

if (ret == null) {

out.writeByte(RESPONSE_NULL_VALUE);

} else {

out.writeByte(RESPONSE_VALUE);

out.writeObject(ret);

}

} else {

out.writeByte(RESPONSE_WITH_EXCEPTION);

out.writeObject(th);

}

}

1字节(请求结果),取值:RESPONSE_NULL_VALUE:表示空结果;RESPONSE_WITH_EXCEPTION:表示异常,RESPONSE_VALUE:正常响应。N字节的请求响应,使用readObject读取即可。

3、ExchangeCodec解码实现原理

ExchangeCodec#decode

public Object decode(Channel channel, ChannelBuffer buffer) throws IOException {

int readable = buffer.readableBytes();

byte[] header = new byte[Math.min(readable, HEADER_LENGTH)]; // @1

buffer.readBytes(header); // @2

return decode(channel, buffer, readable, header); // @3

}

代码@1:创建一个byte数组,其长度为 头部长度和可读字节数取最小值。

代码@2:读取指定字节到header中。

代码@3:调用decode方法尝试解码。

ExchangeCodec#decode

protected Object decode(Channel channel, ChannelBuffer buffer, int readable, byte[] header)

Step1:解释一下方法的参数:

  • Channel channel :网络通道

  • ChannelBuffer buffer : 通道读缓存区

  • int readable :可读字节数。

  • byte[] header :已读字节数,(尝试读取一个完整头部)

ExchangeCodec#decode

// check magic number.

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;

}

}

return super.decode(channel, buffer, readable, header);

}

Step2:检查魔数,判断是否是dubbo协议,如果不是dubbo协议,则调用父类的解码方法,例如telnet协议。

如果至少读取到一个字节,如果第一个字节与魔数的高位字节不相等或至少读取了两个字节,并且第二个字节与魔数的地位字节不相等,则认为不是   dubbo协议,则调用父类的解码方法,如果是其他协议的化,将剩余的可读字节从通道中读出,提交其父类解码。

ExchangeCodec#decode

// check length.

if (readable < HEADER_LENGTH) {

return DecodeResult.NEED_MORE_INPUT;

}

Step3:如果是dubbo协议,判断可读字节的长度是否大于协议头部的长度,如果可读字节小于头部字节,则跳过本次读事件处理,待读缓存区中更多的数据到达。

ExchangeCodec#decode

// get data length.

int len = Bytes.bytes2int(header, 12);

checkPayload(channel, len);

int tt = len + HEADER_LENGTH;

if (readable < tt) {

return DecodeResult.NEED_MORE_INPUT;

}

Step4:如果读取到一个完整的协议头,然后读取消息体长度,如果当前可读自己小于消息体+header的长度,返回NEED_MORE_INPUT,表示放弃本次解码,待更多数据到达缓冲区时再解码。

ExchangeCodec#decode

// limit input stream.

ChannelBufferInputStream is = new ChannelBufferInputStream(buffer, len); // @1

try {

return decodeBody(channel, is, header); // @2

} finally {

if (is.available() > 0) {

try {

if (logger.isWarnEnabled()) {

logger.warn("Skip input stream " + is.available());

}

StreamUtils.skipUnusedStream(is); // @3

} catch (IOException e) {

logger.warn(e.getMessage(), e);

}

}

}

代码@1:创建一个ChannelBufferInputStream,并限制最多只读取len长度的字节。

代码@2:调用decodeBody方法解码协议体。

代码@3:如果本次并未读取len个字节,则跳过这些字节,保证下一个包从正确的位置开始处理。

这个其实就是典型的网络编程(自定义协议)的解码实现。

由于本文只关注Dubbo协议的解码,故decodeBody方法的实现,请看DubboCodec#decodeBody。

3.1 DubboCodec#decodeBody 详解

byte flag = header[2], proto = (byte) (flag & SERIALIZATION_MASK);

Serialization s = CodecSupport.getSerialization(channel.getUrl(), proto);

// get request id.

long id = Bytes.bytes2long(header, 4);

Step1:根据协议头获取标记为(header[2])(根据协议可知,包含请求类型、序列化器)。

DubboCodec#decodeBody

总结

在清楚了各个大厂的面试重点之后,就能很好的提高你刷题以及面试准备的效率,接下来小编也为大家准备了最新的互联网大厂资料。

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

本文已被CODING开源项目:【一线大厂Java面试题解析+核心总结学习笔记+最新讲解视频+实战项目源码】收录

需要这份系统化的资料的朋友,可以点击这里获取

可知,包含请求类型、序列化器)。

DubboCodec#decodeBody

总结

在清楚了各个大厂的面试重点之后,就能很好的提高你刷题以及面试准备的效率,接下来小编也为大家准备了最新的互联网大厂资料。

[外链图片转存中…(img-nsPEUqUs-1715677003256)]

[外链图片转存中…(img-REgtJALr-1715677003257)]

[外链图片转存中…(img-WvwEW9Ca-1715677003257)]

[外链图片转存中…(img-LMBZtdfJ-1715677003258)]

本文已被CODING开源项目:【一线大厂Java面试题解析+核心总结学习笔记+最新讲解视频+实战项目源码】收录

需要这份系统化的资料的朋友,可以点击这里获取

  • 18
    点赞
  • 18
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值