Docker化Spring Boot应用中嵌入式Infinispan序列化问题求助
首先,我们来拆解这个异常的核心根源:
java.io.NotSerializableException: com.ericsson.vdc.ratelimiter.service.service.StatisticsService
这个错误出现的关键原因是:你在computeIfAbsentAsync和merge方法中传入的Lambda表达式会隐式持有外部类StatisticsService的引用,而你配置的缓存模式是REPL_SYNC(同步复制)——Infinispan需要将这些Lambda逻辑序列化后传输到集群的其他节点,但StatisticsService并没有实现Serializable接口,最终导致序列化失败。
下面是三种可行的解决方案,你可以根据自身场景选择:
方案一:让StatisticsService实现Serializable接口
最简单的处理方式是直接让你的服务类实现Serializable,同时注意将不可序列化的成员(比如Logger)标记为transient,避免其被序列化:
import java.io.Serializable; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.stereotype.Service; @Service public class StatisticsService implements Serializable { // 标记为transient,跳过Logger的序列化 private transient Logger logger = LoggerFactory.getLogger(StatisticsService.class); // 你的incrementSmsSend方法及其他业务代码 public void incrementSmsSend(String key){ // ... 现有代码逻辑 } }
同时,务必确保你的DailySms类也实现Serializable(或者通过Protobuf配置序列化规则):
public class DailySms implements Serializable { private Long total; private Date expireTime; // Date本身是可序列化的,其他自定义成员需确保可序列化 // 构造方法、getter/setter、increment方法等 }
方案二:避免Lambda捕获外部类引用(更推荐)
如果不想让服务类实现Serializable,可以将Lambda中的业务逻辑抽成静态方法,这样Lambda就不会持有外部类的引用,自然也就不需要序列化外部类了:
@Service public class StatisticsService { // 使用静态Logger,避免序列化问题 private static final Logger logger = LoggerFactory.getLogger(StatisticsService.class); public void incrementSmsSend(String key){ AdvancedCache<String, DailySms> advancedCache = cacheManager.<String, DailySms>getCache("statistics-cache").getAdvancedCache() ; try { // 用静态方法引用替代原Lambda advancedCache.computeIfAbsentAsync("totalSms_"+key, StatisticsService::createInitialDailySms); advancedCache.merge("totalSms_"+key, new DailySms(1L,null), StatisticsService::mergeDailySms); }catch (Exception e){ logger.error("Error:",e); } } // 抽离初始化逻辑为静态方法 private static DailySms createInitialDailySms(String key) { return new DailySms(0L,null); } // 抽离合并逻辑为静态方法 private static DailySms mergeDailySms(DailySms dailySmsInit, DailySms dailySms) { if(dailySms.isExpired()){ logger.debug("init numberDailySms: {}",dailySmsInit.getTotal()); return dailySmsInit; }else{ dailySms.increment(); } logger.debug("numberDailySms: {}",dailySms.getTotal()); return dailySms; } }
方案三:配置Protobuf序列化(适合生产环境)
你已经在全局配置中添加了ManualSerializationContextInitializer,建议完善这个初始化器,注册DailySms的Protobuf序列化规则——Protobuf序列化比Java序列化更高效、更稳定,也更适合分布式场景:
- 先定义
DailySms的Protobuf schema(比如创建daily_sms.proto文件):
syntax = "proto3"; package com.ericsson.vdc.ratelimiter; message DailySms { int64 total = 1; int64 expire_time = 2; // 用时间戳存储日期,避免序列化Date对象的兼容性问题 }
- 在
ManualSerializationContextInitializer中注册该schema和对应的Marshaller:
public class ManualSerializationContextInitializer implements SerializationContextInitializer { @Override public String getProtoFileName() { return "daily_sms.proto"; } @Override public String getProtoFile() { // 读取proto文件内容,或直接返回schema字符串 return "syntax = \"proto3\"; package com.ericsson.vdc.ratelimiter; message DailySms { int64 total = 1; int64 expire_time = 2; }"; } @Override public void registerSchema(SerializationContext serCtx) { serCtx.registerProtoFiles(ProtoFile.fromString(getProtoFileName(), getProtoFile())); } @Override public void registerMarshallers(SerializationContext serCtx) { serCtx.registerMarshaller(new DailySmsMarshaller()); } } // 实现DailySms的Protobuf Marshaller public class DailySmsMarshaller implements BaseMarshaller<DailySms> { @Override public Class<? extends DailySms> getJavaClass() { return DailySms.class; } @Override public String getTypeName() { return "com.ericsson.vdc.ratelimiter.DailySms"; } @Override public DailySms readFrom(ProtoStreamReader reader) throws IOException { Long total = reader.readInt64("total"); Long expireTimeStamp = reader.readInt64("expire_time"); return new DailySms(total, expireTimeStamp != null ? new Date(expireTimeStamp) : null); } @Override public void writeTo(ProtoStreamWriter writer, DailySms dailySms) throws IOException { writer.writeInt64("total", dailySms.getTotal()); if(dailySms.getExpireTime() != null){ writer.writeInt64("expire_time", dailySms.getExpireTime().getTime()); } } }
结合方案二的静态方法使用,就能彻底避免外部类序列化的问题,同时获得更优的缓存性能。
额外注意事项
- 分布式缓存场景下,所有要缓存的对象、以及传递给缓存操作的逻辑(比如Lambda)都必须是可序列化的
- Logger对象一定不能被序列化,要么标记为
transient,要么使用静态Logger实例 - 生产环境优先选择Protobuf序列化,而非Java原生序列化,前者的兼容性和性能表现更出色
内容的提问来源于stack exchange,提问作者CimaW

