基于 RedisTemplate 的分页缓存设计

场景:某后台查询业务涵盖分页+条件搜索,那么我们需要设计一个缓存来有效存储检索数据,且基于 RedisTemplate 的分页缓存设计

核心:分页缓存键设计,我需要考虑如何将查询条件转化为缓存键的一部分。通常,处理这种情况的方法是对查询条件进行哈希处理,生成一个唯一的字符串作为键的一部分。这样,不同的查询条件会有不同的哈希值,从而避免键的冲突。例如,用户可能有多个查询参数,如作者、状态、日期范围等,这些参数组合起来应该生成唯一的键

设计规范:模块名:业务类型:页码:页大小:条件哈希

我们自定义RedisUtil工具,此工具功能

  • 统一缓存键(key)的创建格式

  • 删除缓存键(key)

    /**

    • Redis统一键命名规范

    • 分页缓存(键类型) 模块名:业务类型:page_{页码}size{页数}_queryhash 如 user:list:page_1_size_10_abcd123

    • 详情缓存(键类型)模块名:业务类型:id_{ID} 如 user:detail:id_1001

    • 统计缓存(键类型)模块名:statistics:类型 如 order:statistics:daily
      */
      public class RedisUtil {

      private final RedisTemplate<String, Object> redisTemplate;

      public RedisUtil(RedisTemplate<String, Object> redisTemplate) {
      this.redisTemplate = redisTemplate;
      }

      /**

      • 清理指定模块的所有缓存
      • @param module 模块名(如 "user", "order")
        /
        public void cleanModuleCache(String module) {
        deleteByPattern(module + ":
        ");
        }

      /**

      • 清理模块下特定业务类型缓存
      • @param module 模块名
      • @param bizType 业务类型(如 "list", "detail")
        /
        public void cleanBizTypeCache(String module, String bizType) {
        deleteByPattern(module + ":" + bizType + ":
        ");
        }

      /**

      • 通用清理方法(支持任意模式)

      • @param pattern

      • @return
        */
        public long deleteByPattern(String pattern) {
        return redisTemplate.execute((RedisCallback<Long>) connection -> {
        List<byte[]> keysToDelete = new ArrayList<>();

        复制代码
         ScanOptions options = ScanOptions.scanOptions()
                 .match(pattern)
                 .count(500) // 每批扫描500个键
                 .build();
        
         connection.scan(options).forEachRemaining(keyBytes -> {
             String key = new String(keyBytes, StandardCharsets.UTF_8);
             // 添加额外验证逻辑(可选)
             if (isValidKey(key)) {
                 keysToDelete.add(keyBytes);
             }
         });
        
         if (!keysToDelete.isEmpty()) {
             connection.del(keysToDelete.toArray(new byte[0][]));
         }
         return (long) keysToDelete.size();

        });
        }

      /**

      • 验证键格式合法性(防止误删)
      • @param key
      • @return
        /
        private boolean isValidKey(String key) {
        // 示例验证:必须包含至少两级分类(如 "user:list:
        ")
        return key.matches("^\w+:\w+:.*");
        }

      /**

      • 生成分页缓存键, 如: user:list:1:10:abcd123
      • @param module
      • @param page
      • @param size
      • @param query
      • @return
        */
        public String generatePageKey(String module, int page, int size, Object query) {
        String queryHash = generateConditionHash(query);
        return String.format("%s:%d:%d:%s", module, page, size, queryHash);
        }

      /**

      • 生成条件哈希值
      • @param query
      • @return
        */
        private String generateConditionHash(Object query) {
        if (query == null) return "no_condition";
        try {
        String json = new ObjectMapper().writeValueAsString(query);
        return DigestUtils.md5DigestAsHex(json.getBytes());
        } catch (JsonProcessingException e) {
        throw new RuntimeException("生成条件哈希失败", e);
        }
        }
        }

通过上面的RedisUtil工具,我们将缓存键场景通过下面列表进行总结

