本篇文章主要是对 Redis 常见的数据结构进行讲解,同时还对其所对应的不同的内部编码进行讲解。希望本篇文章会对你有所帮助。
文章目录
一、五大数据结构
二、数据结构对应的编码方式
String
hash
list
set
zset
🙋♂️ 作者:@Ggggggtm 🙋♂️
👀 专栏:Redis 👀
💥 标题:Redis中的数据结构与内部编码 💥
❣️ 寄语:与其忙着诉苦,不如低头赶路,奋路前行,终将遇到一番好风景 ❣️
我们知道,redis是支持不同的数据类型的,而不同的数据类型底层所采用的数据结构也有所差异,同时不同的数据结构也会有不同的操作指令。我们这里所说的不同的数据类型都是指的value的类型,而key的类型固定是string的。上篇文章讲解到的type指令,其返回值是key所对应的value的数据类型!
一、五大数据结构
Redis的键值对中,value所用到的五种主要的数据结构,它们分别是:
String(字符串):最基本的类型,可以包含任何形式的数据,比如一个文本、JSON字符串或者二进制数据。
List(列表):一个有序的字符串列表,元素的顺序由插入顺序决定,可以在两端进行插入和删除操作。
Set(集合):一个不重复且无序的字符串集合,可对集合中的元素进行添加、删除和判断某个元素是否存在的操作。
Hash(哈希表):类似于关联数组,包含字段和与其对应的值,常用于存储对象。
Zset(有序集合):类似于集合,但每个元素都关联一个分数,可以按照分数进行排序,适合构建排行榜等功能。
具体可看下图:
Redis 底层在实现上述数据结构的时候,会在源码层面针对上述实现进行特定的优化,来达到节省时间或者节省空间的效果。简单来说,数据结构的内部具体实现不是固定统一的,会根据不同的情况,redis会自动适配合适的数据结构。但是每个数据结构的底层实现有固定的几种方式。
举个例子,redis承诺现在我这有个hash表,你进行查询、插入、删除操作都保证O(1)的书简复杂度。但是这个背后的实现,不一定就是一个标准的hash表。可能再特定场景下,使用别的数据结构实现。但是仍然保证时间复杂度符合承诺。
那么哦我们现在就会有两个大致方向上的理解:
- 数据结构是指redis 承诺给你的。也可以理解成数据类型;
- 编码方式是redis内部底层的实现;
- 一个数据结构有对应固定的编码方式。
二、数据结构对应的编码方式
实际上每种数据结构都有自己底层的内部编码实现,而且是多种实现。这样 Redis 会在合适的场景选择合适的内部编码。具体数据结构所对应的编码方式如下:
数据结构 内部编码 string 1.raw
2.embstr
3.inthash 1.hashtable
2.ziplistlist 1.linkedlist
2.ziplistset 1.hashtable
2.intsetzset 1.skiplist
2.ziplist下面我们来详细解释一下不同的内部编码。
String
Redis中 String 的内部编码有三种:int、raw、embstr。
int编码
- 当字符串可以被解释为整数时,Redis会将该字符串以int编码进行存储,节省内存空间。
- int编码使用64位有符号整数来表示,其范围为-2^63到2^63-1。
- 当字符串的值处于int编码的范围内时,Redis会使用int编码来存储,这样可以减少内存占用并提高性能。
raw编码
- 当字符串无法被解释为整数时,Redis会以raw编码进行存储。
- raw编码使用简单动态字符串(SDS)来表示,SDS是一种带有缓冲区的字符串表示形式,可以动态扩展和收缩,适用于存储任意长度的字符串数据。
- raw编码适用于存储包含非整数数据的字符串,如文本、JSON、XML等。
embstr编码
- embstr是一种特殊的内部编码方式,用于存储长度较短的字符串。
- 当字符串长度在一定范围内(默认为39字节)时,Redis会使用embstr编码来存储,以进一步减少内存开销。
- embstr编码实际上将字符串值和其他元数据一起存储在一个连续的内存块中,节省了额外指针和分配内存的开销。
SDS动态字符串可参考文章:Redis(设计与实现):01---数据结构之SDS动态字符串(raw、embstr、int、struct sdshdr)_sds类型的int与raw查询速度的区别
hash
Redis 中的 Hash 类型可以使用两种内部编码方式:hashtable 和 ziplist。
Hashtable:
- Hashtable 是 Redis 中 Hash 类型的默认内部编码方式。
- 当 Hash 中包含的键值对数量较多,或者键值对的值较大时,Redis 会自动将 Hash 内部编码为 Hashtable。
- Hashtable 使用哈希表来存储键值对,可以快速进行数据查找,适合处理大规模的数据。
Ziplist:
- Ziplist 是一种紧凑的数据结构,适合存储小规模的数据。
- 当 Hash 中包含的键值对数量较少,且每个键值对的键和值的长度都较小时,Redis 会选择使用 Ziplist 进行内部编码。
- Ziplist 使用一段连续的内存空间来存储键值对,节约了内存的使用,但查找效率没有哈希表高。
- 由于元素过少,通过遍历,时间复杂度还是可以认为是 O(1)
总结来说,当 Hash 数据较大或者键值对较复杂时,Redis 会使用 Hashtable 进行内部编码以获得更好的性能;而当 Hash 数据较小且简单时,Redis 会选择使用 Ziplist 进行内部编码以节约内存空间。
具体底层实现细节可参考文章:Redis(设计与实现):06---数据结构之压缩列表(ziplist、struct ziplist)_4位长,介于0至12之间的无符号整数Redis(设计与实现):03---数据结构之字典(hashtable、struct dictht、struct dictEntry、struct dict)-CSDN博客
list
在 Redis 中,List 类型可以使用三种不同的内部编码方式:linkedlist、ziplist 和 quicklist。
Linkedlist(双向链表):
- linkedlist 内部编码主要适用于 List 包含的元素数量较多,或者元素的大小较大的情况。它通过指针将元素连接在一起,支持快速的插入和删除操作,适合处理大规模的数据。
Ziplist(压缩列表):
- ziplist 内部编码适用于 List 包含的元素数量较少,且每个元素的大小都比较小时。Ziplist 使用紧凑的数据结构来存储元素,节约了内存的使用,但查找和修改操作的效率没有 linkedlist 高。
Quicklist(快速列表):
- quicklist 是 Redis 为了优化 List 类型而引入的一种内部编码方式。它实际上是将多个 ziplist 连接在一起形成的一个双向链表结构。quicklist 既具备了 ziplist 节约内存空间的优点,又能够快速地处理大规模的数据,同时还兼顾了快速的插入和删除操作。
综上所述,Redis 根据 List 中元素的数量和大小来选择合适的内部编码方式:linkedlist 适用于大规模数据的场景,ziplist 适用于节约内存的场景,而 quicklist 则是为了兼顾以上两种场景而设计的一种高效内部编码方式。大部分场景下都是使用的quicklist。
set
在 Redis 中,Set 类型可以使用两种不同的内部编码方式:hashtable 和 intset。
Hashtable(哈希表):
- 当 Set 中的元素数量较多,或者元素较长时,Redis 会选择使用 hashtable 内部编码。这种编码方式使用了哈希表结构来存储元素,它提供了较快的查找、插入和删除操作,适用于处理较大规模的数据。
Intset(整数集合):
- 当 Set 中的所有元素都是整数,并且数量较少时,Redis 会选择使用 intset 内部编码。intset 使用紧凑的数组结构来存储整数元素,节省了内存空间的使用,并且提供了高效的操作,适用于存储小规模的整数数据集。
在实际使用中,Redis 根据 Set 的特点(元素数量、元素类型等)来动态地选择合适的内部编码方式,以提高性能和节约内存空间。通过使用合适的内部编码方式,Redis 能够更好地满足不同场景下对 Set 数据类型的需求。
intset底层实现可参考文章:Redis(设计与实现):04---数据结构之整数集合(intset、struct intset)
zset
在 Redis 中,有序集合(Sorted Set)类型可以使用两种不同的内部编码方式:skiplist 和 ziplist。
Skiplist(跳跃表):
- 当有序集合中的成员数量较多或者成员较长时,Redis 会选择使用 skiplist 内部编码。跳跃表是一种基于链表的数据结构,它可以提供快速的插入、删除和查找操作。
- Skiplist 编码方式适用于处理较大规模的有序集合数据,因为它能够保持较高的性能并且具有良好的平衡性能。
Ziplist(压缩列表):
- 当有序集合中的成员数量较少且每个成员都比较短小精悍时,Redis 会选择使用 ziplist 内部编码。压缩列表是一种紧凑的数据结构,可以节省内存空间,适用于存储较小规模的有序集合数据。
- Ziplist 内部编码方式对于小型的有序集合数据具有较好的存储效率和操作性能。
根据有序集合的实际情况,Redis 会动态地选择合适的内部编码方式,以便在不同场景下提供最佳的性能和内存利用率。
skiplist底层实现原理可参考文章:Redis(设计与实现):05---数据结构之跳跃表(skiplist、struct zskiplistNode、struct zskiplist)_skiplist和zskiplistnode
可以看到每种数据结构都有至少两种以上的内部编码实现,例如list数据结构包含了linkedlist和ziplist 两种内部编码。同时有些内部编码,例如ziplist,可以作为多种数据结构的内部实现,可以通过object encoding命令查询内部编码:
Redis这样设计有两个好处:
- 可以改进内部编码,而对外的数据结构和命令没有任何影响,这样一旦开发出更优秀的内部编码,无需改动外部数据结构和命令,例如Redis 3.2提供了quicklist,结合了ziplist和linkedlist两者的优势,为列表类型提供了一种更为优秀的内部编码实现,而对用户来说基本无感知。
- 多种内部编码实现可以在不同场景下发挥各自的优势,例如ziplist 比较节省内存,但是在列表元素比较多的情况下,性能会下降,这时候Redis 会根据配置选项将列表类型的内部实现转换为linkedlist,整个过程用户同样无感知。