rocketmq怎么做序列化的?
首先看一下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;
除了static之外,还有body、extfields是transitent,除此之外都是要直接进行序列化的,默认用fastjson直接序列化。
这里面的extfields跟customHeader是互相转换的,也就是序列化的时候用前者传入,在代码里面用反序列化后的customer对象做操作。customHeader是一个接口,他有很多实现类,不同的request请求体除了body不同之外,还有一个不同就在于customHeader不同。
比如注册broker的:
final RegisterBrokerRequestHeader requestHeader = new RegisterBrokerRequestHeader(); requestHeader.setBrokerAddr(brokerAddr); requestHeader.setBrokerId(brokerId); requestHeader.setBrokerName(brokerName); requestHeader.setClusterName(clusterName); requestHeader.setHaServerAddr(haServerAddr); requestHeader.setCompressed(compressed); RegisterBrokerBody requestBody = new RegisterBrokerBody(); requestBody.setTopicConfigSerializeWrapper(topicConfigWrapper); requestBody.setFilterServerList(filterServerList); final byte[] body = requestBody.encode(compressed); final int bodyCrc32 = UtilAll.crc32(body);
这里的requestHeader属于RegisterBrokerRequestHeader,就是一种特殊的commandheader,他有自己独特的属性,比如brokeraddr、brokerId等等。统统塞入header里面。
再构造需要的body体,把header跟序列化后的body一起:
RemotingCommand request = RemotingCommand.createRequestCommand(RequestCode.REGISTER_BROKER, requestHeader); request.setBody(body);
public static RemotingCommand createRequestCommand(int code, CommandCustomHeader customHeader) { RemotingCommand cmd = new RemotingCommand(); cmd.setCode(code); cmd.customHeader = customHeader; setCmdVersion(cmd); return cmd; }
把header、body都放入RemotingCommand里面,回到command的属性,只有code、version、remark、opaque才是通用的属性,其他统统通过header、body实现个性化。对于注册broker来说,个性化的body就是注册的具体内容。
拿到一个完整的command以后,就是如何在netty里面进行序列化和反序列化了,这里面的body已经被序列化了,还剩下通用属性和header属性。
回到encode方法:
@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()); } }
简单来看就是header跟body一起放入out里面。进入encodeHeader方法:
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; }
使用LengthFieldBasedFrameDecoder的时候,第一个put的数据就是总体长度:所有header(包括公共的和extrafields的)+ body+markprotocol得到的长度(所有header长度),这里的总体长度不需要包括总体长度本身,也就是putint(length)本身不需要额外算4个字节,这里面多出来的4是由于markprotocol的长度。
private byte[] headerEncode() { this.makeCustomHeaderToNet(); if (SerializeType.ROCKETMQ == serializeTypeCurrentRPC) { return RocketMQSerializable.rocketMQProtocolEncode(this); } else { return RemotingSerializable.encode(this); } }
在makeCustomHeaderToNet方法里面,通过反射把header的内容全部放入extrafields里面,这样再通过encode(this)就可以直接把header的内容(已经转换成extrafields)和公共属性全部序列化成result。
再看看反序列化:
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 cmd = headerDecode(headerData, getProtocolType(oriHeaderLen)); int bodyLength = length - 4 - headerLength; byte[] bodyData = null; if (bodyLength > 0) { bodyData = new byte[bodyLength]; byteBuffer.get(bodyData); } cmd.body = bodyData; return cmd; }
虽然我们在encode的时候塞入了总体length,但是LengthFieldBasedFrameDecoder已经帮我们解析了去掉了,我们第一个取数据的时候是后面的markeprotocolType得到的header长度,
根据header长度以后拿到header,他就是除了body之外所有的数据。body可以通过后面的buff拿到,header反序列化后得到的extrafields可以进一步转换成commandHeader,由于在公共属性里面我们塞入了code,于是我们可以知道应该转换成具体哪一种commandHeader。