3种分页列表缓存方式,速收藏~

分页,列表,缓存,方式,收藏 · 浏览次数 : 147

小编点评

**分页列表缓存的三种实现方式** **1. 直接缓存分页列表结果** * 缓存策略:将分页列表的结果直接存储在缓存中。 * 优点:简单易实现,性能快。 * 缺点:列表缓存的颗粒度很大,当列表中数据发生增删时,需要修改缓存,影响效率。 **2. 依靠缓存过期来惰性实现** * 策略:设置缓存超时时间,当缓存超过该时间时,重新获取列表结果。 * 优点:避免频繁网络访问,提高性能。 * 缺点:缓存命中率降低,性能可能下降。 **3. 使用 Redis 的 keys 找到该业务的分页缓存** * 策略:使用 Redis 的 keys 找到该业务的分页缓存,并从缓存中获取结果。 * 优点:降低网络访问量,提高性能。 * 缺点:Redis 的 keys 命令对性能影响很大,会导致 Redis 很大的延迟。 **目标:更细粒度的控制缓存** * 采用细粒度的控制缓存,根据商品ID或其他条件,动态创建缓存。 * 使用缓存聚合技术,将多个商品对象的信息缓存到同一个缓存中。 * 使用 Redis 的 mget 或 hmget 命令,以进行批量查询,以降低网络访问量。

正文

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

本文分享自华为云社区《分页列表缓存,你真的会吗》,作者: 勇哥java实战分享 。

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列表,同时缓存每个对象条目

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

我们使用推模式将每一条动态 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列表,同时缓存每个对象条目

这三种方式是一层一层递进的,要诀是:细粒度的控制缓存和批量加载对象。

 

点击关注,第一时间了解华为云新鲜技术~

与3种分页列表缓存方式,速收藏~相似的内容:

3种分页列表缓存方式,速收藏~

摘要:本文介绍了实现分页列表缓存的三种方式。 本文分享自华为云社区《分页列表缓存,你真的会吗》,作者: 勇哥java实战分享 。 1 直接缓存分页列表结果 显而易见,这是最简单易懂的方式。 我们按照不同的分页条件来缓存分页结果 ,伪代码如下: public List getPage

Python 列表操作指南1

Python 列表 mylist = ["apple", "banana", "cherry"] 列表用于在单个变量中存储多个项目。列表是 Python 中的 4 种内置数据类型之一,用于存储数据集合,其他 3 种分别是元组(Tuple)、集合(Set)和字典(Dictionary),它们具有不同的

[转帖]关于nginx 反向代理upstream中的 keepalive配置

一、关于nginx upstream 在nginx的模块中,分为3种类型,分别是handler,filter和upstream,其中upstream可以看做一种特殊的handler,它主要用来实现和后端另外的服务器进行通信,由于在nginx中全部都是使用非阻塞,并且是一个流式的处理,所以upstre

[转帖]服务器体系(SMP, NUMA, MPP)与共享存储器架构(UMA和NUMA)

1 3种系统架构与2种存储器共享方式 1.1 架构概述 从系统架构来看,目前的商用服务器大体可以分为三类 对称多处理器结构(SMP:Symmetric Multi-Processor) 非一致存储访问结构(NUMA:Non-Uniform Memory Access) 海量并行处理结构(MPP:Ma

介绍3种ssh远程连接的方式

摘要:SSH(安全外壳协议 Secure Shell Protocol,简称SSH)是一种加密的网络传输协议,用于在网络中实现客户端和服务端的连接,典型的如我们在本地电脑通过 SSH连接远程服务器。 本文分享自华为云社区《ssh 远程连接方式总结》,作者:嵌入式视觉。 SSH(安全外壳协议 Secu

高并发环境下3种方式优化Tomcat性能

摘要:Tomcat作为最常用的Java Web服务器,随着并发量越来越高,Tomcat的性能会急剧下降,那有没有什么方法来优化Tomcat在高并发环境下的性能呢? 本文分享自华为云社区《【高并发】高并发环境下优化Tomcat性能》,作者: 冰 河 。 写在前面 Tomcat作为最常用的Java We

带你熟悉3种AQS的线程并发工具的用法

摘要:AQS 的全称为(AbstractQueuedSynchronizer),AQS 是一个用来构建锁和同步器的框架,使用 AQS 能简单且高效地构造出应用广泛的大量的同步器。 本文分享自华为云社区《【高并发】AQS中的CountDownLatch、Semaphore与CyclicBarrier核

带你认识3个J.U.C组件扩展

摘要:本文主要为大家讲解3种J.U.C组件扩展。 本文分享自华为云社区《【高并发】J.U.C组件扩展》,作者: 冰 河。 1.FutureTask FutureTask是J.U.C(java.util.concurrent)下的,但不是AQS(AbstractQueuedSynchronizer)的

在audio DSP中如何做软件固化

在audio DSP中, 软件的code和data主要放在3种不同的memory上,分别是片内的ITCM、DTCM和片外的memory(比如DDR)上。ITCM只能放code,DTCM只能放data,片外的memory既能放code也能放data。在写代码时要规划好哪些放片内,哪些放片外。上面说的这

synchronized原理-字节码分析、对象内存结构、锁升级过程、Monitor

本文分析的问题: synchronized 字节码文件分析之 monitorenter、monitorexit 指令 为什么任何一个Java对象都可以成为一把锁? 对象的内存结构 锁升级过程 Monitor 是什么、源码查看 字节码分析 synchronized的3种使用方式 作用于实例方法,对对象