我理解的RocketMQ:网络层的通信协议

1 概述

如下图所示,业务层与Netty客户端之间用RemotingCommand进行交互,即业务层调用Netty发送消息时,会将消息封装在RemotingCommand对象里面,而Netty接收到外部消息的时候会给业务层返回RemotingCommand的对象实例。

Netty与外部世界通过字节流进行传输。Netty在发送消息的时候,对RemotingCommand进行编码(对象–>字节流);在接收到外部消息的时候会对字节流进行解码(字节流–>对象)。

在这里插入图片描述

业务层与Netty之间交互方式的伪代码:

  • 准备消息头,CommandCustomHeader,“通信协议”,不同场景下,例如发送消息和请求消息,携带的信息不一样。约定好的协议。
  • 准备消息体,body
  • 使用消息头和消息体创建RemotingCommand对象
  • 调用Netty进行发送
----经过业务层构建处理的消息体Header/body----
header = ...;
body = ...;
----准备RemotingCommand----
remotingCommand = new RemotingCommand();
remotingCommand.setCustomerHeader(header);
remotingCommand.setBody(body);
----调用Netty发送RemotingCommand----
Netty.send(remotingCommand);

2 编码

Netty会用NettyEncoder对对象进行编码。该类的源码如下,它继承了Netty中的MessageToByteEncoder。覆写了encode()方法。它的泛型变量为RemotingCommand表示它只处理传递过来的RemotingCommand的对象。

它里面的编码逻辑,首先对消息头进行编码,然后编码消息体。消息体其实已经是字节流了,直接写入ByteBuf中即可。而需要对消息头进行编码。对消息头编码时调用的是RemotingCommandencodeHeader()方法。

public class NettyEncoder extends MessageToByteEncoder<RemotingCommand> {
    private static final InternalLogger log = InternalLoggerFactory.getLogger(RemotingHelper.ROCKETMQ_REMOTING);

    @Override
    public void encode(ChannelHandlerContext ctx, RemotingCommand remotingCommand, ByteBuf out)
            throws Exception {
        try {
            // 编码头部
            ByteBuffer header = remotingCommand.encodeHeader();
            out.writeBytes(header);
            // 之前已经编码好了,即已经是二进制了
            byte[] body = remotingCommand.getBody();
            if (body != null) {
                out.writeBytes(body);
            }
        } catch (Exception e) {
            log.error("encode exception, " + RemotingHelper.parseChannelRemoteAddr(ctx.channel()), e);
            if (remotingCommand != null) {
                log.error(remotingCommand.toString());
            }
            RemotingUtil.closeChannel(ctx.channel());
        }
    }
}

3 解码

如下是NettyDecoder的源代码,它继承了LengthFieldBasedFrameDecoder,表明它解决TCP粘包和拆包的方式为长度基础方式。

里面的逻辑很简单,从网络通道读取到数据,然后调用RemotingCommand的静态解码方法decode进行解码,返回一个RemotingCommand对象。

public class NettyDecoder extends LengthFieldBasedFrameDecoder {
    private static final InternalLogger log = InternalLoggerFactory.getLogger(RemotingHelper.ROCKETMQ_REMOTING);
    private static final int FRAME_MAX_LENGTH =
        Integer.parseInt(System.getProperty("com.rocketmq.remoting.frameMaxLength", "16777216"));
    public NettyDecoder() {
        super(FRAME_MAX_LENGTH, 0, 4, 0, 4);
    }
    @Override
    public Object decode(ChannelHandlerContext ctx, ByteBuf in) throws Exception {
        ByteBuf frame = null;
        try {
            frame = (ByteBuf) super.decode(ctx, in);
            if (null == frame) {
                return null;
            }

            ByteBuffer byteBuffer = frame.nioBuffer();
            // 直接调用RemotingCommand进行解码
            return RemotingCommand.decode(byteBuffer);
        } catch (Exception e) {
            log.error("decode exception, " + RemotingHelper.parseChannelRemoteAddr(ctx.channel()), e);
            RemotingUtil.closeChannel(ctx.channel());
        } finally {
            if (null != frame) {
                frame.release();
            }
        }
        return null;
    }
}

4 RemotingCommand

RemotingCommand的成员属性。其中customeHeader表示消息头,body表示消息体,它是不进行序列化。

