Skip to content

Commit

Permalink
feature: multi-version seata protocol support (#6226)
Browse files Browse the repository at this point in the history
  • Loading branch information
Bughue authored Jun 27, 2024
1 parent 699891d commit 08eb0e6
Show file tree
Hide file tree
Showing 20 changed files with 734 additions and 138 deletions.
10 changes: 10 additions & 0 deletions core/src/main/java/org/apache/seata/core/protocol/RpcMessage.java
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,8 @@ public class RpcMessage implements Serializable {
private Map<String, String> headMap = new HashMap<>();
private Object body;

private String otherSideVersion;

/**
* Gets id.
*
Expand Down Expand Up @@ -169,6 +171,14 @@ public void setMessageType(byte messageType) {
this.messageType = messageType;
}

public String getOtherSideVersion() {
return otherSideVersion;
}

public void setOtherSideVersion(String otherSideVersion) {
this.otherSideVersion = otherSideVersion;
}

@Override
public String toString() {
return StringUtils.toString(this);
Expand Down
10 changes: 10 additions & 0 deletions core/src/main/java/org/apache/seata/core/protocol/Version.java
Original file line number Diff line number Diff line change
Expand Up @@ -150,6 +150,16 @@ public static long convertVersionNotThrowException(String version) {
return -1;
}

public static byte calcProtocolVersion(String sdkVersion) throws IncompatibleVersionException {
long version = convertVersion(sdkVersion);
long v0 = convertVersion(VERSION_0_7_1);
if (version <= v0) {
return ProtocolConstants.VERSION_0;
} else {
return ProtocolConstants.VERSION_1;
}
}

private static long calculatePartValue(String partNumeric, int size, int index) {
return Long.parseLong(partNumeric) * Double.valueOf(Math.pow(100, size - index)).longValue();
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@ public Object sendSyncRequest(String resourceId, String clientId, Object msg, bo
if (channel == null) {
throw new RuntimeException("rm client is not connected. dbkey:" + resourceId + ",clientId:" + clientId);
}
RpcMessage rpcMessage = buildRequestMessage(msg, ProtocolConstants.MSGTYPE_RESQUEST_SYNC);
RpcMessage rpcMessage = buildRequestMessage(channel, msg, ProtocolConstants.MSGTYPE_RESQUEST_SYNC);
return super.sendSync(channel, rpcMessage, NettyServerConfig.getRpcRequestTimeout());
}

Expand All @@ -78,7 +78,7 @@ public Object sendSyncRequest(Channel channel, Object msg) throws TimeoutExcepti
if (channel == null) {
throw new RuntimeException("client is not connected");
}
RpcMessage rpcMessage = buildRequestMessage(msg, ProtocolConstants.MSGTYPE_RESQUEST_SYNC);
RpcMessage rpcMessage = buildRequestMessage(channel, msg, ProtocolConstants.MSGTYPE_RESQUEST_SYNC);
return super.sendSync(channel, rpcMessage, NettyServerConfig.getRpcRequestTimeout());
}

Expand All @@ -87,26 +87,42 @@ public void sendAsyncRequest(Channel channel, Object msg) {
if (channel == null) {
throw new RuntimeException("client is not connected");
}
RpcMessage rpcMessage = buildRequestMessage(msg, ProtocolConstants.MSGTYPE_RESQUEST_ONEWAY);
RpcMessage rpcMessage = buildRequestMessage(channel, msg, ProtocolConstants.MSGTYPE_RESQUEST_ONEWAY);
super.sendAsync(channel, rpcMessage);
}

@Override
public void sendAsyncResponse(RpcMessage rpcMessage, Channel channel, Object msg) {
final Channel clientChannel = msg instanceof HeartbeatMessage
? channel
: ChannelManager.getSameClientChannel(channel);

if (clientChannel == null) {
throw new RuntimeException("Not found client channel to response | channel: " + channel);
Channel clientChannel = channel;
if (!(msg instanceof HeartbeatMessage)) {
clientChannel = ChannelManager.getSameClientChannel(channel);
}

RpcMessage rpcMsg = buildResponseMessage(rpcMessage, msg, msg instanceof HeartbeatMessage
if (clientChannel != null) {
RpcMessage rpcMsg = buildResponseMessage(channel, rpcMessage, msg, msg instanceof HeartbeatMessage
? ProtocolConstants.MSGTYPE_HEARTBEAT_RESPONSE
: ProtocolConstants.MSGTYPE_RESPONSE);
super.sendAsync(clientChannel, rpcMsg);
super.sendAsync(clientChannel, rpcMsg);
} else {
throw new RuntimeException("channel is error.");
}
}


private RpcMessage buildResponseMessage(Channel channel, RpcMessage fromRpcMessage, Object msg, byte messageType) {
RpcMessage rpcMessage = super.buildResponseMessage(fromRpcMessage, msg, messageType);
RpcContext rpcContext = ChannelManager.getContextFromIdentified(channel);
rpcMessage.setOtherSideVersion(rpcContext.getVersion());
return rpcMessage;
}

protected RpcMessage buildRequestMessage(Channel channel, Object msg, byte messageType) {
RpcMessage rpcMessage = super.buildRequestMessage(msg, messageType);
RpcContext rpcContext = ChannelManager.getContextFromIdentified(channel);
rpcMessage.setOtherSideVersion(rpcContext.getVersion());
return rpcMessage;
}


@Override
public void registerProcessor(int messageType, RemotingProcessor processor, ExecutorService executor) {
Pair<RemotingProcessor, ExecutorService> pair = new Pair<>(processor, executor);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,155 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.seata.core.rpc.netty;

import com.google.common.collect.ImmutableMap;
import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelHandlerContext;
import io.netty.handler.codec.LengthFieldBasedFrameDecoder;
import org.apache.seata.core.exception.DecodeException;
import org.apache.seata.core.protocol.ProtocolConstants;
import org.apache.seata.core.rpc.netty.v0.ProtocolDecoderV0;
import org.apache.seata.core.rpc.netty.v1.ProtocolDecoderV1;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.Map;

/**
* <pre>
* (> 0.7.0)
* 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
* +-----+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----+
* | magic |Proto| Full length | Head | Msg |Seria|Compr| RequestId |
* | code |colVer| (head+body) | Length |Type |lizer|ess | |
* +-----------+-----------+-----------+-----------+-----------+-----------+-----------+-----------+
*
* (<= 0.7.0)
* 0 1 2 3 4 6 8 10 12 14
* +-----+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----+
* | 0xdada | flag | typecode/ | requestid |
* | | | bodylength| |
* +-----------+-----------+-----------+-----------+-----------+-----------+-----------+
*
* </pre>
* <p>
* <li>Full Length: include all data </li>
* <li>Head Length: include head data from magic code to head map. </li>
* <li>Body Length: Full Length - Head Length</li>
* </p>
*/
public class CompatibleProtocolDecoder extends LengthFieldBasedFrameDecoder {

private static final Logger LOGGER = LoggerFactory.getLogger(CompatibleProtocolDecoder.class);
private static Map<Byte, ProtocolDecoder> protocolDecoderMap;

public CompatibleProtocolDecoder() {
// default is 8M
this(ProtocolConstants.MAX_FRAME_LENGTH);
}

public CompatibleProtocolDecoder(int maxFrameLength) {
/*
int maxFrameLength,
int lengthFieldOffset, magic code is 2B, and version is 1B, and then FullLength. so value is 3
int lengthFieldLength, FullLength is int(4B). so values is 4
int lengthAdjustment, FullLength include all data and read 7 bytes before, so the left length is (FullLength-7). so values is -7
int initialBytesToStrip we will check magic code and version self, so do not strip any bytes. so values is 0
*/
super(maxFrameLength, 3, 4, -7, 0);
protocolDecoderMap = ImmutableMap.<Byte, ProtocolDecoder>builder()
.put(ProtocolConstants.VERSION_0, new ProtocolDecoderV0())
.put(ProtocolConstants.VERSION_1, new ProtocolDecoderV1())
.build();
}

@Override
protected Object decode(ChannelHandlerContext ctx, ByteBuf in) throws Exception {
ByteBuf frame;
Object decoded;
byte version;
try {
if (isV0(in)) {
decoded = in;
version = ProtocolConstants.VERSION_0;
} else {
decoded = super.decode(ctx, in);
version = decideVersion(decoded);
}

if (decoded instanceof ByteBuf) {
frame = (ByteBuf) decoded;
try {
ProtocolDecoder decoder = protocolDecoderMap.get(version);
if (decoder == null) {
throw new UnsupportedOperationException("Unsupported version: " + version);
}
return decoder.decodeFrame(frame);
} finally {
if (version != ProtocolConstants.VERSION_0) {
frame.release();
}
}
}
} catch (Exception exx) {
LOGGER.error("Decode frame error, cause: {}", exx.getMessage());
throw new DecodeException(exx);
}
return decoded;
}

protected byte decideVersion(Object in) {
if (in instanceof ByteBuf) {
ByteBuf frame = (ByteBuf) in;
frame.markReaderIndex();
byte b0 = frame.readByte();
byte b1 = frame.readByte();
if (ProtocolConstants.MAGIC_CODE_BYTES[0] != b0
|| ProtocolConstants.MAGIC_CODE_BYTES[1] != b1) {
throw new IllegalArgumentException("Unknown magic code: " + b0 + ", " + b1);
}

byte version = frame.readByte();
frame.resetReaderIndex();
return version;
}
return -1;
}


protected boolean isV0(ByteBuf in) {
boolean isV0 = false;
in.markReaderIndex();
byte b0 = in.readByte();
byte b1 = in.readByte();
// v1/v2/v3 : b2 = version
// v0 : 1st byte in FLAG(2byte:0x10/0x20/0x40/0x80)
byte b2 = in.readByte();
if (ProtocolConstants.MAGIC_CODE_BYTES[0] == b0
&& ProtocolConstants.MAGIC_CODE_BYTES[1] == b1
&& 0 == b2) {
isV0 = true;
}

in.resetReaderIndex();
return isV0;
}

protected boolean isV0(byte version) {
return version == ProtocolConstants.VERSION_0;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.seata.core.rpc.netty;

import com.google.common.collect.ImmutableMap;
import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelHandlerContext;
import io.netty.handler.codec.MessageToByteEncoder;
import org.apache.seata.common.util.StringUtils;
import org.apache.seata.core.protocol.ProtocolConstants;
import org.apache.seata.core.protocol.RpcMessage;
import org.apache.seata.core.protocol.Version;
import org.apache.seata.core.rpc.netty.v0.ProtocolEncoderV0;
import org.apache.seata.core.rpc.netty.v1.ProtocolEncoderV1;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.Map;

/**
* Compatible Protocol Encoder
* <p>
* <li>Full Length: include all data </li>
* <li>Head Length: include head data from magic code to head map. </li>
* <li>Body Length: Full Length - Head Length</li>
* </p>
*/
public class CompatibleProtocolEncoder extends MessageToByteEncoder {

private static final Logger LOGGER = LoggerFactory.getLogger(CompatibleProtocolEncoder.class);

private static Map<Byte, ProtocolEncoder> protocolEncoderMap;

public CompatibleProtocolEncoder() {
super();
protocolEncoderMap = ImmutableMap.<Byte, ProtocolEncoder>builder()
.put(ProtocolConstants.VERSION_0, new ProtocolEncoderV0())
.put(ProtocolConstants.VERSION_1, new ProtocolEncoderV1())
.build();
}

@Override
public void encode(ChannelHandlerContext ctx, Object msg, ByteBuf out) {
try {
if (msg instanceof RpcMessage) {
RpcMessage rpcMessage = (RpcMessage) msg;
String sdkVersion = rpcMessage.getOtherSideVersion();
if (StringUtils.isBlank(sdkVersion)) {
sdkVersion = Version.getCurrent();
}
byte protocolVersion = Version.calcProtocolVersion(sdkVersion);
ProtocolEncoder encoder = protocolEncoderMap.get(protocolVersion);
if (encoder == null) {
throw new UnsupportedOperationException("Unsupported protocolVersion: " + protocolVersion);
}

encoder.encode(rpcMessage, out);
} else {
throw new UnsupportedOperationException("Not support this class:" + msg.getClass());
}
} catch (Throwable e) {
LOGGER.error("Encode request error!", e);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -35,8 +35,6 @@
import org.apache.seata.common.exception.FrameworkException;
import org.apache.seata.common.thread.NamedThreadFactory;
import org.apache.seata.core.rpc.RemotingBootstrap;
import org.apache.seata.core.rpc.netty.v1.ProtocolV1Decoder;
import org.apache.seata.core.rpc.netty.v1.ProtocolV1Encoder;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

Expand Down Expand Up @@ -134,8 +132,8 @@ public void initChannel(SocketChannel ch) {
new IdleStateHandler(nettyClientConfig.getChannelMaxReadIdleSeconds(),
nettyClientConfig.getChannelMaxWriteIdleSeconds(),
nettyClientConfig.getChannelMaxAllIdleSeconds()))
.addLast(new ProtocolV1Decoder())
.addLast(new ProtocolV1Encoder());
.addLast(new CompatibleProtocolDecoder())
.addLast(new CompatibleProtocolEncoder());
if (channelHandlers != null) {
addChannelPipelineLast(ch, channelHandlers);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,8 +37,6 @@
import org.apache.seata.common.thread.NamedThreadFactory;
import org.apache.seata.config.ConfigurationFactory;
import org.apache.seata.core.rpc.RemotingBootstrap;
import org.apache.seata.core.rpc.netty.v1.ProtocolV1Decoder;
import org.apache.seata.core.rpc.netty.v1.ProtocolV1Encoder;
import org.apache.seata.discovery.registry.MultiRegistryFactory;
import org.apache.seata.discovery.registry.RegistryService;
import org.slf4j.Logger;
Expand Down Expand Up @@ -161,8 +159,8 @@ public void start() {
@Override
public void initChannel(SocketChannel ch) {
ch.pipeline().addLast(new IdleStateHandler(nettyServerConfig.getChannelMaxReadIdleSeconds(), 0, 0))
.addLast(new ProtocolV1Decoder())
.addLast(new ProtocolV1Encoder());
.addLast(new CompatibleProtocolDecoder())
.addLast(new CompatibleProtocolEncoder());
if (channelHandlers != null) {
addChannelPipelineLast(ch, channelHandlers);
}
Expand Down
Loading

0 comments on commit 08eb0e6

Please sign in to comment.