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

在ApiControllers服务层搭配Redis实现二级缓存存储匿名用户数据的最佳实践

二级缓存方案:内存缓存+Redis兜底(针对匿名用户短时效数据)

核心思路

针对匿名用户5分钟内的短时效数据(收藏、浏览记录),采用进程内内存缓存作为一级缓存、Redis作为二级缓存的架构:

  • 内存缓存负责高频读写,设置5分钟过期,直接命中时性能拉满;
  • Redis作为兜底,设置稍长过期时间(如10分钟),避免内存缓存失效后冷启动,同时保证服务重启或多实例部署时的数据一致性。

具体实现方案

1. 内存缓存选型

优先选Caffeine(性能优于Guava Cache,是当前Java生态最优的内存缓存库),配置核心参数:

  • expireAfterWrite=5分钟:写入后5分钟过期,匹配用户回访窗口;
  • maximumSize:根据服务内存容量设置上限(如10000条),防止内存溢出;
  • 可选开启统计(recordStats()),用于监控缓存命中率。

2. 统一二级缓存抽象层

封装内存缓存与Redis的操作逻辑,业务服务仅依赖抽象层,提升代码可读性与可维护性:

配置Caffeine缓存实例

@Configuration
public class CaffeineCacheConfig {
    @Bean
    public Cache<String, Object> anonymousUserCache() {
        return Caffeine.newBuilder()
                .expireAfterWrite(5, TimeUnit.MINUTES)
                .maximumSize(10000)
                .recordStats() // 可选:开启统计
                .build();
    }
}

二级缓存服务实现

@Service
public class TwoLevelCacheService {
    private final Cache<String, Object> anonymousUserCache;
    private final RedisService redisService;
    private final ThreadPoolTaskExecutor asyncRedisExecutor;

    // 构造注入依赖
    public TwoLevelCacheService(Cache<String, Object> anonymousUserCache, 
                                RedisService redisService,
                                ThreadPoolTaskExecutor asyncRedisExecutor) {
        this.anonymousUserCache = anonymousUserCache;
        this.redisService = redisService;
        this.asyncRedisExecutor = asyncRedisExecutor;
    }

    // 读操作:内存优先,Redis兜底并回写内存
    @SuppressWarnings("unchecked")
    public <T> T get(String key, Class<T> clazz) {
        // 1. 查内存缓存
        T value = (T) anonymousUserCache.getIfPresent(key);
        if (value != null) {
            return value;
        }
        // 2. 内存未命中,查Redis
        value = redisService.get(key, clazz);
        if (value != null) {
            // 3. 将Redis数据回写内存缓存
            anonymousUserCache.put(key, value);
        }
        return value;
    }

    // 写操作:同步更新内存,异步写入Redis(避免阻塞业务线程)
    public void put(String key, Object value) {
        // 1. 同步更新内存缓存
        anonymousUserCache.put(key, value);
        // 2. 异步写入Redis,设置10分钟过期
        asyncRedisExecutor.execute(() -> 
            redisService.set(key, value, 10, TimeUnit.MINUTES)
        );
    }

    // 删除操作:同时失效内存与Redis数据
    public void delete(String key) {
        anonymousUserCache.invalidate(key);
        asyncRedisExecutor.execute(() -> redisService.delete(key));
    }
}

异步线程池配置(可选,替代JDK原生线程池)

@Configuration
public class AsyncConfig {
    @Bean("asyncRedisExecutor")
    public ThreadPoolTaskExecutor asyncRedisExecutor() {
        ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
        executor.setCorePoolSize(4);
        executor.setMaxPoolSize(8);
        executor.setQueueCapacity(100);
        executor.setThreadNamePrefix("async-redis-");
        executor.initialize();
        return executor;
    }
}

3. 业务服务调用示例

以匿名用户收藏功能为例,统一使用缓存key前缀规范(anon:{匿名ID}:{数据类型}):

@Service
public class AnonymousUserService {
    private final TwoLevelCacheService twoLevelCacheService;

    public AnonymousUserService(TwoLevelCacheService twoLevelCacheService) {
        this.twoLevelCacheService = twoLevelCacheService;
    }

    // 保存匿名用户商品收藏
    public void addFavorite(String anonId, Long productId) {
        String cacheKey = String.format("anon:%s:favorites", anonId);
        // 从缓存获取现有收藏列表
        Set<Long> favorites = twoLevelCacheService.get(cacheKey, Set.class);
        if (favorites == null) {
            favorites = new HashSet<>();
        }
        favorites.add(productId);
        // 写入二级缓存
        twoLevelCacheService.put(cacheKey, favorites);
    }

    // 获取匿名用户商品收藏列表
    public Set<Long> getFavorites(String anonId) {
        String cacheKey = String.format("anon:%s:favorites", anonId);
        return twoLevelCacheService.get(cacheKey, Set.class);
    }
}

可选简化方案:Spring Cache注解式实现

如果不想手动封装缓存逻辑,可以利用Spring Cache的扩展能力,同时配置Caffeine与Redis缓存管理器,通过@Cacheable、@CachePut注解实现二级缓存:

  1. 配置两个缓存管理器(Caffeine对应内存缓存,Redis对应远程缓存);
  2. 自定义缓存注解或使用@Caching组合多个缓存操作,实现先读内存、再读Redis,写操作同步更新两者。

这种方式代码更简洁,但灵活性稍弱,适合业务逻辑简单的场景。

关键优化点

  • Key命名规范:统一使用anon:{匿名ID}:{数据类型}格式,避免缓存键冲突;
  • 序列化兼容性:Redis存储对象时,优先用JSON序列化(如Jackson),避免Java序列化的兼容性问题;
  • 监控与调优:通过Caffeine的统计功能监控缓存命中率,调整maximumSize与过期时间;
  • 多实例一致性:因为匿名用户数据一致性要求低,Redis稍长的过期时间足以覆盖多实例间的数据同步需求,无需额外的分布式缓存失效方案。

内容的提问来源于stack exchange,提问作者FoolCode

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.02 05:34:51