redis分页缓存--解决了pageNum与size切换造成的资源浪费问题

文章介绍了如何利用Redis的有序集合(ZSet)解决分页缓存中因切换pageNum和size导致的资源浪费问题,包括避免bigKey、优化缓存命中及保持数据顺序。作者还提到了现有方案的常见bug,并分享了自研版本的实现代码。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

redis分页缓存--解决了pageNum与size切换造成的资源浪费问题

最近接到一个需求,需要对不规则数据进行一个分页缓存。

不规则!!!

顾名思义就是,你根本不知道要缓存的数据结构长啥样,有什么字段内容!!!

网上看过好多篇文章都不适用的,干脆自己倒腾一下。并且发现网上的例子存在很多的bug。

例如数据都存放到一个key里面了,容易造成bigKey影响性能,

用户切换或pageNum或size之后无法命中缓存,

缓存的数据没有顺序。

接下来就有了自研版的redis分页缓存,使用zset有序集合来实现,代码思路:
control:

		//请求传来的参数--pageNum当前页码--pageSize大小
		long pageStart = ((long) (pageNum - 1) *pageSize);
		//组装key--可根据具体项目拼接key,这里简单举例
        //总数据量
        String totalCountKey = "TOTAL_COUNT";
        //存储目录
        String cacheDataKey = "SOURCE_TABLE_LIST";

        //有缓存取缓存,无缓存查库
        Object totalCountObj = cacheManager.get(totalCountKey);
        if (!ObjectUtils.isEmpty(totalCountObj)) {
            //取出缓存重点看这里开始!!!
            //计算需要的缓存数据
            Long totalCount = Long.valueOf((String) totalCountObj);
            int currentPage = pageNum;
            if (totalCount<=pageStart){ //防止size切换,造成当前页*size超出边界
                currentPage = Math.min(1,(int)Math.ceil((double) pageStart / (double) pageSize)+1);
                pageStart = ((long) (currentPage - 1) *pageSize);
            }
            //留给大家发挥的空间,根据计算结果,统计结果缓存区间中数据量是否小于所需的数据量且小于等于totalCount,是则分页查库,把空缺的数据插入缓存
            //使用计算后结果取出对应位置缓存数据
            Set<Object> tuples = cacheManager.rangeByScore(cacheDataKey, (double) pageStart, (double) pageStart + pageSize);
            //类型转换,无需理会,各自发挥
            List<TableInfo> cacheDataList = PortalBeanUtils.convertList(Arrays.asList(tuples.toArray()), TableInfo.class);
            if (totalCount != null && cacheDataList != null && cacheDataList.size()>0) {
                //将计算后的当前页码返回给前端,以及保证totalCount是缓存刷新后数据库中的实际数据条数
                return ResponseEntity.ok(new PaginatedResult()
                        .setCode(SuccessCode.DATA_CHECK_SUCCESS.getCode())
                        .setMessage(SuccessCode.DATA_CHECK_SUCCESS.getMessage())
                        .setData(cacheDataList)
                        .setCurrentPage(currentPage)
                        .setPageSize(pageSize)
                        .setTotalCount(totalCount));
            }
        }
        
		//无缓存查库并将结果插入缓存
		//查库代码开始--无需理会------------------------------------------------------!
        Object[] builderResult = SourceDatabaseServiceImpl.buildSql(sourceDatabase);

        String sqlBody = (String) builderResult[0];

        //获取连接--动态数据源查询
        JdbcUtils jdbc = JdbcUtils.newJdbcUtils(sourceDatabase);

        String pageSql = jdbc.getLimitString(sqlBody, pageStart, pageSize);
        Object[] params = (Object[]) builderResult[1];
        List<LinkedHashMap<String, Object>> data = jdbc.selectSortList(pageSql, params);

        List<Object> tableInfoList = new ArrayList<>();
        for (Map<String, Object> rsMap: data) {
            TableInfo tableInfo = PortalBeanUtils.mapToBean(rsMap, TableInfo.class, true);
            tableInfoList.add(tableInfo);
        }

        String countSql = (String) builderResult[2];
        PageHelpDto pageHelpDto = jdbc.pageHelp(countSql, params, pageStart, pageSize);
		//查库代码结束--无需理会--------------------------------------------------------------------!
        //异步加入缓存
        long finalPageStart = pageStart;
        Thread thread = new Thread(() -> {
            cacheManager.set(totalCountKey, pageHelpDto.getTotalCount(), 30L, TimeUnit.MINUTES);
            //插入缓存重点看这里!!!
            cacheManager.zAdd(cacheDataKey, tableInfoList, (double) finalPageStart,30L); //todo test设置30分钟过期
        });
        thread.start();

插入缓存逻辑zAdd:

    /**
     * 同时将多个值存入有序集合中。同时为每个值设置相同的生存时间
     * @param key 键
     * @param objectList 值
     * @param times 生存时间(分钟)
     */
    public void zAdd(String key, List<Object> objectList, double stratScore, Long times) {
        int i = 0;
        for (Object obj : objectList) {
            Double stratScoreInc = stratScore + i;
            redisTemplate.opsForZSet().add(key, obj, stratScoreInc);
            i++;
        }
        redisTemplate.expire(key, times, TimeUnit.MINUTES);
    }

取出缓存逻辑rangeByScore:

    /**
     * 获取有序集合中的缓存
     * @param key 键
     * @param stratScore 开始下标
     * @param endScore 结束下标
     */
    public Set<Object> rangeByScore(String key, Double stratScore, Double endScore){
        Set<Object> values = redisTemplate.opsForZSet().rangeByScore(key, stratScore, endScore);
        return values;
    }

初版代码,有bug欢迎吐槽~
转载请注明出处!!!

好的,下面是一个PageInfo分页查询带Redis缓存的业务逻辑层实现类的示例代码(Java语言): ```java public class UserServiceImpl implements UserService { private final UserDao userDao; private final RedisTemplate<String, Object> redisTemplate; public UserServiceImpl(UserDao userDao, RedisTemplate<String, Object> redisTemplate) { this.userDao = userDao; this.redisTemplate = redisTemplate; } @Override public PageInfo<User> getUsers(int pageNum, int pageSize) { String cacheKey = "user_page_" + pageNum + "_" + pageSize; PageInfo<User> pageInfo = (PageInfo<User>) redisTemplate.opsForValue().get(cacheKey); if (pageInfo == null) { PageHelper.startPage(pageNum, pageSize); List<User> userList = userDao.getUsers(); pageInfo = new PageInfo<>(userList); redisTemplate.opsForValue().set(cacheKey, pageInfo, 5, TimeUnit.MINUTES); // 设置5分钟过期 } return pageInfo; } } ``` 在这个示例中,我们使用了一个`UserDao`对象来访问数据库,同时也注入了一个`RedisTemplate`对象来进行缓存操作。 `getUsers()`方法接受两个参数,`pageNum`表示要查询的页码,`pageSize`表示每页的记录数。首先,我们根据`pageNum`和`pageSize`构建一个缓存键`cacheKey`,然后尝试从Redis缓存中获取用户分页信息。如果缓存中没有该分页信息,则调用`PageHelper`对象设置分页信息,并从`UserDao`对象中获取用户列表。接着,我们使用获取到的用户列表构建一个`PageInfo`对象,并将其存储到Redis缓存中,并设置过期时间为5分钟。最后,我们返回用户分页信息。 使用`Redis`作为缓存可以提高系统的可扩展性和可靠性,可以实现分布式系统的缓存共享。需要注意的是,缓存的过期时间应该根据数据的实时性和数据量大小来进行设置,以避免缓存数据过期或者占用过多内存的问题
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值