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

Spring结合EhCache异步调用时缓存失效问题排查

问题排查与解决方案

这问题我之前帮同事排查过类似的,核心大概率是异步执行的时序或者Spring缓存与异步线程的上下文传递搞的鬼,咱们一步步拆解:

1. 异步加载的时序问题(最常见)

当你调用REST API触发缓存驱逐+异步加载时,异步任务是在后台线程单独跑的,当前API请求不会等它完成就直接返回了。这就导致第一次调用API后,缓存其实还没被填充——如果第二次调用来得快,异步任务还没跑完,那还是会查DB;但要是两次调用间隔够短(或者异步任务刚好在两次调用之间完成),第二次就能读到缓存了。

解决办法:

  • 如果业务允许,你可以让API等待异步任务完成后再返回(但这会失去异步的意义,适合后台预热类场景)
  • 重点检查你的@Cacheable注解是不是加在了异步执行的数据加载方法上,还要注意注解顺序:Spring的AOP是按注解顺序执行的,建议把@Cacheable放在@Async上面,确保缓存拦截器能正确捕获返回值并写入缓存,示例代码:
    @Cacheable(value = "dataCache", key = "#dataId")
    @Async
    public Data loadDataFromDb(Long dataId) {
        // 从数据库查询数据的逻辑
        return dataRepository.findById(dataId).orElse(null);
    }
    

2. Spring缓存的代理上下文坑

因为@Async是通过Spring代理执行的,如果你的缓存注解(@Cacheable/@CacheEvict)是在同一个类内的方法调用,会直接绕过代理,导致缓存逻辑根本不生效。比如你在Controller里直接调用同一个类的@Async方法,这时候Spring的AOP拦截器根本没起作用,缓存自然不会被写入。

解决办法:

  • 把异步加载的方法抽成单独的Service类,让Controller调用代理后的Service方法,这样@Cacheable和@Async的拦截器都能正常工作
  • 要是不想抽类,也可以在同一个类内通过ApplicationContext获取自身的代理对象来调用异步方法,示例:
    @Autowired
    private ApplicationContext context;
    
    public void evictAndReload(Long dataId) {
        // 驱逐缓存
        cacheManager.getCache("dataCache").evict(dataId);
        // 通过代理对象调用异步方法
        context.getBean(YourService.class).loadDataFromDb(dataId);
    }
    

3. 缓存Key生成不一致

检查你的@Cacheable的Key是不是每次都生成一致——如果Key不一样,就算是同一数据,也会命中不同的缓存条目,看起来就像没缓存一样。比如Key里用到了请求参数的toString()结果不一致,或者用到了ThreadLocal变量(而异步线程的ThreadLocal值和主线程不一样),都会导致Key不匹配。

解决办法:

  • 显式指定Key,别用默认的生成策略,比如:
    @Cacheable(value = "dataCache", key = "#dataId.toString()")
    
  • 可以在代码里打印每次查询的缓存Key,确认同一数据对应的Key是不是完全一致。

4. 事务与缓存的交互问题

如果你的异步加载方法上加了@Transactional注解,Spring默认会在事务提交后才写入缓存。这时候异步任务的事务还没提交,主线程的API就返回了,缓存自然没填充;等第二次调用时,异步任务的事务刚好提交,缓存就有数据了。

解决办法:

  • 如果不需要事务,直接去掉异步方法上的@Transactional
  • 要是必须用事务,可以调整缓存的事务行为,让缓存写入不等待事务提交,比如在Spring缓存配置里关闭事务感知:
    @Bean
    public CacheManager cacheManager() {
        EhCacheCacheManager cacheManager = new EhCacheCacheManager();
        cacheManager.setCacheManager(ehCacheManagerFactoryBean().getObject());
        cacheManager.setTransactionAware(false); // 关闭事务感知
        return cacheManager;
    }
    

5. 基础缓存配置检查

最后再确认下你的EhCache配置:缓存的过期时间、最大条目数是不是合理,有没有被意外清除;同时确保@EnableCaching注解已经在配置类上生效,servlet xml里的异步配置(比如<task:annotation-driven executor="taskExecutor"/>)也没问题。


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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.21 06:27:22