Skip to content

Commit 8297e9d

Browse files
committed
blog
1 parent 71bb352 commit 8297e9d

9 files changed

Lines changed: 29 additions & 25 deletions

File tree

docs/.DS_Store

2 KB
Binary file not shown.

docs/.vuepress/dist

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
Subproject commit 356586bb9f66cae45a41358b7c93f4e38c1a6a17
1+
Subproject commit 01cb7f86d902379df686ef6fd7c07ef534dcf388

docs/_images/.DS_Store

2 KB
Binary file not shown.
3.09 MB
Loading
873 KB
Loading

docs/_images/redis/redis-kv.jpg

1.52 MB
Loading
4.42 MB
Loading

docs/data-management/.DS_Store

0 Bytes
Binary file not shown.

docs/data-management/Redis/Redis-Datatype.md

Lines changed: 28 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -8,37 +8,21 @@
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
6246
2. 把哈希表 1 中的数据重新映射并拷贝到哈希表 2 中;
6347
3. 释放哈希表 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

Comments
 (0)