Redis源码、面试指南(3)数据对象类型编码(下)

本文涉及的产品
Redis 开源版,标准版 2GB
推荐场景:
搭建游戏排行榜
简介: Redis源码、面试指南(3)数据对象类型编码

Redis源码、面试指南(3)数据对象类型编码(上):https://developer.aliyun.com/article/1508229

哈希对象

源码文件t_hash.c。

编码是ziplist或者hashtable。

  • ziplist编码,底层是压缩列表,有两个条件,哈希对象保存的所有键值对的键和值的字符串长度都小于 64 字节;哈希对象保存的键值对数量小于 512 个

每当有新的键值对要加入到哈希对象时,保存了同一键值对的两个节点总是紧挨在一起,保存键的节点在前,保存值的节点在后;且这些键值对遵循先来后到的原则。

注:在使用ziplist编码的时候,获取键对应值的时间复杂度不是O(1),而是O(N^2)(先遍历键,在遍历值是否相等)!但由于使用ziplist的时候,长度和键的长度较小,对性能影响不是很大。

  • hashtable编码,当不满足上述的两个条件时,就使用字典作为底层实现。哈希对象中的每个键值对都使用一个字典键值对来保存:字典的每个键和值都是一个字符串对象

以下是一个以字典为底层的哈希对象实例:

注:当ziplist不满足两个条件其中之一时,原本保存在压缩列表里的所有键值对都会被转移并保存到字典里面。

注:常用命令

命令 ziplist 编码实现方法 hashtable 编码的实现方法
HSET 首先调用 ziplistPush 函数, 将键推入到压缩列表的表尾, 然后再次调用 ziplistPush 函数, 将值推入到压缩列表的表尾。 调用 dictAdd 函数, 将新节点添加到字典里面。

image.png

集合对象

源码问阿金t_set.c。

集合对象的编码可以是intset或者hashtable

  • intset编码,使用整数集合作为集合对象的实现,有两个条件:集合对象保存的所有元素都是整数值;集合对象保存的元素数量不超过512个

集合对象包含的所有元素都被保存在整数集合里面,见下:

  • hashtable编码,当intset编码的两个条件不满足时,使用字典作为底层实现,字典的每个键都是一个字符串对象,每个字符串对象包含了一个集合元素,而字典的值则全部被设置为 NULL(因为没有没有键就代表了值)

注:常用命令

命令 intset 编码的实现方法 hashtable 编码的实现方法
SADD 调用 intsetAdd 函数, 将所有新元素添加到整数集合里面。 调用 dictAdd , 以新元素为键, NULL 为值, 将键值对添加到字典里面。
SCARD 调用 intsetLen 函数, 返回整数集合所包含的元素数量, 这个数量就是集合对象所包含的元素数量。 调用 dictSize 函数, 返回字典所包含的键值对数量, 这个数量就是集合对象所包含的元素数量。

image.png

有序集合对象

源码文件t_zset.c。

有序集合的编码可以是ziplist和skiplist

  • ziplist 编码,使用压缩列表作为底层实现有两个条件:有序集合保存的元素数量小于 128 个;有序集合保存的所有元素成员的长度都小于 64 字节

注:ziplist为了保证有序性,每次插入删除操作时,都可能需要对其中的数据进行移动。

每个集合元素使用两个紧挨在一起的压缩列表节点来保存,第一个节点保存元素的成员,而第二个元素则保存元素的分值(score)(排序)

  • skiplist 编码,不满足上述条件时的有序集合对象使用zset 结构作为底层实现,一个zset结构同时包含一个字典和一个跳跃表
typedef struct zset {
    zskiplist *zsl;
    dict *dict;
} zset;


zset结构中的跳跃表按分值从小到大保存了所有集合元素,每个跳跃表节点都保存了一个集合元素:跳跃表节点的 object 属性保存了元素的成员,而跳跃表节点的 score 属性则保存了元素的分值。 通过这个跳跃表,

程序可以对有序集合进行范围型操作,比如 ZRANK 、ZRANGE 等命令就是基于跳跃表 API 来实现的

除此之外,zset结构中的dict字典为有序集合创建了一个从成员到分值的映射,字典中的每个键值对都保存了一个集合元素:字典的键保存了元素的成员,而字典的值则保存了元素的分值。通过这个字典,程序可以用O(1)复杂度查找给定成员的分值,ZSCORE命令就是根据这一特性实现的,而很多其他有序集合命令都在实现的内部用到了这一特性

