在业务系统中,为了降低对业务数据库的负载压力,我们需要在程序启动时将一些常用数据主动加载到内存数据库,这就是我们通常所说的缓存预热策略。

官方定义如下:

缓存预热是一种策略,它在程序启动或缓存失效后主动将热点数据加载到缓存中。通过这种方式,当实际请求到达程序时,热点数据已经存在于缓存中,从而有助于减少缓存穿透和缓存击穿的情况,同时减轻了SQL服务器的负载。

为了实现这一策略,我们结合业务系统并进行了相关设计,以下是设计代码的详细描述:

1.定义缓存抽象类

定义了一个抽象类,用于执行缓存操作,包括初始化缓存、从缓存中获取数据、清理缓存以及刷新缓存等操作。这个抽象类与Spring Boot的生命周期监控相关联。

public abstract class AbstractCache { /** * 缓存 */ protected abstract void init(); /** * 获取缓存 * @return 缓存列表 */ public abstract <T> T get(); /** *清理缓存 */ public abstract void clear(); /** *重新加载*/ public void reload() { clear(); init(); } } 

2.spring boot生命周期的监控

在Spring Boot项目启动后,立即执行初始化缓存操作。而在项目结束时,缓存被立即删除。

@Component public class MyFullLifecycle implements SmartLifecycle { @Resource private ApplicationContext applicationContext; private boolean isRunning = false; public AbstractCache getAbstractCache() { Map<String, AbstractCache> beansOfType = applicationContext.getBeansOfType(AbstractCache.class); for (Map.Entry<String, AbstractCaches> cacheEntry : beansOfType.entrySet()) { return applicationContext.getBean(cacheEntry.getValue().getClass()); } return null; } @Override public void start() { //编写程序启动时的逻辑 System.out.println("应用程序启动"); isRunning = true; AbstractCache cache = getAbstractCache(); cache.init(); } @Override public void stop() { //编写程序停止时的逻辑 System.out.println("应用程序停止"); isRunning = false; AbstractCache cache = getAbstractCache(); cache.clear(); } @Override public boolean isRunning() { return isRunning; } } 

3.创建AbstractCache的具体实现类

创建了继承该抽象类的具体实现类,其中包括以下核心方法的重写:

  • 初始化:将所有热点数据缓存到Redis中。
  • 查询:如果缓存中不存在特定key的数据,就执行初始化缓存;否则,直接从缓存中获取数据。
  • 删除:用于在服务关闭时清除缓存。这里采用了直接删除key的方法,但对于大量key的情况,建议使用游标或Lua脚本来删除。
@Component @RequiredArgsConstructor public class UserCache extends AbstractCache { private static final String USERS_KEY = "users" ; @Resource private RedisTemplate<String, Object> redisTemplate; @Resource private UserService userService; @Override protected void init() { if (Boolean.FALSE.equals(redisTemplate.hasKey(USERS_KEY))) { redisTemplate.opsForValue().set(USERS_KEY, userService.list(), 30, TimeUnit.MINUTES); } } @Override public <T> T get() { if (Boolean.FALSE.equals(redisTemplate.hasKey(USERS_KEY))){ init(); } return (T) redisTemplate.opsForValue().get(USERS_KEY); } @Override public void clear() { redisTemplate.delete(USERS_KEY); } } 

4.测试

提供了一个接口类,用于测试实现效果。

@GetMapping(value = "users ") public List<User> getUsers() { return userCache.get(); } 

以上核心的代码完成后我们启动服务测试一下效果:

相关的时间段redis的日志如下图,当服务启动之后,缓存中就有了数据,接口测试可以直接拿到相关数据;当服务关闭之后,缓存数据也一起的清空了。

需要注意的是,这种设计方式适用于单机模式,而对于多实例和分布式服务,必须考虑数据同步的问题。