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欢迎吐槽~
转载请注明出处!!!