88
99
1010
11- ## 一、Redis 的五种基本数据类型和其数据结构
12-
13- 由于 Redis 是基于标准 C 写的,只有最基础的数据类型,因此 Redis 为了满足对外使用的 5 种数据类型,开发了属于自己** 独有的一套基础数据结构** ,使用这些数据结构来实现 5 种数据类型。
14-
15- ** Redis** 有 5 种基础数据类型,它们分别是:** string(字符串)** 、** list(列表)** 、** hash(字典)** 、** set(集合)** 和 ** zset(有序集合)** 。
16-
17- Redis 底层的数据结构包括:** 简单动态数组SDS、链表、字典、跳跃链表、整数集合、压缩列表、对象。**
18-
19- Redis 为了平衡空间和时间效率,针对 value 的具体类型在底层会采用不同的数据结构来实现,其中哈希表和压缩列表是复用比较多的数据结构,如下图展示了对外数据类型和底层数据结构之间的映射关系:
11+ 我们都知道 Redis 是个 KV 数据库,那 KV 结构的数据在 Redis 中是如何存储的呢?
2012
21- ![ ] ( https://tva1.sinaimg.cn/large/007S8ZIlly1ge0k8do3s3j30im0el7aa.jpg )
22-
23- ![ img] ( https://static001.geekbang.org/resource/image/82/01/8219f7yy651e566d47cc9f661b399f01.jpg )
24-
25- > 安装好 Redis,我们可以使用 ` redis-cli ` 来对 Redis 进行命令行的操作,当然 Redis 官方也提供了在线的调试器,你也可以在里面敲入命令进行操作:http://try.redis.io/#run
26-
27-
28-
29- ### 键和值用什么结构组织?
13+ ### 一、KV 如何存储?
3014
3115为了实现从键到值的快速访问,Redis 使用了一个哈希表来保存所有键值对。一个哈希表,其实就是一个数组,数组的每个元素称为一个哈希桶。类似我们的 HashMap
3216
3317看到这里,你可能会问了:“如果值是集合类型的话,作为数组元素的哈希桶怎么来保存呢?”
3418
35- 其实,哈希桶中的元素保存的并不是值本身,而是指向具体值的指针。这也就是说,不管值是 String,还是集合类型,哈希桶中的元素都是指向它们的指针。
19+ 其实,** 哈希桶中的元素保存的并不是值本身,而是指向具体值的指针** 。这也就是说,不管值是 String,还是集合类型,哈希桶中的元素都是指向它们的指针。
3620
3721在下图中,可以看到,哈希桶中的 entry 元素中保存了 ` *key ` 和 ` *value ` 指针,分别指向了实际的键和值,这样一来,即使值是一个集合,也可以通过 ` *value ` 指针被查找到。
3822
39- ![ ] ( https://static001.geekbang.org/resource/image/1c/5f/1cc8eaed5d1ca4e3cdbaa5a3d48dfb5f .jpg )
23+ ![ redis-kv ] ( https://tva1.sinaimg.cn/large/008i3skNly1gqsf47nimoj324o0u0hdt .jpg )
4024
41- 因为这个哈希表保存了所有的键值对,所以,我也把它称为全局哈希表 。哈希表的最大好处很明显,就是让我们可以用 $O(1)$ 的时间复杂度来快速查找到键值对——我们只需要计算键的哈希值,就可以知道它所对应的哈希桶位置,然后就可以访问相应的 entry 元素。
25+ 因为这个哈希表保存了所有的键值对,所以,也把它称为全局哈希表 。哈希表的最大好处很明显,就是让我们可以用 $O(1)$ 的时间复杂度来快速查找到键值对——我们只需要计算键的哈希值,就可以知道它所对应的哈希桶位置,然后就可以访问相应的 entry 元素。
4226
4327你看,这个查找过程主要依赖于哈希计算,和数据量的多少并没有直接关系。也就是说,不管哈希表里有 10 万个键还是 100 万个键,我们只需要一次计算就能找到相应的键。但是,如果你只是了解了哈希表的 $O(1)$ 复杂度和快速查找特性,那么,当你往 Redis 中写入大量数据后,就可能发现操作有时候会突然变慢了。这其实是因为你忽略了一个潜在的风险点,那就是哈希表的冲突问题和 rehash 可能带来的操作阻塞。
4428
@@ -50,7 +34,7 @@ Redis 解决哈希冲突的方式,就是链式哈希。和 JDK7 中的 HahsMap
5034
5135如下图所示:entry1、entry2 和 entry3 都需要保存在哈希桶 3 中,导致了哈希冲突。此时,entry1 元素会通过一个 ` *next ` 指针指向 entry2,同样,entry2 也会通过 ` *next ` 指针指向 entry3。这样一来,即使哈希桶 3 中的元素有 100 个,我们也可以通过 entry 元素中的指针,把它们连起来。这就形成了一个链表,也叫作哈希冲突链。
5236
53- ![ img ] ( https://static001.geekbang.org/resource/image/8a/28/8ac4cc6cf94968a502161f85d072e428 .jpg )
37+ ![ redis-kv-conflict ] ( https://tva1.sinaimg.cn/large/008i3skNly1gqsfbb5t10j31kx0u0qqw .jpg )
5438
5539但是,这里依然存在一个问题,哈希冲突链上的元素只能通过指针逐一查找再操作。如果哈希表里写入的数据越来越多,哈希冲突可能也会越来越多,这就会导致某些哈希冲突链过长,进而导致这个链上的元素查找耗时长,效率降低。对于追求“快”的 Redis 来说,这是不太能接受的。
5640
@@ -62,16 +46,36 @@ Redis 解决哈希冲突的方式,就是链式哈希。和 JDK7 中的 HahsMap
62462 . 把哈希表 1 中的数据重新映射并拷贝到哈希表 2 中;
63473 . 释放哈希表 1 的空间。
6448
65- 到此,我们就可以从哈希表 1 切换到哈希表 2,用增大的哈希表 2 保存更多数据,而原来的哈希表 1 留作下一次 rehash 扩容备用。这个过程看似简单,但是第二步涉及大量的数据拷贝,如果一次性把哈希表 1 中的数据都迁移完,会造成 Redis 线程阻塞,无法服务其他请求。此时,Redis 就无法快速访问数据了。为了避免这个问题,Redis 采用了渐进式 rehash。
49+ 到此,我们就可以从哈希表 1 切换到哈希表 2,用增大的哈希表 2 保存更多数据,而原来的哈希表 1 留作下一次 rehash 扩容备用。这个过程看似简单,但是第二步涉及大量的数据拷贝,如果一次性把哈希表 1 中的数据都迁移完,会造成 Redis 线程阻塞,无法服务其他请求。此时,Redis 就无法快速访问数据了。为了避免这个问题,Redis 采用了< mark >渐进式 rehash</ mark > 。
6650
6751简单来说就是在第二步拷贝数据时,Redis 仍然正常处理客户端请求,每处理一个请求时,从哈希表 1 中的第一个索引位置开始,顺带着将这个索引位置上的所有 entries 拷贝到哈希表 2 中;等处理下一个请求时,再顺带拷贝哈希表 1 中的下一个索引位置的 entries。如下图所示:
6852
69- ![ img ] ( https://static001.geekbang.org/resource/image/73/0c/73fb212d0b0928d96a0d7d6ayy76da0c .jpg )
53+ ![ redis-rehash ] ( https://tva1.sinaimg.cn/large/008i3skNly1gqsfuduehtj30u00v3e84 .jpg )
7054
7155渐进式 rehash 这样就巧妙地把一次性大量拷贝的开销,分摊到了多次处理请求的过程中,避免了耗时操作,保证了数据的快速访问。
7256
7357好了,到这里,你应该就能理解,Redis 的键和值是怎么通过哈希表组织的了。对于 String 类型来说,找到哈希桶就能直接增删改查了,所以,哈希表的 $O(1)$ 操作复杂度也就是它的复杂度了。但是,对于集合类型来说,即使找到哈希桶了,还要在集合中再进一步操作。
7458
59+ ## 一、Redis 的五种基本数据类型和其数据结构
60+
61+
62+
63+ 由于 Redis 是基于标准 C 写的,只有最基础的数据类型,因此 Redis 为了满足对外使用的 5 种数据类型,开发了属于自己** 独有的一套基础数据结构** ,使用这些数据结构来实现 5 种数据类型。
64+
65+ ** Redis** 有 5 种基础数据类型,它们分别是:** string(字符串)** 、** list(列表)** 、** hash(字典)** 、** set(集合)** 和 ** zset(有序集合)** 。
66+
67+ Redis 底层的数据结构包括:** 简单动态数组SDS、链表、字典、跳跃链表、整数集合、压缩列表、对象。**
68+
69+ Redis 为了平衡空间和时间效率,针对 value 的具体类型在底层会采用不同的数据结构来实现,其中哈希表和压缩列表是复用比较多的数据结构,如下图展示了对外数据类型和底层数据结构之间的映射关系:
70+
71+ ![ ] ( ../../../docs/_images/redis/data-type-structure.jpg )
72+
73+ > 安装好 Redis,我们可以使用 ` redis-cli ` 来对 Redis 进行命令行的操作,当然 Redis 官方也提供了在线的调试器,你也可以在里面敲入命令进行操作:http://try.redis.io/#run
74+
75+
76+
77+
78+
7579下面我们具体看下各种数据类型的底层实现和操作。
7680
7781
0 commit comments