《Redis核心技术与实战》学习总结(2)
【Redis】| 总结/Edison Zhou
1上一篇的遗留问题
上一篇总结了一个KV数据库的基本架构 和 Redis的底层数据结构概览,重点总结了Sorted Set的两个数据结构的切换,但没有介绍List的两个数据结构的切换,因此本文试着总结一下。
这里先直接给出答案:
从上图可以看到,当List的数据满足下面两个条件时,就会使用压缩列表,否则使用双向链表。
(1)列表对象保存的所有字符串元素的长度都小于64字节;
(2)列表对象保存的元素数量小于512个;
这两个参数其实也是可以在redis.conf中修改的:
list-max-ziplist-value 64
list-max-ziplist-entries 512
2Redis 3.2之前的实现
由上一篇已经知道,List类型的底层实现包括了 双向链表 和 压缩列表,但这是在Redis的3.2版本之前的底层实现。而从Redis 3.2版本开始,Redis修改了List的底层实现,将压缩列表 和 双向链表 结合,我们称它为 quickList 快速列表。
从第一节的内容我们已经知道,当创建一个新的List时,Redis会优先使用压缩列表,然后在有需要的时候,再转成双向链表。
Redis为什么要这么设计呢?
因为,双向链表的内存占用 比 压缩列表 多,而压缩列表的设计初衷就在于 节约内存。众所周知,Redis之所以快的原因之一就是它是内存数据库,所有操作都在内存上完成,因此对于内存的占用有要求。
双向链表
压缩列表
画外音:在Redis 3.2 之前,我们也可以通过命令来验证:
192.168.80.100:6379> rpush testkey "edison" "andy" "leo"
3
192.168.80.100:6379> object encoding testkey
ziplist
192.168.80.100:6379> rpush testkey "wwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwww"
4
192.168.80.100:6379> object encoding testkey
linkedlist
那么,压缩列表为什么占用内存少呢?
其实从上面的图和下面的源码也可以看出来,压缩列表并没有维护双向指针prev 和 next,而只是存储了上一个entry的长度 和 下一个entry的长度,通过长度来推算下一个entry在哪里。
typedef struct zlentry { // 压缩列表节点unsigned int prevrawlensize, prevrawlen; // prevrawlen是前一个节点的长度,prevrawlensize是指prevrawlen的大小,有1字节和5字节两种unsigned int lensize, len; // len为当前节点长度 lensize为编码len所需的字节大小unsigned int headersize; // 当前节点的header大小unsigned char encoding; // 节点的编码方式unsigned char *p; // 指向节点的指针
} zlentry;
这是一种典型的“时间换空间”的方法,即牺牲读取的性能,换取极致的存储空间。由于压缩列表存储在一段连续的内存上,所以它的存储效率还是蛮高的。
但是,此种设计只适合在字段个数、值比较小的时候,一旦长度过长,压缩列表的设计(利于读取但不利于修改的初衷)会导致修改和删除操作需要频繁的申请和释放内存,可能会导致大量的数据拷贝,拖慢Redis的整体性能。
因此,Redis选择了在达到阈值时,切换数据结构为双向链表。
3Redis 3.2之后的实现
在Redis 3.2及之后,Redis选择了结合压缩列表 和 双向链表的优点,形成了一个新的底层实现:quicklist 快速列表。
快速列表是一个压缩列表组成的双向链表,每个节点使用压缩列表来保存数据。换句话说,快速列表中保存了一个个小的压缩列表。其结构如下图所示:
为了进一步节约空间,Redis 还会对压缩列表进行压缩存储(一种无损压缩算法LZF),这取决压缩深度的参数设置,我们可以选择不压缩(默认值不压缩) 也可以 选择压缩中间节点。
画外音:两端节点一般不被压缩,因为当一个链表很长时,最频繁访问的就是两端的数据,根据“二八定律”,两端数据不压缩,而将中间数据压缩,从而节省空间,但又保证读取效率。
此外,对于每个压缩列表的大小,也是可以通过在redis.conf中的参数来设置的:
list-max-ziplist-size -2
参数可选值从-1到-5,其含义如下:
1) -5:每个quicklist节点上的ziplist大小不能超过64kb。
2) -4:每个quicklsit节点上的ziplist大小不能超过32kb。
3) -3:每个quicklsit节点上的ziplist大小不能超过16kb。
4) -2:每个quicklsit节点上的ziplist大小不能超过8kb。
5) -1:每个quicklsit节点上的ziplist大小不能超过4kb。
画外音:在Redis 3.2 之后,我们也可以通过命令来验证:
192.168.80.100:6379> rpush testkey "edison" "andy" "leo"
3
192.168.80.100:6379> object encoding testkey
quicklist
192.168.80.100:6379> rpush testkey "wwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwww"
4
192.168.80.100:6379> object encoding testkey
quicklist
综述,快速列表的本质其实是对压缩列表的一次封装,使用小块的压缩列表来组织,既可以保证内存占用较小,也可以保证操作性能。
End总结
本文总结了Redis的List类型在何时使用压缩列表,何时使用双向链表,以及快速列表的基本概念。当然,更多的内容还是需要自行去搜索学习,意犹未尽的童鞋也可以去分析源码。最后,如果你对其他集合类型也有此类问题,你可以参考下面附录中的内容,而至于Why,则可以自行百度搜索了解。
Anyway,对于Redis集合类型的底层思想采用了两种数据结构的设计思想是值得我们学习借鉴的,它其实充分体现了软件设计中的Tradeoff(权衡)思想。对于Redis来说,即在主体目标是保证性能的大约束前提下,权衡多方因素如操作时间和空间占用,以达到较为稳定的运行表现。对于软件设计来说,也需要在时间 vs 空间,新技术 vs 老技术,优雅 vs 效率,轻度设计 vs 重度设计等之间做权衡,一个问题总会有多种解决方案可以实现,在特定的时间段,永远没有最完美的设计,只有较合适的设计。在实际中,它可能结合了多种因素的考虑,不断地去粗取精,迭代为更好的设计。
Ref附录
Hash:
Set:
Sorted Set(zset):
参考资料
极客时间,蒋德钧《Redis核心技术与实战》
年终总结:Edison的2020年终总结
数字化转型:我在传统企业做数字化转型
C#刷题:C#刷剑指Offer算法题系列文章目录
.NET面试:.NET开发面试知识体系
.NET大会:2020年中国.NET开发者大会PDF资料
《Redis核心技术与实战》学习总结(2)相关推荐
- Redis核心技术与实战-学习笔记(二十九):Redis并发控制
一.需要并发控制的原因 Redis不可避免的会遇到并发访问问题,比如多用户同时下单,就会对缓存在Redis中的商品库存并发更新,一旦有了并发操作,数据就会被修改,如果我们没有对并发写请求做好控制,就可 ...
- Redis核心技术与实战-学习笔记(二十六):缓存雪崩、击穿、穿透
一.缓存雪崩 缓存雪崩:大量应用请求无法在Redis缓存中进行处理,应用请求频繁访问数据库,导致数据库压力激增. 产生原因: 缓存中有大量数据同时过期,导致大量请求无法得到处理 数据保存在缓存中,并设 ...
- Redis核心技术与实战-学习笔记(十五):消息队列(Redis的解决方案)
一.消息队列 消息队列:分布式系统必备的一个基础软件,能支持组件通信消息的快速读写 Redis本身支持数据的快速访问,满足消息队列的读写性能需求 二.Redis适合做消息队列吗? 消息队列的消息存取需 ...
- Redis核心技术与实战-学习笔记(五)内存快照RDB
一.为什么需要RDB AOF 方法优势:每次执行只需要记录操作命令,需要持久化的数据量不大.在进行写后日志只要不采用always(同步写回)的持久化策略就不会对性能造成太大影响. AOF方法劣势:AO ...
- Redis核心技术与实战-学习笔记(七)哨兵机制
一.主库挂了,如何不间断服务? 主库挂了,需要运行一个新的主库:将从库切换为主库.这就涉及到三个问题: 主库真的挂了吗? 选择哪个从库作为主库? 如何把新主库相关信息通知给从库和客户端 Redis主从 ...
- Redis核心技术与实战-学习笔记(三十五)Redis使用建议
键值对使用规范 key的命令规范,只有命名规范,才能提供可读性强,可维护性好的key,方便日常管理: value的设计规范,包括避免bigkey,选择高效序列化方法和压缩方法,使用整数对象共享池,数据 ...
- Redis核心技术与实战-学习笔记(十四):时间序列数据
一.时间序列数据 时间序列数据:没有严格的关系模型,记录的信息可以表示成键和值的关系. (例如,一个设备ID对应一条记录): 时间序列数据的读写特点 持续高并发写入,需要连续记录数万个设备的实时状态值 ...
- 《Redis核心技术与实战》学习总结(1)
[Redis]| 总结/Edison Zhou 0写在开头 作为Key/Value键值数据库,Redis的应用非常广泛.在之前多年的工作生涯中,我也只是关注了零散的技术点,没有对Redis建立起一套整 ...
- [Python核心技术与实战学习] 18 单元测试unittest 库
什么是单元测试? 编写测试来验证某一个模块的功能正确性, 一般会指定输出, 验证输出是否符合预期. 单元测试,就不得不提 Python unittest 库(更多参看文章结尾中的参考资料)它提供了我们 ...
最新文章
- 【算法与数据结构】最大子序列和问题
- C++的sort排序法
- Python 新浪微博 各种表情使用频率
- bzoj 1562 [NOI2009]变换序列 二分图
- android开发app初始化,Android 的 Application 初始化
- 《完美软件》笔记8:如何应对防卫反应
- QTP引用外部脚本路径的设定(二)left函数的使用
- 2012年7月19日 解一元二次方程
- 调幅广播系统建模与仿真
- RSA算法生成2048位公私钥
- 和利时dcs系统服务器设置,和利时DCS控制系统组态流程
- 计算机进制转换列表,计算机进制转换汇总
- kibana Dev Tools语句查询简单使用入门
- SSM+流浪动物救助系统 毕业设计-附源码191631
- 镭速(Raysync)文件传输高可用安装部署介绍!
- mybatis-Plus自动生成代码
- Matlab多项式和符号函数简介
- python简单程序实例-python简单项目实例
- 528全国爱发日,你的头发还好么,防脱秘籍送给你!
- Android SQLite数据库导出/导入Excel
热门文章
- Html5 Video 节点
- CCNA,CCNP资料
- 深入浅出理解索引结构
- js 下拉底部加载|滑轮滚动到页面底部ajax加载数据的实例
- 显示学生各科成绩和总成绩-面试被问到
- hdu 3480 斜率dp
- 计算机复试比重低的学校,又有985院校发布调剂信息,这个34所降低复试比重!...
- vue-typescript
- JavaFX 学习笔记——窗口与控件
- WCF分布式开发常见错误(25):The certificate 'CN=WCFHTTPS' must have a private key