分页列表缓存,你真的会吗

news2024/11/26 8:39:01

看了很多关于缓存的文章,其中多级缓存思路,分页列表缓存这些知识点给了我很大的启发性。

写这篇文章,我们聊聊分页列表缓存,希望能帮助大家提升缓存技术认知。

1 直接缓存分页列表结果

显而易见,这是最简单易懂的方式。

我们按照不同的分页条件来缓存分页结果 ,伪代码如下:

public List<Product> getPageList(String param,int page,int size) {
  String key = "productList:page:" + page + ”size:“ + size + 
               "param:" + param ;
  List<Product> dataList = cacheUtils.get(key);
  if(dataList != null) {
    return dataList;
  }
  dataList = queryFromDataBase(param,page,size);
  if(dataList != null) {
       cacheUtils.set(key , dataList , Constants.ExpireTime);
  }
} 

这种方案的优点是工程简单,性能也快,但是有一个非常明显的缺陷基因:列表缓存的颗粒度非常大

假如列表中数据发生增删,为了保证数据的一致性,需要修改分页列表缓存。

有两种方式 :

1、依靠缓存过期来惰性的实现 ,但业务场景必须包容;

2、使用 Redis 的 keys 找到该业务的分页缓存,执行删除指令。 但 keys 命令对性能影响很大,会导致 Redis 很大的延迟 。

生产环境使用 keys 命令比较危险,发生事故的几率高,非常不推荐使用

2 查询对象 ID 列表,再缓存每个对象条目

缓存分页结果虽然好用,但缓存的颗粒度太大,保证数据一致性比较麻烦。

所以我们的目标是更细粒度的控制缓存 。

我们查询出商品分页对象 ID 列表,然后为每一个商品对象创建缓存,通过商品 ID 和商品对象缓存聚合成列表返回给前端。

伪代码如下:

核心流程:

1、从数据库中查询分页 ID 列表

// 从数据库中查询分页商品 ID 列表
List<Long> productIdList = queryProductIdListFromDabaBase(
                           param, 
                           page, 
                           size);

对应的 SQL 类似:

SELECT id FROM products
ORDER BY id 
LIMIT (page - 1) * size , size 

2、批量从缓存中获取商品对象

Map<Long, Product> cachedProductMap = cacheUtils.mget(productIdList);

假如我们使用本地缓存,直接一条一条从本地缓存中聚合也极快。

假如我们使用分布式缓存,Redis 天然支持批量查询的命令 ,比如 mget ,hmget 。

3、组装没有命中的商品 ID

List<Long> noHitIdList = new ArrayList<>(cachedProductMap.size());
for (Long productId : productIdList) {
     if (!cachedProductMap.containsKey(productId)) {
         noHitIdList.add(productId);
     }
}

因为缓存中可能因为过期或者其他原因导致缓存没有命中的情况,所以我们需要找到哪些商品没有在缓存里。

4、批量从数据库查询未命中的商品信息列表,重新加载到缓存

首先从数据库里批量查询出未命中的商品信息列表 ,请注意是批量

List<Product> noHitProductList = batchQuery(noHitIdList);

参数是未命中缓存的商品 ID 列表,组装成对应的 SQL,这样性能更快 :

SELECT * FROM products WHERE id IN
                         (1,
                          2,
                          3,
                          4);

然后这些未命中的商品信息存储到缓存里,使用 Redis 的 mset 命令。

//将没有命中的商品加入到缓存里
Map<Long, Product> noHitProductMap =
         noHitProductList.stream()
         .collect(
           Collectors.toMap(Product::getId, Function.identity())
         );
cacheUtils.mset(noHitProductMap);
//将没有命中的商品加入到聚合map里
cachedProductMap.putAll(noHitProductMap);

5、 遍历商品 ID 列表,组装对象列表

for (Long productId : productIdList) {
    Product product = cachedProductMap.get(productId);
    if (product != null) {
       result.add(product);
    }
}

当前方案里,缓存都有命中的情况下,经过两次网络 IO ,第一次数据库查询 IO ,第二次 Redis 查询 IO , 性能都会比较好。

所有的操作都是批量操作,就算有缓存没有命中的情况,整体速度也较快。

” 查询对象 ID 列表,再缓存每个对象条目 “ 这个方案比较灵活,当我们查询对象 ID 列表,可以不限于数据库,还可以是搜索引擎,Redis 等等。

下图是开源中国的搜索流程:

精髓在于:搜索的分页结果只包含业务对象 ID ,对象的详细资料需要从缓存 + MySQL 中获取。

3 缓存对象 ID 列表,同时缓存每个对象条目

笔者曾经重构过类似朋友圈的服务,进入班级页面 ,瀑布流的形式展示班级成员的所有动态。

<img src="https://oscimg.oschina.net/oscnet/up-cd84271f048bae099798d4b6b19593e9ab3.png" alt="班级圈" style="zoom:50%;" />

我们使用推模式将每一条动态 ID 存储在 Redis ZSet 数据结构中 。Redis ZSet 是一种类型为有序集合的数据结构,它由多个有序的唯一的字符串元素组成,每个元素都关联着一个浮点数分值。

