最近使用 seata 当分布事务回滚发现,回滚失败,而日志一直在打印序列化的问题。根据网上博客和 issue 的记录,最终解决方案如下:
根据 github-issue 发现需要等待版本更新或是更新依赖。觉得太过麻烦,这里参考 issue 与 掘金-seata中undolog报LocalDateTime转换异常,源码层分析和处理 的方式,最终是如下解决:这里以 json 方式为例,这个问题在 1.5.0 以上的版本已经修改,这里将 1.5.0 的 undoLog 解析类,的代码复制下来 github-1.5.0 版本的解析类。
定义解析类代码
这个是 seata 1.5.0 版本的json方式解析类,这里进行了修改,@LoadLevel(name = MyJacksonUndoLogParser.NAME)
定义了序列方式的名称,这里我定义了为myJackson
。
import com.fasterxml.jackson.annotation.JsonTypeInfo;
import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.core.JsonToken;
import com.fasterxml.jackson.core.type.WritableTypeId;
import com.fasterxml.jackson.databind.*;
import com.fasterxml.jackson.databind.deser.std.JsonNodeDeserializer;
import com.fasterxml.jackson.databind.jsontype.TypeSerializer;
import com.fasterxml.jackson.databind.module.SimpleModule;
import com.fasterxml.jackson.databind.node.ArrayNode;
import com.fasterxml.jackson.databind.ser.std.ArraySerializerBase;
import io.seata.common.Constants;
import io.seata.common.executor.Initialize;
import io.seata.common.loader.EnhancedServiceLoader;
import io.seata.common.loader.EnhancedServiceNotFoundException;
import io.seata.common.loader.LoadLevel;
import io.seata.common.util.CollectionUtils;
import io.seata.rm.datasource.undo.BranchUndoLog;
import io.seata.rm.datasource.undo.UndoLogParser;
import io.seata.rm.datasource.undo.parser.spi.JacksonSerializer;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.sql.rowset.serial.SerialBlob;
import javax.sql.rowset.serial.SerialClob;
import javax.sql.rowset.serial.SerialException;
import java.io.IOException;
import java.io.Reader;
import java.sql.SQLException;
import java.sql.Timestamp;
import java.time.Instant;
import java.time.LocalDateTime;
import java.time.ZoneId;
import java.util.Arrays;
import java.util.List;
import java.util.Objects;
/**
* The type Json based undo log parser.
*
* @author jsbxyyx
*/
@LoadLevel(name = MyJacksonUndoLogParser.NAME)
public class MyJacksonUndoLogParser implements UndoLogParser, Initialize {
public static final String NAME = "myJackson";
private static final Logger LOGGER = LoggerFactory.getLogger(MyJacksonUndoLogParser.class);
/**
* the zoneId for LocalDateTime
*/
private static ZoneId zoneId = ZoneId.systemDefault();
private final ObjectMapper mapper = new ObjectMapper();
private final SimpleModule module = new SimpleModule();
/**
* customize serializer for java.sql.Timestamp
*/
private final JsonSerializer timestampSerializer = new TimestampSerializer();
/**
* customize deserializer for java.sql.Timestamp
*/
private final JsonDeserializer timestampDeserializer = new TimestampDeserializer();
/**
* customize serializer of java.sql.Blob
*/
private final JsonSerializer blobSerializer = new BlobSerializer();
/**
* customize deserializer of java.sql.Blob
*/
private final JsonDeserializer blobDeserializer = new BlobDeserializer();
/**
* customize serializer of java.sql.Clob
*/
private final JsonSerializer clobSerializer = new ClobSerializer();
/**
* customize deserializer of java.sql.Clob
*/
private final JsonDeserializer clobDeserializer = new ClobDeserializer();
/**
* customize serializer of java.time.LocalDateTime
*/
private final JsonSerializer localDateTimeSerializer = new LocalDateTimeSerializer();
/**
* customize deserializer of java.time.LocalDateTime
*/
private final JsonDeserializer localDateTimeDeserializer = new LocalDateTimeDeserializer();
@Override
public void init() {
try {
List<JacksonSerializer> jacksonSerializers = EnhancedServiceLoader.loadAll(JacksonSerializer.class);
if (CollectionUtils.isNotEmpty(jacksonSerializers)) {
for (JacksonSerializer jacksonSerializer : jacksonSerializers) {
Class type = jacksonSerializer.type();
JsonSerializer ser = jacksonSerializer.ser();
JsonDeserializer deser = jacksonSerializer.deser();
if (type != null) {
if (ser != null) {
module.addSerializer(type, ser);
}
if (deser != null) {
module.addDeserializer(type, deser);
}
LOGGER.info("jackson undo log parser load [{}].", jacksonSerializer.getClass().getName());
}
}
}
} catch (EnhancedServiceNotFoundException e) {
LOGGER.warn("JacksonSerializer not found children class.", e);
}
module.addSerializer(Timestamp.class, timestampSerializer);
module.addDeserializer(Timestamp.class, timestampDeserializer);
module.addSerializer(SerialBlob.class, blobSerializer);
module.addDeserializer(SerialBlob.class, blobDeserializer);
module.addSerializer(SerialClob.class, clobSerializer);
module.addDeserializer(SerialClob.class, clobDeserializer);
module.addSerializer(LocalDateTime.class, localDateTimeSerializer);
module.addDeserializer(LocalDateTime.class, localDateTimeDeserializer);
mapper.registerModule(module);
mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
mapper.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL, JsonTypeInfo.As.PROPERTY);
mapper.enable(MapperFeature.PROPAGATE_TRANSIENT_MARKER);
}
@Override
public String getName() {
return NAME;
}
@Override
public byte[] getDefaultContent() {
return "{}".getBytes(Constants.DEFAULT_CHARSET);
}
@Override
public byte[] encode(BranchUndoLog branchUndoLog) {
try {
return mapper.writeValueAsBytes(branchUndoLog);
} catch (JsonProcessingException e) {
LOGGER.error("json encode exception, {}", e.getMessage(), e);
throw new RuntimeException(e);
}
}
@Override
public BranchUndoLog decode(byte[] bytes) {
try {
BranchUndoLog branchUndoLog;
if (Arrays.equals(bytes, getDefaultContent())) {
branchUndoLog = new BranchUndoLog();
} else {
branchUndoLog = mapper.readValue(bytes, BranchUndoLog.class);
}
return branchUndoLog;
} catch (IOException e) {
LOGGER.error("json decode exception, {}", e.getMessage(), e);
throw new RuntimeException(e);
}
}
/**
* if necessary
* extend {@link ArraySerializerBase}
*/
private static class TimestampSerializer extends JsonSerializer<Timestamp> {
@Override
public void serializeWithType(Timestamp timestamp, JsonGenerator gen, SerializerProvider serializers,
TypeSerializer typeSerializer) throws IOException {
JsonToken valueShape = JsonToken.VALUE_NUMBER_INT;
// if has microseconds, serialized as an array
if (timestamp.getNanos() % 1000000 > 0) {
valueShape = JsonToken.START_ARRAY;
}
WritableTypeId typeId = typeSerializer.writeTypePrefix(gen,
typeSerializer.typeId(timestamp, valueShape));
serialize(timestamp, gen, serializers);
gen.writeTypeSuffix(typeId);
}
@Override
public void serialize(Timestamp timestamp, JsonGenerator gen, SerializerProvider serializers) {
try {
gen.writeNumber(timestamp.getTime());
// if has microseconds, serialized as an array, write the nanos to the array
if (timestamp.getNanos() % 1000000 > 0) {
gen.writeNumber(timestamp.getNanos());
}
} catch (IOException e) {
LOGGER.error("serialize java.sql.Timestamp error : {}", e.getMessage(), e);
}
}
}
/**
* if necessary
* extend {@link JsonNodeDeserializer}
*/
private static class TimestampDeserializer extends JsonDeserializer<Timestamp> {
@Override
public Timestamp deserialize(JsonParser p, DeserializationContext ctxt) {
try {
if (p.isExpectedStartArrayToken()) {
ArrayNode arrayNode = p.getCodec().readTree(p);
Timestamp timestamp = new Timestamp(arrayNode.get(0).asLong());
timestamp.setNanos(arrayNode.get(1).asInt());
return timestamp;
} else {
long timestamp = p.getLongValue();
return new Timestamp(timestamp);
}
} catch (IOException e) {
LOGGER.error("deserialize java.sql.Timestamp error : {}", e.getMessage(), e);
}
return null;
}
}
/**
* the class of serialize blob type
*/
private static class BlobSerializer extends JsonSerializer<SerialBlob> {
@Override
public void serializeWithType(SerialBlob blob, JsonGenerator gen, SerializerProvider serializers,
TypeSerializer typeSer) throws IOException {
WritableTypeId typeIdDef = typeSer.writeTypePrefix(gen,
typeSer.typeId(blob, JsonToken.VALUE_EMBEDDED_OBJECT));
serialize(blob, gen, serializers);
typeSer.writeTypeSuffix(gen, typeIdDef);
}
@Override
public void serialize(SerialBlob blob, JsonGenerator gen, SerializerProvider serializers) throws IOException {
try {
gen.writeBinary(blob.getBytes(1, (int)blob.length()));
} catch (SerialException e) {
LOGGER.error("serialize java.sql.Blob error : {}", e.getMessage(), e);
}
}
}
/**
* the class of deserialize blob type
*/
private static class BlobDeserializer extends JsonDeserializer<SerialBlob> {
@Override
public SerialBlob deserialize(JsonParser p, DeserializationContext ctxt) throws IOException {
try {
return new SerialBlob(p.getBinaryValue());
} catch (SQLException e) {
LOGGER.error("deserialize java.sql.Blob error : {}", e.getMessage(), e);
}
return null;
}
}
/**
* the class of serialize clob type
*/
private static class ClobSerializer extends JsonSerializer<SerialClob> {
@Override
public void serializeWithType(SerialClob clob, JsonGenerator gen, SerializerProvider serializers,
TypeSerializer typeSer) throws IOException {
WritableTypeId typeIdDef = typeSer.writeTypePrefix(gen,
typeSer.typeId(clob, JsonToken.VALUE_EMBEDDED_OBJECT));
serialize(clob, gen, serializers);
typeSer.writeTypeSuffix(gen, typeIdDef);
}
@Override
public void serialize(SerialClob clob, JsonGenerator gen, SerializerProvider serializers) throws IOException {
try (Reader r = clob.getCharacterStream()) {
gen.writeString(r, (int)clob.length());
} catch (SerialException e) {
LOGGER.error("serialize java.sql.Blob error : {}", e.getMessage(), e);
}
}
}
private static class ClobDeserializer extends JsonDeserializer<SerialClob> {
@Override
public SerialClob deserialize(JsonParser p, DeserializationContext ctxt) throws IOException {
try {
return new SerialClob(p.getValueAsString().toCharArray());
} catch (SQLException e) {
LOGGER.error("deserialize java.sql.Clob error : {}", e.getMessage(), e);
}
return null;
}
}
/**
* the class of serialize LocalDateTime type
*/
private static class LocalDateTimeSerializer extends JsonSerializer<LocalDateTime> {
@Override
public void serializeWithType(LocalDateTime localDateTime, JsonGenerator gen, SerializerProvider serializers,
TypeSerializer typeSer) throws IOException {
JsonToken valueShape = JsonToken.VALUE_NUMBER_INT;
// if has microseconds, serialized as an array
if (localDateTime.getNano() % 1000000 > 0) {
valueShape = JsonToken.START_ARRAY;
}
WritableTypeId typeIdDef = typeSer.writeTypePrefix(gen,
typeSer.typeId(localDateTime, valueShape));
serialize(localDateTime, gen, serializers);
typeSer.writeTypeSuffix(gen, typeIdDef);
}
@Override
public void serialize(LocalDateTime localDateTime, JsonGenerator gen, SerializerProvider serializers) throws IOException {
try {
Instant instant = localDateTime.atZone(zoneId).toInstant();
gen.writeNumber(instant.toEpochMilli());
// if has microseconds, serialized as an array, write the nano to the array
if (instant.getNano() % 1000000 > 0) {
gen.writeNumber(instant.getNano());
}
} catch (IOException e) {
LOGGER.error("serialize java.time.LocalDateTime error : {}", e.getMessage(), e);
}
}
}
/**
* the class of deserialize LocalDateTime type
*/
private static class LocalDateTimeDeserializer extends JsonDeserializer<LocalDateTime> {
@Override
public LocalDateTime deserialize(JsonParser p, DeserializationContext ctxt) throws IOException {
try {
Instant instant;
if (p.isExpectedStartArrayToken()) {
ArrayNode arrayNode = p.getCodec().readTree(p);
long timestamp = arrayNode.get(0).asLong();
instant = Instant.ofEpochMilli(timestamp);
if (arrayNode.size() > 1) {
int nano = arrayNode.get(1).asInt();
instant = instant.plusNanos(nano % 1000000);
}
} else {
long timestamp = p.getLongValue();
instant = Instant.ofEpochMilli(timestamp);
}
return LocalDateTime.ofInstant(instant, zoneId);
} catch (Exception e) {
LOGGER.error("deserialize java.time.LocalDateTime error : {}", e.getMessage(), e);
}
return null;
}
}
/**
* set zone id
*
* @param zoneId the zoneId
*/
public static void setZoneOffset(ZoneId zoneId) {
Objects.requireNonNull(zoneId, "zoneId must be not null");
MyJacksonUndoLogParser.zoneId = zoneId;
}
}
定义 services 文件
在 META-INF/services 目录中定义名称为io.seata.rm.datasource.undo.UndoLogParser
的文件,将复制下来的解析类,类全路径写入。如我的:
com.old.seata.MyJacksonUndoLogParser
配置 undo_log 的解析方式
注意, seata 的客户端使用不同的配置方式,配置 undo_log 的序列化方式就不同,所以可能和使用者的不同。需要注意这点。最重要的一点是看序列化类是否有初始化,在请求时对序列化类进行断点,查看是否有初始化。如果没有初始化表示配置的方式不对。
以 yml 为例,**注意:**要与类上注解定义的名称相同。
seata:
client:
undo:
logSerialization: myJackson
完成之后这三步之后,再次进行事务的回滚即可避免序列化异常的问题,不过要确定 client.undo.logSerialization
的配置是否生效。