redis 数据结构篇

113 阅读8分钟

redis 有哪些数据类型, 以及 每个数据类型的底层数据结构是什么? 作者为什么这么设计,有些好处?

image.png

Redis是一个高性能的键值对数据库,提供了多种数据类型以支持不同的使用场景。下面是Redis支持的主要数据类型及其底层数据结构的详细说明,以及设计这些数据结构的原因和好处。

1. 字符串(String)

  • 底层数据结构:简单动态字符串(Simple Dynamic String, SDS)。
  • 设计理由与好处:SDS支持修改操作,如追加、减少等,而不仅仅是读取。它比标准的C字符串更安全,因为它使用长度字段来避免缓冲区溢出,并且可以包含任意二进制数据(包括空字节)。SDS的设计使得字符串操作更加高效和安全。

2. 列表(List)

  • 底层数据结构:双向链表或压缩列表(ziplist),具体使用哪种结构取决于列表元素的数量和元素大小。
  • 设计理由与好处:双向链表支持在两端快速添加或删除元素,适合实现队列和栈等数据结构。压缩列表是一种紧凑的数据结构,节省内存,但添加或删除元素的效率较低。这种双重数据结构的设计允许Redis在保持高性能的同时最小化内存使用。

3. 哈希(Hash)

  • 底层数据结构:压缩列表(ziplist)或哈希表,根据元素数量和大小动态选择。
  • 设计理由与好处:压缩列表用于小型哈希以节省空间,而哈希表适用于包含大量键值对的情况。这样的设计在小型哈希上优化内存使用,在大型哈希上优化性能。

4. 集合(Set)

  • 底层数据结构:整数集合(intset)或哈希表。
  • 设计理由与好处:整数集合是当集合中只包含整数且数量不多时使用的一种内存效率高的数据结构。当集合包含非整数元素或元素数量增加时,Redis会使用哈希表来存储元素。这种设计允许集合在小规模时节省内存,在大规模时提高性能。

5. 有序集合(Sorted Set)

  • 底层数据结构:跳跃表(skiplist)和哈希表的组合。
  • 设计理由与好处:跳跃表支持快速的元素访问和范围查询,同时保持元素有序。哈希表用于存储成员到分数的映射,支持快速查找。这种组合使有序集合在执行范围查询和更新操作时既高效又灵活。

6. Bitmaps和HyperLogLogs

  • 底层数据结构:实际上是字符串(SDS)的特殊应用,但它们被用来实现特定的算法和功能。
  • 设计理由与好处:通过利用字符串类型的底层数据结构,Redis能够在不引入新的内部数据结构的情况下提供这些高级数据类型的功能,节省了开发和维护成本,同时保持了内存效率。

设计原理的共同好处

  • 内存效率:通过使用如压缩列表和整数集合等紧凑的数据结构,在保持性能的同时减少内存使用。
  • 灵活性:通过根据使用场景动态选择最合适的数据结构,Redis可以在不同的操作和数据大小之间提供最佳的性能

压缩列表数据结构 具体讲下

压缩列表(ziplist)是Redis用于优化内存使用的数据结构,常用于实现小型列表、哈希和有序集合等数据类型。它是一个特别设计的紧凑序列化格式,用于存储顺序的字符串或整数值,旨在节省空间,特别适合于存储小量数据的场景。

结构特点

压缩列表不是一个标准的数据结构,而是Redis为了内存优化而特别设计的。它的主要特点包括:

  • 连续内存存储:所有元素顺序存储在一块连续的内存区域中,这有助于减少内存碎片。
  • 无指针使用:与传统的链表不同,压缩列表中的元素通过相对偏移量进行访问,而不是指针,这减少了每个元素的存储开销。
  • 变长编码:元素的长度和某些元素本身(尤其是整数)使用变长编码,这意味着小整数和短字符串占用的空间更少。

结构组成

压缩列表由以下部分组成:

  • zlbytes:表示压缩列表占用的字节总数,用于快速获取整个压缩列表的长度。
  • zltail:指向压缩列表中最后一个元素的偏移量,允许从列表末尾开始快速访问元素。
  • zllen:列表中元素的数量。如果元素数量超过65535(即2^16-1),这个值会被设置为65535,此时需要遍历整个列表来计算实际的元素数量。
  • entryX:列表元素,每个元素可以是字符串或整数。每个元素的存储结构包括它的长度、编码方式和实际数据。整数可以使用特殊的编码方式来减少所需的存储空间。
  • zlend:特殊的结束符,标记压缩列表的结束,其值是一个固定的字节。

设计好处

压缩列表的设计带来了几个显著的好处:

  • 内存节省:通过紧凑存储和变长编码,压缩列表显著减少了小型数据集的内存占用。
  • 适应性强:根据存储的数据类型(字符串或整数)和大小动态调整编码方式,使得压缩列表非常灵活。
  • 操作效率:对于小数据集,由于数据存储在连续的内存空间中,压缩列表可以提供较快的访问速度。但对于较大的数据集或频繁修改的场景,操作效率可能会下降,这时Redis会选择其他数据结构如双向链表等来代替压缩列表。

压缩列表是Redis为了在保持高性能的同时优化内存使用而做出的一个设计选择,特别适用于存储小量数据的场景。

Redis的跳表

Redis的跳表(Skip List)是一种高效的数据结构,它提供了与平衡树相似的平均时间和最坏情况时间复杂度,用于插入、删除和搜索操作,同时保持较简单的实现逻辑。跳表通过在多个层级上添加前进指针来提高操作的效率,这使得跳过部分元素成为可能,从而加快搜索速度。

跳表的结构

跳表的基本结构类似于多层的链表:

  • 多层结构:跳表包含多个层级,每一层都是一个有序的链表。最底层(Level 0)包含所有元素,而上面的每一层都以一定概率p(通常是1/2或1/4)包含下一层的元素,即每个元素在上一层出现的概率是p。
  • 头节点:跳表有一个头节点,它在所有层级中都存在,其前进指针指向当前层的第一个实际节点。
  • 指针:每个节点包含多个指针,指向同一层级的下一个节点以及下面层级的对应节点。
  • 尾部指示:每层都有一个尾部标记,表示该层级的结束,有助于终止搜索操作。

使用场景

跳表在Redis中的使用场景主要包括:

  1. 有序集合(Sorted Sets):Redis使用跳表来实现有序集合数据类型。这允许它执行范围查询、插入、删除和查找操作,同时保持元素按分数排序。跳表支持快速的访问和更新操作,非常适合实现排行榜、时间线等功能。

  2. 时间复杂度:跳表为搜索、插入和删除操作提供了O(logN)的平均时间复杂度,这使得它在处理有序数据时非常高效。

  3. 简化并发操作:由于其结构的简单性,跳表相对于其他平衡树结构更易于实现并发操作。虽然Redis本身是单线程的,但跳表的这一特性使得它在需要并发访问时成为一个优选的数据结构。

设计好处

  • 实现简单:与平衡树(如红黑树)相比,跳表的实现更为简单直观。这简化了代码的维护和理解。
  • 性能优异:跳表提供了与平衡树相竞争的性能,特别是在执行顺序访问和范围查询时。
  • 灵活性:跳表的层级结构和概率平衡机制提供了调整性能和内存使用的灵活性,使得开发者可以根据实际应用需求调整跳表的特性。

跳表的这些特性和优势使其成为实现Redis有序集合等功能的理想选择,提供了一个既高效又易于管理的数据结构解决方案。