20
20
21
21
为了满足不同的业务场景,Redis 内置了多种数据类型实现(比如 String、Hash、Sorted Set、Bitmap)。并且,Redis 还支持事务 、持久化、Lua 脚本、多种开箱即用的集群方案(Redis Sentinel、Redis Cluster)。
22
22
23
- Redis 没有外部依赖,Linux 和 OS X 是 Redis 开发和测试最多的两个操作系统,官方推荐生产环境使用 Linux 部署 Redis。
23
+ Redis 没有外部依赖,Linux 和 OS X 是 Redis 开发和测试最多的两个操作系统,官方推荐生产环境使用 Linux 部署 Redis。
24
24
25
25
个人学习的话,你可以自己本机安装 Redis 或者通过 Redis 官网提供的[ 在线 Redis 环境] ( https://try.redis.io/ ) 来实际体验 Redis。
26
26
@@ -36,7 +36,7 @@ Redis 内部做了非常多的性能优化,比较重要的主要有下面 3
36
36
- Redis 基于 Reactor 模式设计开发了一套高效的事件处理模型,主要是单线程事件循环和 IO 多路复用(Redis 线程模式后面会详细介绍到);
37
37
- Redis 内置了多种优化过后的数据结构实现,性能非常高。
38
38
39
- 下面这张图片总结的挺不错的,分享一下,出自 [ Why is Redis so fast?] ( https://twitter.com/alexxubyte/status/1498703822528544770 ) 。
39
+ 下面这张图片总结的挺不错的,分享一下,出自 [ Why is Redis so fast?] ( https://twitter.com/alexxubyte/status/1498703822528544770 ) 。
40
40
41
41
![ why-redis-so-fast] ( ./images/why-redis-so-fast.png )
42
42
@@ -95,7 +95,7 @@ Memcached 是分布式缓存最开始兴起的那会,比较常用的。后来
95
95
96
96
### Redis 除了做缓存,还能做什么?
97
97
98
- - ** 分布式锁** : 通过 Redis 来做分布式锁是一种比较常见的方式。通常情况下,我们都是基于 Redisson 来实现分布式锁。相关阅读: [ 《分布式锁中的王者方案 - Redisson》 ] ( https://mp.weixin.qq.com/s/CbnPRfvq4m1sqo2uKI6qQw ) 。
98
+ - ** 分布式锁** : 通过 Redis 来做分布式锁是一种比较常见的方式。通常情况下,我们都是基于 Redisson 来实现分布式锁。关于 Redis 实现分布式锁的详细介绍,可以看我写的这篇文章: [ 分布式锁详解 ] ( https://javaguide.cn/distributed-system/distributed-lock.html ) 。
99
99
- ** 限流** :一般是通过 Redis + Lua 脚本的方式来实现限流。相关阅读:[ 《我司用了 6 年的 Redis 分布式限流器,可以说是非常厉害了!》] ( https://mp.weixin.qq.com/s/kyFAWH3mVNJvurQDt4vchA ) 。
100
100
- ** 消息队列** :Redis 自带的 list 数据结构可以作为一个简单的队列使用。Redis 5.0 中增加的 Stream 类型的数据结构更加适合用来做消息队列。它比较类似于 Kafka,有主题和消费组的概念,支持消息持久化以及 ACK 机制。
101
101
- ** 复杂业务场景** :通过 Redis 以及 Redis 扩展(比如 Redisson)提供的数据结构,我们可以很方便地完成很多复杂的业务场景比如通过 bitmap 统计活跃用户、通过 sorted set 维护排行榜。
@@ -113,6 +113,10 @@ Redis 5.0 新增加的一个数据结构 `Stream` 可以用来做消息队列,
113
113
114
114
相关文章推荐:[ Redis 消息队列的三种方案(List、Streams、Pub/Sub)] ( https://javakeeper.starfish.ink/data-management/Redis/Redis-MQ.html ) 。
115
115
116
+ ### 如何基于 Redis 实现分布式锁?
117
+
118
+ 关于 Redis 实现分布式锁的详细介绍,可以看我写的这篇文章:[ 分布式锁详解] ( https://javaguide.cn/distributed-system/distributed-lock.html ) 。
119
+
116
120
## Redis 数据结构
117
121
118
122
### Redis 常用的数据结构有哪些?
@@ -140,6 +144,83 @@ Redis 5.0 新增加的一个数据结构 `Stream` 可以用来做消息队列,
140
144
141
145
在绝大部分情况,我们建议使用 String 来存储对象数据即可!
142
146
147
+ ### String 的底层实现是什么?
148
+
149
+ Redis 是基于 C 语言编写的,但 Redis 的 String 类型的底层实现并不是 C 语言中的字符串(即以空字符 ` \0 ` 结尾的字符数组),而是自己编写了 [ SDS] ( https://github.com/antirez/sds ) (Simple Dynamic String,简单动态字符串) 来作为底层实现。
150
+
151
+ SDS 最早是 Redis 作者为日常 C 语言开发而设计的 C 字符串,后来被应用到了 Redis 上,并经过了大量的修改完善以适合高性能操作。
152
+
153
+ Redis7.0 的 SDS 的部分源码如下(https://github.com/redis/redis/blob/7.0/src/sds.h):
154
+
155
+ ``` c
156
+ /* Note: sdshdr5 is never used, we just access the flags byte directly.
157
+ * However is here to document the layout of type 5 SDS strings. */
158
+ struct __attribute__ ((__ packed__ )) sdshdr5 {
159
+ unsigned char flags; /* 3 lsb of type, and 5 msb of string length * /
160
+ char buf[ ] ;
161
+ };
162
+ struct __ attribute__ ((__ packed__ )) sdshdr8 {
163
+ uint8_t len; /* used * /
164
+ uint8_t alloc; /* excluding the header and null terminator * /
165
+ unsigned char flags; /* 3 lsb of type, 5 unused bits * /
166
+ char buf[ ] ;
167
+ };
168
+ struct __ attribute__ ((__ packed__ )) sdshdr16 {
169
+ uint16_t len; /* used * /
170
+ uint16_t alloc; /* excluding the header and null terminator * /
171
+ unsigned char flags; /* 3 lsb of type, 5 unused bits * /
172
+ char buf[ ] ;
173
+ };
174
+ struct __ attribute__ ((__ packed__ )) sdshdr32 {
175
+ uint32_t len; /* used * /
176
+ uint32_t alloc; /* excluding the header and null terminator * /
177
+ unsigned char flags; /* 3 lsb of type, 5 unused bits * /
178
+ char buf[ ] ;
179
+ };
180
+ struct __ attribute__ ((__ packed__ )) sdshdr64 {
181
+ uint64_t len; /* used * /
182
+ uint64_t alloc; /* excluding the header and null terminator * /
183
+ unsigned char flags; /* 3 lsb of type, 5 unused bits * /
184
+ char buf[ ] ;
185
+ };
186
+ ```
187
+
188
+ 通过源码可以看出,SDS 共有五种实现方式 SDS_TYPE_5(并未用到)、SDS_TYPE_8、SDS_TYPE_16、SDS_TYPE_32、SDS_TYPE_64,其中只有后四种实际用到。Redis 会根据初始化的长度决定使用哪种类型,从而减少内存的使用。
189
+
190
+ | 类型 | 字节 | 位 |
191
+ | -------- | ---- | ---- |
192
+ | sdshdr5 | < 1 | <8 |
193
+ | sdshdr8 | 1 | 8 |
194
+ | sdshdr16 | 2 | 16 |
195
+ | sdshdr32 | 4 | 32 |
196
+ | sdshdr64 | 8 | 64 |
197
+
198
+ 对于后四种实现都包含了下面这 4 个属性:
199
+
200
+ - `len` :字符串的长度也就是已经使用的字节数
201
+ - `alloc`:总共可用的字符空间大小,alloc-len 就是 SDS 剩余的空间大小
202
+ - `buf[]` :实际存储字符串的数组
203
+ - `flags` :低三位保存类型标志
204
+
205
+ SDS 相比于 C 语言中的字符串有如下提升:
206
+
207
+ 1. **可以避免缓冲区溢出** :C 语言中的字符串被修改(比如拼接)时,一旦没有分配足够长度的内存空间,就会造成缓冲区溢出。SDS 被修改时,会先根据 len 属性检查空间大小是否满足要求,如果不满足,则先扩展至所需大小再进行修改操作。
208
+ 2. **获取字符串长度的复杂度较低** : C 语言中的字符串的长度通常是经过遍历计数来实现的,时间复杂度为 O(n)。SDS 的长度获取直接读取 len 属性即可,时间复杂度为 O(1)。
209
+ 3. **减少内存分配次数** : 为了避免修改(增加/减少)字符串时,每次都需要重新分配内存(C 语言的字符串是这样的),SDS 实现了空间预分配和惰性空间释放两种优化策略。当 SDS 需要增加字符串时,Redis 会为 SDS 分配好内存,并且根据特定的算法分配多余的内存,这样可以减少连续执行字符串增长操作所需的内存重分配次数。当 SDS 需要减少字符串时,这部分内存不会立即被回收,会被记录下来,等待后续使用(支持手动释放,有对应的 API)。
210
+ 4. **二进制安全** :C 语言中的字符串以空字符 `\0` 作为字符串结束的标识,这存在一些问题,像一些二进制文件(比如图片、视频、音频)就可能包括空字符,C 字符串无法正确保存。SDS 使用 len 属性判断字符串是否结束,不存在这个问题。
211
+
212
+ 多提一嘴,很多文章里 SDS 的定义是下面这样的:
213
+
214
+ ```c
215
+ struct sdshdr {
216
+ unsigned int len;
217
+ unsigned int free;
218
+ char buf[];
219
+ };
220
+ ```
221
+
222
+ 这个也没错,Redis 3.2 之前就是这样定义的。后来,由于这种方式的定义存在问题,` len ` 和 ` free ` 的定义用了 4 个字节,造成了浪费。Redis 3.2 之后,Redis 改进了 SDS 的定义,将其划分为了现在的 5 种类型。
223
+
143
224
### 购物车信息用 String 还是 Hash 存储更好呢?
144
225
145
226
由于购物车中的商品频繁修改和变动,购物车信息建议使用 Hash 存储:
0 commit comments