# 数据结构
Redis 包含五个数据结构。分别是 String, List, Hash, Set 和 Sorted Set. 除了 String 以外,其他四种是集合类型,因为它们一个键对应一个集合的数据。
# 全局哈希表
在 Redis 中,为了组织所有的键值对,它维护了两张全局哈希表。
哈希表是一个数组,每个元素称为一个哈希桶,哈希桶中不保存元素本身,而保存指向具体 entry 的指针。每一个 entry 由键值对构成,可以通过指针被访问到。
利用了这样的全局哈希表,就可以在 O(1) 的时间复杂度中找到键值对。
# 哈希冲突
但是随着数据变多,因为哈希冲突和 rehash, 可能会带来操作阻塞。
当发生哈希冲突时,Redis 采用链式哈希的操作来解决冲突。同一个哈希桶用链表的形式保存多个元素。
当数据逐渐增加,链表会导致性能下降时,它会通过 rehash 启用第二张全局哈希表。rehash 过程为:
- 给第二张全局哈希表分配空间,大小是第一张的两倍
- 将第一张全局哈希表中的数据重新映射到第二张中
- 释放第一张全局哈希表的空间
为了避免第二步中数据映射可能带来的巨大开销,Redis 的 rehash 过程是渐进式的。即在映射过程中,Redis 仍然正常处理客户端请求。每处理一个请求,就通过键找到对应的第一张全局哈希表中的哈希桶位置,并将其所有 entry 都拷贝到第二张全局哈希表中。
通过这样的操作分摊了一次高开销的操作,并且保证了数据的快速访问。
# 内置数据结构与底层实现
# 内置数据结构
- String
- 底层实现:简单动态字符串
- List
- 底层实现:压缩列表;双向链表
- Hash
- 底层实现:压缩列表;哈希表
- Set
- 底层实现:整数数组;哈希表
- Sorted Set (ZSet)
- 底层实现:压缩列表;跳表
# 底层实现
- 压缩列表
- 类似数组,每一个位置保存一个数据。
- 表头有三个字段:zlbytes, 列表长度; zltail, 列表尾偏移量;zllen, 列表 entry 个数.
- 表尾有一个字段:zlend, 列表结束。
- 当查找第一个和最后一个元素时,利用表头三个字段直接定位,时间复杂度 O(1)
- 当查找其他元素时,时间复杂度 O(n)
- 跳表
- 在链表的基础上,增加了多级索引。通过索引位置进行跳转来快速定位。
- 时间复杂度为 O(logN)
# 操作的时间复杂度
- 单元素操作
对单个元素的操作- O(1)
- Hash: HGET, HSET, HDEL
- Set: SADD, SREM, SRANDMEMBER
- O(1)
- 范围操作
对集合遍历操作,时间复杂度为 O(n). 但在 Redis 2.8 提供的 SCAN 操作下采用渐进式遍历,可以避免阻塞。- O(n)
- Hash: HGETALL
- Set: SMEMBERS
- List: LRANGE
- Sorted Set: ZRANGE
- O(n)
- 统计操作
- 统计集合中元素个数,时间复杂度为 O(1). 例如:LLEN, SCARD
- 例外情况
- 利用压缩列表和双向列表的头尾偏移量的操作时间复杂度都为 O(1). 例如:LPOP, RPOP, LPUSH, RPUSH