在Spring Boot中重新加载/刷新缓存

16

我正在使用Spring Boot,并且使用Ehcache进行缓存。目前为止一切正常。但是现在我需要重新加载/刷新,那么我该怎么做才能使我的应用程序没有任何停机时间。

我已经尝试了Spring Ehcache中的许多方法,但它们都没有奏效。或者我必须编写一个调度程序来重新加载数据。


@Override
@Cacheable(value="partTypeCache", key="#partKey")
public List<PartType> loadPartType(String partKey) throws CustomException {
        return productIdentityDao.loadPartType();
}

缓存清除是您正在寻找的吗?:https://docs.spring.io/spring/docs/current/spring-framework-reference/integration.html#cache-annotations-evict - ItFreak
1
@ItFreak CacheEvict 用于清除缓存,但在我的情况下,我必须重新加载/刷新缓存,以便它与我的数据库同步。 - Shubuu
我认为没有现成的解决方案,因为这意味着框架知道所有底层数据源。 - ItFreak
2
CacheEvict会从缓存中删除值。我想在固定的时间间隔后重新加载整个缓存,而不会出现问题。 - Shubuu
6个回答

18

显然,关于您的问题的所有评论都是正确的。您应该使用CacheEvict。我在这里找到了解决方案:https://www.baeldung.com/spring-boot-evict-cache,看起来像这样:

你所需要做的就是创建一个名为CacheService的类,并创建一个方法来清除您拥有的所有缓存对象。然后,您将该方法注释为@Scheduled,并设置您的时间间隔。

@Service
public class CacheService {

    @Autowired
    CacheManager cacheManager;

    public void evictAllCaches() {
        cacheManager.getCacheNames().stream()
          .forEach(cacheName -> cacheManager.getCache(cacheName).clear());
    }

    @Scheduled(fixedRate = 6000)
    public void evictAllcachesAtIntervals() {
        evictAllCaches();
    }

}

我想对cacheservice.evictAllcaches进行单元测试。在清除缓存后,我观察到cacheManager.getCacheNames()返回相同的缓存值。有没有想法如何测试空缓存场景。 - StackOverFlow
由于Collection也有一个forEach方法,因此可以通过删除.stream()调用来简化解决方案:cacheManager.getCacheNames().forEach - M. Justin

4

很多人建议使用@CacheEvict选项是正确的。此外,为了确保你的缓存(近似)始终具有最新的数据加载,即使数据在数据库中更新超出了运行应用程序的范围,你需要周期性地重新加载整个数据集,间隔时间要与应用程序的SLAs匹配。在上面提供的解决方案中,添加重新加载所有数据的逻辑如下:

@Service
public class CacheService {

    @Autowired
    CacheManager cacheManager;

    public void refreshAllCaches() {
        cacheManager.getCacheNames().stream()
          .forEach(cacheName -> cacheManager.getCache(cacheName).clear());
        // reload whole dataset here, dummy example here:
        dataRepository.findAll().forEach(a -> cacheManager.getCache("cache-name")).put(a.getKey(), a));
    }

    @Scheduled(fixedRate = 6000)
    public void refreshAllcachesAtIntervals() {
        refreshAllCaches();
    }

}

2
试试像评论中提到的这样做:

尝试以下操作:

    @Caching(evict={@CacheEvict(value="partTypeCache", key="#partKey")})
    public boolean deletePartType(String partKey) { 
      //when this method is invoked the cache is evicted for the requested key
    }

1
感谢回复。CacheEvict将清除缓存,但我必须刷新缓存以便它与数据库同步。 - Shubuu
2
当@Cacheable执行时,它将在缓存中找不到数据,并自动刷新/重新填充缓存。 - TechFree
但是我知道的是@Cacheable用于将数据加载到缓存中,这很好用,但是假设从不同的来源添加了新值,例如ETL、不同的应用程序。那么在这种情况下,我需要重新加载缓存。 - Shubuu
@Cacheable调用将同时更新缓存。但是,您也可以使用@CachePut来更新缓存。请参阅:https://www.concretepage.com/spring/spring-cacheput-annotation-example-using-javaconfig。现在,当任何来源更新缓存时,需要调用带有@CachePut注释的方法。我假设您已经拥有来自不同应用程序的消息/链接/触发器/处理程序以更新缓存。 - TechFree
不处理那些触发器,因为有各种同步进程直接将数据转储到数据库中。在Spring中是否有一种方式可以直接重新加载整个数据/新添加的数据到缓存中?一种方法是编写一个调度程序,并定期执行它。您有更好的建议吗? - Shubuu

1
除了上面的答案,您还可以在调度程序中使用cron表达式,这样可以提供更大的灵活性。您可以设置每天、每周、每年、每天12点等运行调度程序,而无需编写太多代码。
@Service public class CacheService {
@Autowired
CacheManager cacheManager;

public void evictAllCaches() {
    cacheManager.getCacheNames().stream()
      .forEach(cacheName -> cacheManager.getCache(cacheName).clear());
}

@Scheduled(cron = "@weekly")
public void evictAllcachesAtIntervals() {
    evictAllCaches();
}

}


0

需求是在X时间间隔内刷新1个缓存,Y时间间隔内刷新另一个缓存。如果我们只在一个类中编写以下代码,则缓存不会重新加载。


public class XXXSchedulers {

    @Autowired
    private XXXUtil util;

    @Scheduled(fixedDelay = 10 * 60 * 1000) // Running after 10 minutes
    public void clearSpecificABCCache() {
        util.clearSpecificABCCache();
        util.getABC();//Again gets value in the cache
    }

    @Scheduled(cron = "0 0 2 * * ?") //Running Everyday at 2 AM public void 
    public void clearSpecificXYZCache() {
        util.clearSpecificABCCache();
        util.getXYZ();
    }
}

@Component
public class XXXUtil {
    @Autowired
    private CacheManager cacheManager;

    @Autowired
    private XXXService service;

    @Cacheable("abc")
    public ABC getABC() {
        ABC abc = service.getABC();
    }

    public void clearSpecificABCCache() {
        cacheManager.getCache("abc").clear();
    }

    @Cacheable("xyz")
    public XYZ getXYZCache() {
        XYZ xyz = service.getXYZCache();
    }
    
    public void clearSpecificXYZCache() {
        cacheManager.getCache("xyz").clear();
    }
  }

0

你知道你要特别更新哪些partKeys吗?如果不知道,你必须从缓存本身找到这些keys。你可以使用@CachePut来更新缓存值而不会停机。我的建议性能可能不是很好,但它应该正好符合你的需求。

对于EhCache:

public void renewCache() {
    net.sf.ehcache.EhCache cache = (net.sf.ehcache.EhCache) org.springframework.cache.CacheManager.getCache("partTypeCache").getNativeCache();
    for (Object key : cache.getKeys()) {
          updatePartTypeCache((String) key)
    }
}

@CachePut(value="partTypeCache", key="#partKey")
public List<PartType> updatePartTypeCache(String partKey) throws CustomException {
        return productIdentityDao.loadPartType();
}

要明确一点,最糟糕的部分是从现有缓存中获取密钥。如果您有一个想要保留在缓存中的“热门”部分的简短列表,您可以只针对这些部分调用updatePartTypeCache方法。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接