场景 缓存键示例 操作流程
基础分页查询 TrainManageCache:1:10:no_condition 直接使用页码和分页大小生成键
带状态过滤的分页 TrainManageCache:2:20:d3e5f7a9 将查询条件序列化为哈希值
多条件复杂查询 TrainManageCache:1:10:8c2b4a6d 确保所有条件参数参与哈希计算
排序分页 TrainManageCache:3:15:7e9f1d3a 包含排序字段和方向的哈希值

示例:我们能也可以扩展一个分页缓存工具

复制代码
@Component
public class PageCacheUtil {

    @Autowired
    private RedisTemplate<String, Object> redisTemplate;

    /​**​
     * 写入分页缓存
     * @param key    缓存键
     * @param page   分页数据对象(需包含 total 等元数据)
     * @param ttl    过期时间(单位:分钟)
     */
    public void setPageCache(String key, Page<?> page, long ttl) {
        // 使用 GenericJackson2JsonRedisSerializer 确保类型信息保留
        redisTemplate.opsForValue().set(
            key, 
            page, 
            Duration.ofMinutes(ttl)
        );
    }

    /​**​
     * 读取分页缓存
     * @param key 缓存键
     * @return Page 对象(反序列化失败返回 null)
     */
    public Page<?> getPageCache(String key) {
        try {
            return (Page<?>) redisTemplate.opsForValue().get(key);
        } catch (Exception e) {
            // 处理反序列化异常(如旧数据格式不兼容)
            return null;
        }
    }
}

业务层调用

复制代码
@Service
public class TrainService {

    @Autowired
    private TrainMapper trainMapper;
    @Autowired
    private RedisTemplate redisTemplate;
    
    private static final String CACHE_MODULE = "TrainManageCache";
    private static final int DEFAULT_TTL = 30; // 缓存30分钟

    /​**​
     * 分页查询(带缓存逻辑)
     */
    public Page<Train> queryTrainPage(int page, int size, TrainQuery query) {
        RedisUtil redisUtil = new RedisUtil(redisTemplate);
        // 生成缓存键
        String cacheKey = redisUtil.generatePageKey(
            CACHE_MODULE, page, size, query
        );

        // 尝试读取缓存
        Page<Train> cachedPage = (Page<Train>) pageCacheUtil.getPageCache(cacheKey);
        if (cachedPage != null) return cachedPage;

        // 缓存未命中,查询数据库
        PageHelper.startPage(page, size);
        List<Train> data = trainMapper.selectByQuery(query);
        Page<Train> resultPage = (Page<Train>) data;

        // 写入缓存
        pageCacheUtil.setPageCache(cacheKey, resultPage, DEFAULT_TTL);

        return resultPage;
    }
}

安全与优化

优化项 实现方式
空条件处理 对无查询条件的情况生成统一哈希(no_condition)
动态 TTL 根据查询频率设置不同过期时间(高频查询设置更长 TTL)
防雪崩策略 对缓存设置随机偏移的过期时间(如 ttl + random.nextInt(10))
空值缓存 对查询结果为空的场景也进行短期缓存(防止频繁穿透)
限流降级 当缓存服务异常时,直接走数据库查询并记录告警
相关推荐
程序员张34 小时前
Maven编译和打包插件
java·spring boot·maven
Hello.Reader5 小时前
Redis 延迟监控深度指南
数据库·redis·缓存
ybq195133454315 小时前
Redis-主从复制-分布式系统
java·数据库·redis
weixin_472339465 小时前
高效处理大体积Excel文件的Java技术方案解析
java·开发语言·excel
小毛驴8506 小时前
Linux 后台启动java jar 程序 nohup java -jar
java·linux·jar
枯萎穿心攻击6 小时前
响应式编程入门教程第二节:构建 ObservableProperty<T> — 封装 ReactiveProperty 的高级用法
开发语言·unity·c#·游戏引擎
DKPT6 小时前
Java桥接模式实现方式与测试方法
java·笔记·学习·设计模式·桥接模式
Eiceblue7 小时前
【免费.NET方案】CSV到PDF与DataTable的快速转换
开发语言·pdf·c#·.net
好奇的菜鸟8 小时前
如何在IntelliJ IDEA中设置数据库连接全局共享
java·数据库·intellij-idea
tan180°8 小时前
MySQL表的操作(3)
linux·数据库·c++·vscode·后端·mysql