有序集合每个元素的成员都是一个字符串对象,而每个元素的分值都是一个 double 类型的浮点数。 值得一提的是,虽然zset结构同时使用跳跃表和字典来保存有序集合元素,但这两种数据结构都会通过指针来共享相同元素的成员和分值,所以同时使用跳跃表和字典来保存集合元素不会产生任何重复成员或者分值,也不会因此而浪费额外的内存。


为什么需要这么做呢?


在理论上来说,有序集合可以单独使用字典或者跳跃表的其中一种数据结构来实现,但无论单独使用字典还是跳跃表,在性能上对比起同时使用字典和跳跃表都会有所降低。


举个例子,如果我们只使用字典来实现有序集合,那么虽然以O(1)复杂度查找成员的分值这一特性会被保留,但是,因为字典以无序的方式来保存集合元素,所以每次在执行范围型操作一一一比如ZRANK、ZRANG等命令时,程序都需要对字典保存的所有元素进行排序,完成这种排序需要至少O(NlogN)时间复杂度,以及额外的O(N)内存空间(因为要创建一个数组来保存排序后的元素)


另一方面,如果我们只使用跳跃表来实现有序集合,那么跳跃表执行范围型操作的所有优点都会被保留,但因为没有了字典,所以根据成员查找分值这一操作的复杂度将上升到到O(logn)。


因为以上原因,为了让有序集合的查找和范围型操作都尽可能快地执行,Redis选择了同时使用字典和跳跃表两种数据结构来实现有序集合。


注:常用命令

命令 ziplist 编码的实现方法 zset 编码的实现方法
ZADD 调用 ziplistInsert 函数, 将成员和分值作为两个节点分别插入到压缩列表。 先调用 zslInsert 函数, 将新元素添加到跳跃表, 然后调用 dictAdd 函数, 将新元素关联到字典。
ZCARD 调用 ziplistLen 函数, 获得压缩列表包含节点的数量, 将这个数量除以 2 得出集合元素的数量。 访问跳跃表数据结构的 length 属性, 直接返回集合元素的数量。
ZCOUNT 遍历压缩列表, 统计分值在给定范围内的节点的数量。 遍历跳跃表, 统计分值在给定范围内的节点的数量。
ZRANGE 从表头向表尾遍历压缩列表, 返回给定索引范围内的所有元素。 从表头向表尾遍历跳跃表, 返回给定索引范围内的所有元素。
ZREVRANGE 从表尾向表头遍历压缩列表, 返回给定索引范围内的所有元素。 从表尾向表头遍历跳跃表, 返回给定索引范围内的所有元素。
ZRANK 从表头向表尾遍历压缩列表, 查找给定的成员, 沿途记录经过节点的数量, 当找到给定成员之后, 途经节点的数量就是该成员所对应元素的排名。 从表头向表尾遍历跳跃表, 查找给定的成员, 沿途记录经过节点的数量, 当找到给定成员之后, 途经节点的数量就是该成员所对应元素的排名。

image.png

Hyperloglog对象

源码文件:hyperloglog.c。

建议直接参考:

https://blog.csdn.net/qq_39885372/article/details/104245363

类型检查和多态

Redis中用于操作键的命令基本分为两种,一种是键共有操作,比如说DEL命令、EXPIRE命令、RENAME命令、TYPE 命令、OBJECT命令等。另一种是键特定操作,如SET、GET、APPEND、STRLEN等命令只能对字符串键执行。

类型检查

在执行一个类型特定的命令之前, Redis 会先检查输入键的类型是否正确, 然后再决定是否执行给定的命令。通过 redisObject 结构的 type 属性来实现的:

多态命令

这里指的是同一个对象一般具有两种编码方式,比如hash对象是hashtable和ziplist,那么执行命令的时候是怎么确定的呢?也是根据其对象的编码格式来的:

件:hyperloglog.c。

建议直接参考:

https://blog.csdn.net/qq_39885372/article/details/104245363

类型检查和多态

Redis中用于操作键的命令基本分为两种,一种是键共有操作,比如说DEL命令、EXPIRE命令、RENAME命令、TYPE 命令、OBJECT命令等。另一种是键特定操作,如SET、GET、APPEND、STRLEN等命令只能对字符串键执行。

类型检查

在执行一个类型特定的命令之前, Redis 会先检查输入键的类型是否正确, 然后再决定是否执行给定的命令。通过 redisObject 结构的 type 属性来实现的:

[外链图片转存中…(img-VOWn19Ck-1618293094079)]

多态命令

这里指的是同一个对象一般具有两种编码方式,比如hash对象是hashtable和ziplist,那么执行命令的时候是怎么确定的呢?也是根据其对象的编码格式来的:



