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

Spring中Caffeine缓存refreshAfterWrite异步刷新未触发问题排查

Spring中Caffeine缓存异步全量刷新问题解决

问题分析

你想要实现全量异步刷新缓存所有键,从外部服务获取数据后无缝替换旧值且无停机,但当前配置的AsyncLoadingCache中fetchCacheValues()从未被调用,核心原因是你误解了refreshAfterWrite()的行为:

  • refreshAfterWrite是被动触发机制:只有当某个缓存key被访问,且该key距离上次写入时间超过设定的5分钟时,才会异步触发对应的loader逻辑刷新这个key。它不会主动遍历所有key执行全量刷新。
  • 你的loader实现不符合设计预期:buildAsync的loader函数是针对单个key的,应该根据传入的key获取对应值,但你现在不管什么key都调用全量获取的fetchCacheValues(),逻辑本身就不匹配。

另外你提到用定时任务+CachePut出现停机问题,大概率是因为刷新时先删除旧值再加载新值,或者同步加载新值导致缓存空窗期,用户访问无缓存可用。

解决方案

方案1:主动全量异步刷新(匹配你的全量刷新需求)

这种方式主动定时获取全量数据,批量更新缓存,完全避免空窗期:

  1. 缓存初始化时先加载全量数据
  2. 用Spring定时任务异步执行全量刷新,确保新数据就绪后再替换旧值
@Bean
public AsyncLoadingCache<Object, Object> myCache() {
    // 启动时加载初始全量数据
    Map<Object, Object> initialData = cacheInitializer.fetchCacheValues();
    return Caffeine.newBuilder()
            .maximumSize(cacheProperties.getProp())
            .recordStats()
            // 初始化loader,根据key返回对应初始值
            .buildAsync((key, executor) -> CompletableFuture.completedFuture(initialData.get(key)));
}

// 每5分钟异步执行全量刷新
@Scheduled(fixedRate = 5, timeUnit = TimeUnit.MINUTES)
@Async
public void refreshAllCacheEntries() {
    // 先异步获取最新全量数据,确保数据就绪后再更新缓存
    Map<Object, Object> latestData = cacheInitializer.fetchCacheValues();
    // 获取缓存的并发安全视图,直接批量替换
    ConcurrentMap<Object, Object> cacheMap = myCache().asMap();
    
    // 批量更新现有key的新值
    latestData.forEach(cacheMap::put);
    // 可选:删除缓存中存在但新数据已不存在的key(根据业务需求决定)
    cacheMap.keySet().removeIf(key -> !latestData.containsKey(key));
}
  • 用@Async保证刷新任务异步执行,不阻塞主线程
  • 先加载完所有新数据再批量替换,用户访问始终能获取到旧值或新值,完全无空窗期
  • 缓存的asMap()是并发安全的,批量操作不会出现线程安全问题

方案2:修正refreshAfterWrite用法(适合单key被动刷新场景)

如果你的需求其实是单个key被访问时才触发刷新,而非全量刷新,那需要修正loader逻辑,让它根据key获取对应值:

@Bean
public AsyncLoadingCache<Object, Object> myCache() {
    return Caffeine.newBuilder()
            .maximumSize(cacheProperties.getProp())
            .refreshAfterWrite(5, TimeUnit.MINUTES)
            .recordStats()
            // 根据传入的key获取对应数据,而非全量获取
            .buildAsync(key -> cacheInitializer.fetchValueByKey(key));
}

此时当某个key被访问且超过刷新时间时,会异步调用fetchValueByKey(key)获取新值,旧值会被继续返回直到新值加载完成,实现无缝替换。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.24 04:35:11