文
章
目
录
章
目
录
在业务系统中,为了降低对业务数据库的负载压力,我们需要在程序启动时将一些常用数据主动加载到内存数据库,这就是我们通常所说的缓存预热策略。
官方定义如下:
缓存预热是一种策略,它在程序启动或缓存失效后主动将热点数据加载到缓存中。通过这种方式,当实际请求到达程序时,热点数据已经存在于缓存中,从而有助于减少缓存穿透和缓存击穿的情况,同时减轻了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
的日志如下图,当服务启动之后,缓存中就有了数据,接口测试可以直接拿到相关数据;当服务关闭之后,缓存数据也一起的清空了。
需要注意的是,这种设计方式适用于单机模式,而对于多实例和分布式服务,必须考虑数据同步的问题。