You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Docker化Spring Boot应用中嵌入式Infinispan序列化问题求助

解决嵌入式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序列化更高效、更稳定,也更适合分布式场景:

  1. 先定义DailySms的Protobuf schema(比如创建daily_sms.proto文件):
syntax = "proto3";

package com.ericsson.vdc.ratelimiter;

message DailySms {
    int64 total = 1;
    int64 expire_time = 2; // 用时间戳存储日期,避免序列化Date对象的兼容性问题
}
  1. 在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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.08 17:42:37