缓存的实现方式

HanGR 于 2024-09-15 发布


背景


缓存的方式

  1. 本地缓存

    • 基于内存的缓存, 如HashMap, LinkedHashMap, Guava Cache等.
  2. 远程缓存

    • 分布式缓存: 如Redis, Memcached, Etcd(云原生架构的分布式缓存)等.
    • Java进程缓存: 如Ehcache(单机), Caffeine(号称最快的缓存库), Google Guava等.


Redis缓存


使用Redis缓存注意事项

  1. 设计缓存的key

    • 一般来说, 我们需要缓存的数据的key应该是唯一的, 并且能够反映出数据的内容.
    • 格式: systemId:moduleId:func:options(系统ID:模块ID:功能:参数, 可以根据实际情况进行调整, 原则是不要和其他数据冲突)
  2. 缓存的过期时间

    • 一定要设置过期时间, redis的内存不能无限扩大, 所以需要设置合理的过期时间.
    • 缓存的过期时间应该根据数据的重要性和更新频率来设置.
    • 对于不经常更新的数据, 可以设置较短的过期时间, 如10分钟.
    • 对于经常更新的数据, 可以设置较长的过期时间, 如1天.
    • 对于一些重要的数据, 可以设置更长的过期时间, 如30天.


问题延伸

  1. 问题描述

    • redis缓存解决了查询效率的问题, 但是缓存的命中率并不一定高, 比如缓存删除之后,第一个查询还是会到数据库查询, 数据量大的话, 用户还是会看到延迟.
  2. 解决方案

    • 可以使用缓存预热


缓存预热

  1. 缓存预热的优点

    • 降低了首次加载的延迟, 提高了用户体验
    • 缓存预热后, 缓存命中率提高, 降低了数据库的压力
  2. 缓存预热的缺点

    • 增加开发成本, 需要额外的开发、设计
    • 预热的时机和时间如果设置不当, 可能会造成缓存数据不正确.
    • 缓存预热的缓存数据量不能太大, 否则会占用过多的内存.
  3. 缓存预热的实现方式

    • (1) 定时缓存预热: 定时任务定期访问数据库, 将热点数据加载到缓存中.
    • (2) 模拟触发(手动触发): 在代码中主动访问数据库, 将热点数据加载到缓存中.
  4. 定时缓存预热的实现方式

    • (1) Spring Scheduler(spring boot 默认整合了, 最常用)
    • (2) Quartz(独立于 Spring 存在的定时任务框架)
    • (3) XXL-Job 之类的分布式任务调度平台(界面 + sdk), 非常值得学习, https://github.com/xuxueli/xxl-job


使用Spring Scheduler实现定时缓存预热

  1. 主类添加注解@EnableScheduling

       @EnableScheduling // 开启定时任务
       public class MainApplication {
          
         public static void main(String[] args) {
         SpringApplication.run(MainApplication.class, args);
       }
     }
    
  2. 给要定时执行的方法添加 @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);
               }
           }
       }
     }