【基础篇】数据结构

Redis 的底层数据结构

我们都知道 Redis 中的数据结构有:String,List,Hash,Set,Sort Set等,那么他们对应的底层的数据结构有哪些呢?

可以看到,String 类型的底层实现只有一种数据结构,即简单动态字符串。但是,其他四种数据结构,都有两种底层实现结构,它们被称为"集合类型"。

上面的数据结构都是值的底层实现,键和值之间用什么结构组织呢?

键与值之间的关联

为了实现从键到值的快速访问,Redis 使用了一个哈希表来保存所有键值对。哈希表中每个元素称为一个哈希桶,每个哈希桶中保存了键值对数据。哈希桶中的元素保存的并不是值本身,而是指向具体值的指针。

哈希表的最大好处很明显,就是让我们可以用 O(1) 的时间复杂度来快速查找键值对。

但是,我们往 Redis 中写入大量数据后,就可能发现操作有时候会突然变慢了。这其实是因为忽略了一个潜在的风险点,那就是哈希表的冲突问题和 rehash 可能带来的操作阻塞。

哈希表的哈希冲突

这里的哈希冲突,就是值两个 key 的哈希值和哈希桶计算对应关系的时候,正好落在同一个哈希桶里面。这一点很好理解,因为哈希桶的个数通常要少于 key 的数量。或者,你可以想象下:你有10 个篮子,12 个苹果,很明显,要全部放到篮子里的话,其中有两个篮子会有两个苹果。

Redis 中解决哈希冲突的方法,就是链式哈希。也即:同一个哈希桶中的多个元素,使用一个链表来保存:

这里回到我们之前说的点:往 Redis 中写入大量数据后,就可能发现操作有时候会突然变慢了。很明显,当数据过多的时候,一个哈希桶上的哈希链就会越长,那么查询耗时就会增大,效率降低。这对与我们来说是不能接受的,所以我们需要对哈希表做 rehash 操作。

rehash 就是增加现有的哈希桶数量,让逐渐增多的 entry 元素能在更多的桶之间分散保存,减少单个桶中元素数量。具体操作如下:

  1. 为了 rehash 更有效率,Redis 默认使用两个全局哈希表:哈希表1 和哈希表2,一开始都使用 哈希表1;
  2. 当数据增多,要开始执行 rehash 时:
    • 给哈希表 2 分配更大的空间,比如说哈希表1 的两倍;
    • 把哈希表 1 中的数据重新映射到哈希表 2;
    • 释放哈希表 1 的空间;
  3. 哈希表 1就留着用于下一次 rehash 使用;
渐进式 rehash

上面的过程看似简单,但是数据的重新映射设计大量的数据拷贝,如果一次性将其迁移完成,会造成 Redis 线程阻塞。为了避免这个问题,Redis 采用了渐进式 rehash。

简单来说就是在第二步拷贝数据时,Redis 仍然正常处理客户端请求,每处理一个请求时,从哈希表 1 中的第一个索引位置开始,顺带着将这个索引位置上的所有 entries 拷贝到哈希表 2 中;等处理下一个请求时,再顺带拷贝哈希表 1 中的下一个索引位置的 entries。

这样就巧妙地将一次性大量拷贝的开销,分摊到多次处理请求的过程中,避免了耗时操作。

集合类型的查找

对于 Sting 类型来说,找到哈希桶就能直接增删改查了,所以,哈希表的 O(1) 操作复杂度也就是它的复杂度了。

但是,对于集合类型来说,即使找到哈希桶了,还要在集合中再进一步操作。接下来,我们来看集合类型的操作效率又是怎样的。也即:第一步是通过全局哈希表找到对应的哈希桶位置,第二步是在集合中再增删改查。

集合类型的底层包括 5 种:整数数组、双向链表、哈希表、压缩列表和跳表。

一些使用上的点

  1. 范围操作,是指集合类型中的遍历操作,可以返回集合中的所有数据,比如 Hash 类型的 HGETALL 和 Set 类型的 SMEMBERS,或者返回一个范围内的部分数据。这类操作的复杂度一般是 O(N),比较耗时,我们应该尽量避免。
  2. 整数数组和压缩列表在查找时间复杂度方面并没有很大的优势,那为什么 Redis 还会把它们作为底层数据结构呢?
  • 内存利用率,数组和压缩列表都是非常紧凑的数据结构,它比链表占用的内存要更少。Redis是内存数据库,大量数据存到内存中,此时需要做尽可能的优化,提高内存的利用率。
  • 数组对CPU高速缓存支持更友好,所以Redis在设计时,集合数据元素较少情况下,默认采用内存紧凑排列的方式存储,同时利用CPU高速缓存不会降低访问速度。当数据元素超过设定阈值后,避免查询时间复杂度太高,转为哈希和跳表数据结构存储,保证查询效率。(这点和Hash 类似: Hash 集合中写入的元素个数超过了 hash-max-ziplist-entries,或者写入的单个元素大小超过了 hash-max-ziplist-value,Redis 就会自动把 Hash 类型的实现结构由压缩列表转为哈希表。 还有 String 的 SDS 底层也是类型)
相关推荐
盼海5 分钟前
排序算法(五)--归并排序
数据结构·算法·排序算法
搬砖的小码农_Sky6 小时前
C语言:数组
c语言·数据结构
先鱼鲨生8 小时前
数据结构——栈、队列
数据结构
一念之坤8 小时前
零基础学Python之数据结构 -- 01篇
数据结构·python
IT 青年8 小时前
数据结构 (1)基本概念和术语
数据结构·算法
熬夜学编程的小王8 小时前
【初阶数据结构篇】双向链表的实现(赋源码)
数据结构·c++·链表·双向链表
liujjjiyun9 小时前
小R的随机播放顺序
数据结构·c++·算法
Reese_Cool10 小时前
【数据结构与算法】排序
java·c语言·开发语言·数据结构·c++·算法·排序算法
djk888811 小时前
.net将List<实体1>的数据转到List<实体2>
数据结构·list·.net
搬砖的小码农_Sky11 小时前
C语言:结构体
c语言·数据结构