文章目录
Redis 学习笔记-哈希桶和底层数据结构
前面文章介绍了 Redis
基本数据类型和这些数据类型对应的一些操作,知道了 Redis
很快的原因是 内存
和 数据结构
,这篇文章就围绕 数据结构
来介绍一下 Redis
底层有哪些数据结构,为什么这些数据结构会提升访问速度,学习这些底层数据结构将会加深对 Redis
设计原理的理解。
1.Redis 基本数据类型
- String(字符串)
- Set(集合)
- Sorted Set(有序集合)
- List(列表)
- 散列表(哈希)
- GEO
- HyperLogLog
- Stream(流)
2.Redis 底层数据结构类型
- 简单动态字符串
- 双向链表
- 压缩列表
- 哈希表
- 跳表
- 整数集合
3.Redis 基本数据类型和底层数据结构关系示意图
String
类型对应简单动态字符串
List
类型对应双向链表
和压缩列表
Hash
类型对应压缩列表
和散列表
Sorted Set
类型对应压缩列表
和跳表
Set
类型对应散列表
和整数集合
Tips:
GEO
、HyperLogLog
、Stream
后续文章单独讨论。
4.key 是如何查找到哈希桶
首先 key
需要经历 全局哈希表
找到 哈希桶
,关于哈希表(HashTable)
的原理可以参考我之前写的文章来了解,哈希表
也叫散列表
,这里简单介绍一下 哈希表(HashTable)
的原理,每一个 key
可以通过哈希计算变成一个较大的整数,然后通过数学模型取模
就可以对应到某个数组中的索引
,然后通过这个索引值
可以快速找到数组中的元素,这个元素可以称为 哈希桶
元素,如下图所示简单展示了哈希表(HashTable)
的实现原理:
4.1 哈希表(HashTable)原理图
Tips:如上图所示的数组可以称为一个
哈希桶
,Redis
中解决哈希冲突时采用拉出一个链表来解决的。
4.2 哈希桶和数据类型对应原理图
4.3 渐进式 rehash
上述哈希桶需要考虑一个问题,当 key
的数量比较庞大时,可能导致哈希桶的数量不合理(即哈希取模
时的素数
不合理),从而出现哈希冲突比较多的情况,直接带来的影响是哈希桶中的 entry
链表过长,众所周知,当链表太长的时候查找元素需要遍历(复杂度O(N))才能找到 key
对应的那一个 entry
,为了解决这个问题 Redis
设计者采用了 渐进式 rehash
,它的目的是重构
哈希桶的数量,让哈希桶数量更加合理,使 key
对的 entry
在哈希桶中分布更加均匀,从而从概率角度上减少 entry
链表的长度,具体实现逻辑如下:
Redis
设计了2
个全局哈希表,记作哈希表A
和哈希表B
,最开始默认使用哈希表A
,哈希桶记作是Ma
,哈希表B
不分配内存,等待调度。- 当
哈希表A
中的哈希桶冲突比较频繁,且数量比较大(某个阈值)的时候,就会启用哈希表B
,并且哈希表B
的哈希桶数量Mb
大于Ma
(具体要依据数学模型)。 哈希表B
建立好之后就需要考虑将哈希表A
的数据转移到哈希表B
中,如果有大量的数据迁移可能会导致Redis
线程阻塞导致业务停摆,这个时候Redis
采用了渐进式 rehash
。渐进式 rehash
可以简单理解为每次访问哈希表A
数据的时候顺带带一点
数据出来给哈希表B
,整个过程是渐进
完成的,不会影响业务在正常运行。- 数据转移完成之后就会释放
哈希表A
的内存,等待下次同样的情况时,重构哈希桶
使用。
5.底层数据结构
对于 字符串(String)
来说,找到哈希桶就可以直接对它操作(增删改查等)简单动态字符串
,而对于 List
、Hash
、Sorted Set
、Set
这些数据类型来说,找到哈希桶之后还需要对应到 双向链表
、压缩列表
、哈希表
、跳表
、整数集合
这些底层数据结构中的,下面简单介绍一下几种数据结构:
5.1 双向链表示意图
5.2 压缩列表
压缩列表(ziplist)
实际是一个字节数组
,它的设计目的是为了节约内存,和普通数组不同的是在数组头部会有三个字段,分别是 zlbytes(列表长度)
、zltail(列表尾部偏移量)
、zllen(entry 的个数)
,在 压缩列表
的尾部还会有 zlend(结束)
字段,示意图如下图:
5.3 跳表
5.4 不同数据结构时间复杂度
6.小结
Redis
中的数据结构比较丰富,全部熟练地记住所有的操作比较困难,但掌握其底层数据结构原理之后,在如何选择合适的数据类型时能有一个理性地推断,而且也能根据这些原理合理地选择数据类型。
扫码关注