ZSet 使用的是 member -> score 结构 :

  • member : 被排序的标识,也是默认的第二排序维度( score 相同时,Redis 以 member 的字典序排列)
  • score : 被排序的分值,存储类型是 double

如上图所示:ZSet 存储动态 ID 列表,member 的值是动态编号,score 值是创建时间

通过 ZSet 的 ZREVRANGE 命令就可以实现分页的效果。

ZREVRANGE 是 Redis 中用于有序集合(sorted set)的命令之一,它用于按照成员的分数从大到小返回有序集合中的指定范围的成员。

为了达到分页的效果,传递如下的分页参数 :

通过 ZREVRANGE 命令,我们可以查询出动态 ID 列表。

查询出动态 ID 列表后,还需要缓存每个动态对象条目,动态对象包含了详情,评论,点赞,收藏这些功能数据 ,我们需要为这些数据提供单独做缓存配置。

无论是查询缓存,还是重新写入缓存,为了提升系统性能,批量操作效率更高。

若 ** 缓存对象结构简单,使用 mget 、hmget 命令;若结构复杂,可以考虑使用 pipleline,Lua 脚本模式 。** 笔者选择的批量方案是 Redis 的 pipleline 功能。

我们再来模拟获取动态分页列表的流程:

  1. 使用 ZSet 的 ZREVRANGE 命令 ,传入分页参数,查询出动态 ID 列表 ;
  2. 传递动态 ID 列表参数,通过 Redis 的 pipleline 功能从缓存中批量获取动态的详情,评论,点赞,收藏这些功能数据 ,组装成列表 。

4 总结

本文介绍了实现分页列表缓存的三种方式:

  1. 直接缓存分页列表结果

  2. 查询对象 ID 列表,只缓存每个对象条目

  3. 缓存对象 ID 列表,同时缓存每个对象条目

这三种方式是一层一层递进的,要诀是:

细粒度的控制缓存批量加载对象

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/765554.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

终点与起点,“玄魂工作室”的最后一篇推文

本文原载于 微信公众号 “VisActor”&#xff0c;https://mp.weixin.qq.com/s/tAjcPT8FjIQ6qbhSNWr2hw 做一个决绝的人 用了一天时间&#xff0c;我手工删除了500多篇原创文章&#xff0c;200多篇转载文章&#xff0c;删除了所有菜单、清空了所有自动回复、修改了公众号简介、…

JS事件及相关操作

JS如何处理事件&#xff1a;&#xff08;有三种处理方式&#xff09; this指向事件源 this指向事件源&#xff1a; this指向事件源&#xff1a; 冒泡流是从里向外&#xff0c;一层一层向父级抛&#xff0c;目前只有ie浏览器支持 捕获流是从外向里

2023年7月济南/郑州/深圳DAMA-CDGP数据治理专家认证报名

DAMA认证为数据管理专业人士提供职业目标晋升规划&#xff0c;彰显了职业发展里程碑及发展阶梯定义&#xff0c;帮助数据管理从业人士获得企业数字化转型战略下的必备职业能力&#xff0c;促进开展工作实践应用及实际问题解决&#xff0c;形成企业所需的新数字经济下的核心职业…

C++ 南科大于仕琪课程

目录 课程信息 7.4 function pointers 7.5 递归函数(Recursive functions) 课程信息 作者&#xff1a;于仕琪(南方科技大学计算机系副教授/opencv中国团队负责人) 视频地址&#xff1a;快速学习C和C&#xff0c;基础语法和优化策略&#xff0c;学了不再怕指针(南科大计算机…

Android事件高级手势处理

原文链接 Android事件高级手势处理 GestureDetector只能帮我们处理并识别一些常用的简单的手势&#xff0c;如点击&#xff0c;双击&#xff0c;长按&#xff0c;滑动&#xff08;Scroll&#xff09;和快速滑动&#xff08;Fling&#xff09;等&#xff0c;一般情况下&#xf…

js 实现导入导出功能(FileSave.js)

点击导出按钮弹出文件框导出文件&#xff0c;点击导入选择文件获取文件数据&#xff0c;以下我保存的是json。 html&#xff1a; <div class"layui-inline"> <div class"layui-btn-group"><button type"button" class"la…

文心一言 VS 讯飞星火 VS chatgpt (61)-- 算法导论6.4 5题

文心一言 VS 讯飞星火 VS chatgpt &#xff08;61&#xff09;-- 算法导论6.4 5题 五、证明:在所有元素都不同的情况下&#xff0c;HEAPSORT 的时间复杂度是 Ω (nlgn)。 文心一言&#xff1a; 要证明在所有元素都不同的情况下&#xff0c;HEAPSORT 的时间复杂度是 Ω(n log…

音视频H265编码; Socket通信实现N对N连接代码示例

