背景
- 之前提到过在数据库查询中, 我们可以使用缓存来提高查询效率, 减少数据库的压力.
缓存的方式
- 
    本地缓存 - 基于内存的缓存, 如HashMap, LinkedHashMap, Guava Cache等.
 
- 
    远程缓存 - 分布式缓存: 如Redis, Memcached, Etcd(云原生架构的分布式缓存)等.
- Java进程缓存: 如Ehcache(单机), Caffeine(号称最快的缓存库), Google Guava等.
 
Redis缓存
- 在分布式缓存中, Redis是最常用的缓存服务器.
- 了解redis的基本概念和使用方法, 可以查看文章: redis知识
使用Redis缓存注意事项
- 
    设计缓存的key - 一般来说, 我们需要缓存的数据的key应该是唯一的, 并且能够反映出数据的内容.
- 格式: systemId:moduleId:func:options(系统ID:模块ID:功能:参数, 可以根据实际情况进行调整, 原则是不要和其他数据冲突)
 
- 
    缓存的过期时间 - 一定要设置过期时间, redis的内存不能无限扩大, 所以需要设置合理的过期时间.
- 缓存的过期时间应该根据数据的重要性和更新频率来设置.
- 对于不经常更新的数据, 可以设置较短的过期时间, 如10分钟.
- 对于经常更新的数据, 可以设置较长的过期时间, 如1天.
- 对于一些重要的数据, 可以设置更长的过期时间, 如30天.
 
问题延伸
- 
    问题描述 - redis缓存解决了查询效率的问题, 但是缓存的命中率并不一定高, 比如缓存删除之后,第一个查询还是会到数据库查询, 数据量大的话, 用户还是会看到延迟.
 
- 
    解决方案 - 可以使用缓存预热
 
缓存预热
- 缓存预热是指将热点数据提前加载到缓存中, 这样当第一个用户请求到来时, 缓存就有数据可以直接返回, 避免了延迟.
- 
    缓存预热的优点 - 降低了首次加载的延迟, 提高了用户体验
- 缓存预热后, 缓存命中率提高, 降低了数据库的压力
 
- 
    缓存预热的缺点 - 增加开发成本, 需要额外的开发、设计
- 预热的时机和时间如果设置不当, 可能会造成缓存数据不正确.
- 缓存预热的缓存数据量不能太大, 否则会占用过多的内存.
 
- 
    缓存预热的实现方式 - (1) 定时缓存预热: 定时任务定期访问数据库, 将热点数据加载到缓存中.
- (2) 模拟触发(手动触发): 在代码中主动访问数据库, 将热点数据加载到缓存中.
 
- 
    定时缓存预热的实现方式 - (1) Spring Scheduler(spring boot 默认整合了, 最常用)
- (2) Quartz(独立于 Spring 存在的定时任务框架)
- (3) XXL-Job 之类的分布式任务调度平台(界面 + sdk), 非常值得学习, https://github.com/xuxueli/xxl-job
 
使用Spring Scheduler实现定时缓存预热
- 
    主类添加注解@EnableScheduling @EnableScheduling // 开启定时任务 public class MainApplication { public static void main(String[] args) { SpringApplication.run(MainApplication.class, args); } }
- 
    给要定时执行的方法添加 @Scheduling 注解,指定 cron 表达式或者执行频率 @Component @Slf4j public class PreCacheJob { @Resource private UserService userService; @Resource private RedisTemplate<String, Object> redisTemplate; // 重点用户 private List<Long> mainUserList = Arrays.asList(1L); // 每天执行,预热推荐用户 @Scheduled(cron = "0 45 14 * * *") //自己设置时间测试 public void doCacheRecommendUser() { //查数据库 for (Long userId : mainUserList) { QueryWrapper<User> queryWrapper = new QueryWrapper<>(); Page<User> userPage = userService.page(new Page<>(1, 20), queryWrapper); String redisKey = String.format("hjx:user:recommend:%s", userId); ValueOperations valueOperations = redisTemplate.opsForValue(); //写缓存,30s过期 try { valueOperations.set(redisKey, userPage, 30000, TimeUnit.MILLISECONDS); } catch (Exception e) { log.error("redis set key error", e); } } } }