相关文章
|
1月前
|
存储 缓存 NoSQL
Redis常见面试题全解析
Redis面试高频考点全解析:从过期删除、内存淘汰策略,到缓存雪崩、击穿、穿透及BigKey问题,深入原理与实战解决方案,助你轻松应对技术挑战,提升系统性能与稳定性。(238字)
|
6月前
|
缓存 NoSQL 关系型数据库
美团面试:MySQL有1000w数据,redis只存20w的数据,如何做 缓存 设计?
美团面试:MySQL有1000w数据,redis只存20w的数据,如何做 缓存 设计?
美团面试:MySQL有1000w数据,redis只存20w的数据,如何做 缓存 设计?
|
4月前
|
存储 NoSQL 定位技术
Redis数据类型面试给分情况
Redis常见数据类型包括:string、hash、list、set、zset(有序集合)。此外还包含高级结构如bitmap、hyperloglog、geo。不同场景可选用合适类型,如库存用string,对象存hash,列表用list,去重场景用set,排行用zset,签到用bitmap,统计访问量用hyperloglog,地理位置用geo。
122 5
|
5月前
|
缓存 NoSQL Java
Java Redis 面试题集锦 常见高频面试题目及解析
本文总结了Redis在Java中的核心面试题,包括数据类型操作、单线程高性能原理、键过期策略及分布式锁实现等关键内容。通过Jedis代码示例展示了String、List等数据类型的操作方法,讲解了惰性删除和定期删除相结合的过期策略,并提供了Spring Boot配置Redis过期时间的方案。文章还探讨了缓存穿透、雪崩等问题解决方案,以及基于Redis的分布式锁实现,帮助开发者全面掌握Redis在Java应用中的实践要点。
309 6
|
5月前
|
存储 缓存 NoSQL
告别数据僵尸!Redis实现自动清理过期键值对
在数据激增的时代,Redis如同内存管理的智能管家,支持键值对的自动过期功能,实现“数据保鲜”。通过`EXPIRE`设定生命倒计时、`TTL`查询剩余时间,结合惰性删除与定期清理策略,Redis高效维护内存秩序。本文以Python实战演示其过期机制,并提供最佳实践指南,助你掌握数据生命周期管理的艺术,让数据优雅退场。
364 0
|
5月前
|
机器学习/深度学习 数据采集 人机交互
springboot+redis互联网医院智能导诊系统源码,基于医疗大模型、知识图谱、人机交互方式实现
智能导诊系统基于医疗大模型、知识图谱与人机交互技术,解决患者“知症不知病”“挂错号”等问题。通过多模态交互(语音、文字、图片等)收集病情信息,结合医学知识图谱和深度推理,实现精准的科室推荐和分级诊疗引导。系统支持基于规则模板和数据模型两种开发原理:前者依赖人工设定症状-科室规则,后者通过机器学习或深度学习分析问诊数据。其特点包括快速病情收集、智能病症关联推理、最佳就医推荐、分级导流以及与院内平台联动,提升患者就诊效率和服务体验。技术架构采用 SpringBoot+Redis+MyBatis Plus+MySQL+RocketMQ,确保高效稳定运行。
407 0
|
存储 Java
【IO面试题 四】、介绍一下Java的序列化与反序列化
Java的序列化与反序列化允许对象通过实现Serializable接口转换成字节序列并存储或传输,之后可以通过ObjectInputStream和ObjectOutputStream的方法将这些字节序列恢复成对象。
|
存储 算法 Java
大厂面试高频:什么是自旋锁?Java 实现自旋锁的原理?
本文详解自旋锁的概念、优缺点、使用场景及Java实现。关注【mikechen的互联网架构】,10年+BAT架构经验倾囊相授。
大厂面试高频:什么是自旋锁?Java 实现自旋锁的原理?
|
存储 缓存 算法
面试官:单核 CPU 支持 Java 多线程吗?为什么?被问懵了!
本文介绍了多线程环境下的几个关键概念,包括时间片、超线程、上下文切换及其影响因素,以及线程调度的两种方式——抢占式调度和协同式调度。文章还讨论了减少上下文切换次数以提高多线程程序效率的方法,如无锁并发编程、使用CAS算法等,并提出了合理的线程数量配置策略,以平衡CPU利用率和线程切换开销。
面试官:单核 CPU 支持 Java 多线程吗?为什么?被问懵了!
|
存储 缓存 Java
大厂面试必看!Java基本数据类型和包装类的那些坑
本文介绍了Java中的基本数据类型和包装类,包括整数类型、浮点数类型、字符类型和布尔类型。详细讲解了每种类型的特性和应用场景,并探讨了包装类的引入原因、装箱与拆箱机制以及缓存机制。最后总结了面试中常见的相关考点,帮助读者更好地理解和应对面试中的问题。
307 4