H.265编码和Socket通信是两个不同的概念&#xff0c;它们分别涉及视频编码和网络通信。在实现N对N连接时&#xff0c;您可以将它们结合起来&#xff0c;但要注意每个方面的具体实现。 H.265编码&#xff08;视频编码&#xff09;&#xff1a; H.265编码涉及将视频数据进行压缩…

速腾robosense-sdk配置和使用方法

官方的安装和配置教程https://github.com/RoboSense-LiDAR/rslidar_sdk/ 1.手动下载sdk&#xff0c;下载tar.gz&#xff0c;然后解压缩:Releases RoboSense-LiDAR/rslidar_sdk GitHub 2.个人电脑连接速腾激光雷达后&#xff0c;确保能够ping通。当连接网线后&#xff0c;电…

使用 appium 进行微信小程序的自动化测试

目录 前言&#xff1a; 微信小程序结构 自动化用例的调整 示例代码 后记 前言&#xff1a; 微信小程序是一种流行的移动应用程序&#xff0c;它在移动设备上提供了丰富的功能和用户体验。为了确保微信小程序的质量和稳定性&#xff0c;自动化测试是必不可少的一环。Appiu…

视频融合平台EasyCVR级联后上级平台播放失败的问题排查与优化

EasyCVR视频融合平台基于云边端智能协同架构&#xff0c;具有强大的数据接入、处理及分发能力&#xff0c;平台可提供视频监控直播、云端录像、云存储、录像检索与回看、智能告警、平台级联、云台控制等视频能力与服务&#xff0c;可支持多协议、多类型的海量设备接入与分发。 …

MATLAB数据类型及代码实现

本推文是MATLAB基础与统计实战课程中的S02-1数据类型及代码实现 矩阵(Matrix) MATLAB最基础的数据单位是矩阵。什么是矩阵&#xff1f; 如下图可以看作M行乘以N列的数的组。这就是矩阵最基础的显示 ■区别于其他数据分 析软件或者编程语言的最大一 个特点(如&#xff0c;转…

DOM4j及源码分析

文章目录 DOM4jXML 解析技术原理XML 解析技术介绍 DOM4J 介绍DOM4j 中&#xff0c;获得 Document 对象的方式有三种源码增删改查代码 DOM4j 文档: https://dom4j.github.io/javadoc/1.6.1/ 本地文档: dom4j-1.6.1\docs\index.html XML 解析技术原理 不管是 html 文件还是 x…

OAuth2.0详细介绍与实践(通俗易懂)

一、OAuth2.0介绍 1.1 概述 OAUTH协议为用户资源的授权提供了一个安全的、开放而又简易的标准。与以往的授权方式不同之处是OAUTH的授权不会使第三方触及到用户的帐号信息&#xff08;如用户名与密码&#xff09;&#xff0c;即第三方无需使用用户的用户名与密码就可以申请获…

[Juc进阶]Callable、Future和FutureTask

一、Callable 与 Runnable 先说一下java.lang.Runnable吧&#xff0c;它是一个接口&#xff0c;在它里面只声明了一个run()方法&#xff1a; public interface Runnable {public abstract void run(); }由于run()方法返回值为void类型&#xff0c;所以在执行完任务之后无法返…

多个文件保存位置不同:如何一键批量重命名为相同名称

在日常工作中&#xff0c;我们会经常遇到需要修改文件名称&#xff0c;文件改名方法也是很多种呢&#xff0c;可以手动修改或使用工具批量重命名&#xff0c;一般大家修改文件或文件夹重命名&#xff0c;都是在同一个保存位置&#xff0c;有没有遇到多个文件保存位置不同&#…

抖音账号矩阵系统源码.搭建技术开发分享

技术自研框架开发背景&#xff1a; 抖音账号矩阵系统是一种基于数据分析和管理的全新平台&#xff0c;能够帮助用户更好地管理、扩展和营销抖音账号。 部分源码分享&#xff1a; //计算分页$active_list_all $Video_model->getCount($where);$page_libs new Libs_Pagin…

Android平台如何高效率实现GB28181对接?

技术背景 GB28181协议是一种用于设备状态信息报送的协议&#xff0c;可以在不同设备之间进行通信和数据传输。 在安卓系统上实现GB/T 28181非常必要&#xff0c;GB28181协议实现分两部分&#xff0c;一部分是信令&#xff0c;另外一部分就是媒体数据的编码。 信令主要包括S…

CenterNet Objects as Points 论文学习

论文链接&#xff1a;Objects as Points 1. 解决了什么问题&#xff1f; 目标检测的任务是从图像中检出目标的矩形框。现有的检测方法大多会穷举所有潜在的目标位置&#xff0c;然后做分类。这非常浪费资源、低效率&#xff0c;并且依赖后处理。单阶段方法会在图像上放置大量…

049、事务设计之分布式基本原理

隔离级别 iso定义的隔离级别 可串行化 可重复读 读已提交 读未提交 隔离级别区分的现象 脏读&#xff1a; 一个事务读取另一个未提交的事务所做更改 不可重复度 &#xff1a;同一事务中&#xff0c;前后执行相同的语句&#xff0c;出来的记录不一样 幻读&#xff1a; 同一事务…