public class RemotingCommand {
    // 请求码
    private int code;
    private LanguageCode language = LanguageCode.JAVA;
    // 版本
    private int version = 0;
    // 递增
    private int opaque = requestId.getAndIncrement();
    private int flag = 0;
    private String remark;
    private HashMap<String, String> extFields;

    // 消息头
    private transient CommandCustomHeader customHeader;
    private SerializeType serializeTypeCurrentRPC = serializeTypeConfigInThisServer;

    // 不进行序列化
    // 消息体
    private transient byte[] body;
}

4.1 编码消息头

最终结果就是将RemotingCommand对象进行序列化,而customeHeader的属性以及值会被放到RemotinCommand的extFields之中,之后随RemotingCommand一起序列化成字节码。

首先确定消息体的长度。

public ByteBuffer encodeHeader() {
    return encodeHeader(this.body != null ? this.body.length : 0);
}
public ByteBuffer encodeHeader(final int bodyLength) {
    // 1> header length size
    int length = 4;
    // 2> header data length
    byte[] headerData;

    // 【!!!】真正的编码
    headerData = this.headerEncode();

    length += headerData.length;
    // 3> body data length
    length += bodyLength;
    ByteBuffer result = ByteBuffer.allocate(4 + length - bodyLength);
    // length
    result.putInt(length);
    // header length
    result.put(markProtocolType(headerData.length, serializeTypeCurrentRPC));
    // header data
    result.put(headerData);
    result.flip();
    return result;
}

首先将customeHeader中的属性放到RemotingCommand的属性extFields之中,是一个map<fildName,fieldValue>

然后根据设置序列的方式采用不同的编码方式,其实就是序列化RemotingCommand对象本身。主要是RocketMQ自定义的协议,或者采用json的方式。

private byte[] headerEncode() {
    // 【!!!!!】提取customHeader的成员变量
    // 放到RemotingCommand的extFieldsmap之中
    this.makeCustomHeaderToNet();

    // 使用不同的协议序列化RemotingCommand对象
    if (SerializeType.ROCKETMQ == serializeTypeCurrentRPC) {
        return RocketMQSerializable.rocketMQProtocolEncode(this);
    } else {
        return RemotingSerializable.encode(this);
    }
}

4.2 解码

最终结果是就是将字节码还原为一个RemotingCommand对象。并设置customeHeaderbody的值

public static RemotingCommand decode(final byte[] array) {
    ByteBuffer byteBuffer = ByteBuffer.wrap(array);
    return decode(byteBuffer);
}
public static RemotingCommand decode(final ByteBuffer byteBuffer) {
    int length = byteBuffer.limit();
    int oriHeaderLen = byteBuffer.getInt();
    int headerLength = getHeaderLength(oriHeaderLen);

    // 读取头部数据
    byte[] headerData = new byte[headerLength];
    byteBuffer.get(headerData);

    // 【!!!!】解码头部并达到RemotingCommand
    RemotingCommand cmd = headerDecode(headerData, getProtocolType(oriHeaderLen));

    int bodyLength = length - 4 - headerLength;
    byte[] bodyData = null;
    if (bodyLength > 0) {
        // 获取到body数据,至于body数据的解码放在了后面进行
        bodyData = new byte[bodyLength];
        byteBuffer.get(bodyData);
    }
    cmd.body = bodyData;

    return cmd;
}
private static RemotingCommand headerDecode(byte[] headerData, SerializeType type) {
    switch (type) {
        // 调用相应的序列化方式进行解码
        case JSON:
            // 还原为RemotingCommand对象
            RemotingCommand resultJson = RemotingSerializable.decode(headerData, RemotingCommand.class);
            resultJson.setSerializeTypeCurrentRPC(type);
            return resultJson;
        case ROCKETMQ:
            RemotingCommand resultRMQ = RocketMQSerializable.rocketMQProtocolDecode(headerData);
            resultRMQ.setSerializeTypeCurrentRPC(type);
            return resultRMQ;
        default:
            break;
    }

    return null;
}

5 通信协议

在传递消息的时候会创建一个协议头customeHeader,不同场景下会使用不同的协议头。

协议头都实现了一个公共的接口CommandCustomHeader。它的实现者有很多。

public interface CommandCustomHeader {
    void checkFields() throws RemotingCommandException;
}

在这里插入图片描述

已标记关键词 清除标记
©️2020 CSDN 皮肤主题: 大白 设计师:CSDN官方博客 返回首页