Apache Seata(incubating) 是一款开源的分布式事务解决方案,致力于在微服务架构下提供高性能和简单易用的分布式事务服务。
本篇文章主要介绍Seata序列化实现优化。Seata对于网络传输数据,提供了多种序列化实现,包含Seata自身的序列化实现、protobuf实现、kryo实现等;
通过benckmark,发现seata的反序列化吞吐量远小于protobuf的吞吐,如下所示:
benchmark代码参考:
@State(value = Scope.Benchmark)
@Warmup(iterations = 1, time = 1)
@Measurement(iterations = 3, time = 1)
@BenchmarkMode({Mode.Throughput, Mode.SampleTime})
@OutputTimeUnit(TimeUnit.MILLISECONDS)
public class SerializerBenchMark {@Param({"SEATA", "PROTOBUF", "KRYO", "HESSIAN"})
private String serializerType;@Param({"GlobalBeginRequest", "GlobalBeginResponse", "GlobalCommitRequest", "GlobalCommitResponse"})
private String type;private Object serializerObject;private byte[] deserializerByteArray;private Serializer serializer;@Setup(Level.Trial)
public void setup() {serializer = SerializerServiceLoader.load(SerializerType.getByName(serializerType));switch (type) {case "GlobalBeginRequest":GlobalBeginRequest globalBeginRequest = new GlobalBeginRequest();globalBeginRequest.setTimeout(10);globalBeginRequest.setTransactionName("transactionName");serializerObject = globalBeginRequest;break;case "GlobalBeginResponse":GlobalBeginResponse globalBeginResponse = new GlobalBeginResponse();globalBeginResponse.setTransactionExceptionCode(TransactionExceptionCode.GlobalTransactionNotActive);globalBeginResponse.setExtraData("{\"key\",\"value\"}");globalBeginResponse.setXid(XID.generateXID(UUIDGenerator.generateUUID()));globalBeginResponse.setResultCode(ResultCode.Failed);globalBeginResponse.setMsg("msg");serializerObject = globalBeginResponse;break;case "GlobalCommitRequest":GlobalCommitRequest globalCommitRequest = new GlobalCommitRequest();globalCommitRequest.setExtraData("{\"key\",\"value\"}");globalCommitRequest.setXid(XID.generateXID(UUIDGenerator.generateUUID()));serializerObject = globalCommitRequest;break;case "GlobalCommitResponse":GlobalCommitResponse globalCommitResponse = new GlobalCommitResponse();globalCommitResponse.setGlobalStatus(GlobalStatus.AsyncCommitting);globalCommitResponse.setMsg("msg");globalCommitResponse.setResultCode(ResultCode.Failed);globalCommitResponse.setTransactionExceptionCode(TransactionExceptionCode.GlobalTransactionStatusInvalid);serializerObject = globalCommitResponse;break;}deserializerByteArray = serializer.serialize(serializerObject);
}@Benchmark
public void testSerialize() {serializer.serialize(serializerObject);
}@Benchmark
public void testDeserialize() {serializer.deserialize(deserializerByteArray);
}
}
定位性能问题有很多方案,我们这里使用火焰图方式,在运行benchmark的时候,同时使用火焰图定位热点代码,在火焰图里一般热点代码就是性能瓶颈。
我们发现性能瓶颈在MessageCodecFactory#getMessageCodec方法。Seata自身序列化,设计了MessageCodec的超类,包含encode和decode方法,不同的对象类型,有不同的MessageCodec实现,比如GlobalBeginRequest对象对应GlobalBeginRequestCodec实现,这个方法是根据对象类型来获取对应的MessageCodec实现。
我们细化到这个方法里面,发现是热点集中在异常初始化这里,跟进历史代码,发现是获取机制实现得有问题,先get request相关类,再response,resopnse类必然会走到异常的逻辑,在JAVA里面构造异常是一个耗时操作,所以导致整体吞吐的下降。
与此同时,我们关注到序列化过程中存在多次无效拷贝,比如下面NIO的byteBuffer重复拷贝,一般反序列化不会对对象序列化内容进行修改,所以这个拷贝是没有必要的。
同时这块存在NIO byteBuffer和netty的byteBuf混用的情况,也导致了比较多的无效拷贝,这块使用的地方比较多,后续也是一个优化的点。
序列化优化的PR参考:https://github.com/apache/incubator-seata/pull/6727
**优化效果:**优化完成后,通过上面的benchmark,seata反序列化的吞吐量提升7~10倍左右。