1
- # Table of Contents
2
-
3
- * [ 使用和基础数据结构(外观)] ( #使用和基础数据结构(外观) )
4
- * [ 底层数据结构] ( #底层数据结构 )
5
- * [ redis server结构和数据库redisDb] ( #redis-server结构和数据库redisdb )
6
- * [ redis的事件模型] ( #redis的事件模型 )
7
- * [ 备份方式] ( #备份方式 )
8
- * [ redis主从复制] ( #redis主从复制 )
9
- * [ 分布式锁实现] ( #分布式锁实现 )
1
+ # 目录
2
+ * [ 使用和基础数据结构(外观)] ( #使用和基础数据结构(外观) )
3
+ * [ 底层数据结构] ( #底层数据结构 )
4
+ * [ redis server结构和数据库redisDb] ( #redis-server结构和数据库redisdb )
5
+ * [ redis的事件模型] ( #redis的事件模型 )
6
+ * [ 备份方式] ( #备份方式 )
7
+ * [ redis主从复制] ( #redis主从复制 )
8
+ * [ 分布式锁实现] ( #分布式锁实现 )
10
9
* [ 使用setnx加expire实现加锁和时限] ( #使用setnx加expire实现加锁和时限 )
11
10
* [ 使用getset加锁和获取过期时间] ( #使用getset加锁和获取过期时间 )
12
11
* [ 2.0的setnx可以配置过期时间。] ( #20的setnx可以配置过期时间。 )
13
12
* [ 使用sentx将值设为时间戳,通过lua脚本进行cas比较和删除操作] ( #使用sentx将值设为时间戳,通过lua脚本进行cas比较和删除操作 )
14
13
* [ 分布式Redis锁:Redlock] ( #分布式redis锁:redlock )
15
14
* [ 总结] ( #总结 )
16
- * [ 分布式方案] ( #分布式方案 )
17
- * [ redis事务] ( #redis事务 )
15
+ * [ 分布式方案] ( #分布式方案 )
16
+ * [ redis事务] ( #redis事务 )
18
17
* [ redis脚本事务] ( #redis脚本事务 )
19
- * [ 微信公众号] ( #微信公众号 )
18
+ * [ 微信公众号] ( #微信公众号 )
20
19
* [ Java技术江湖] ( #java技术江湖 )
21
20
* [ 个人公众号:黄小斜] ( #个人公众号:黄小斜 )
22
21
23
22
24
- ---
25
- title: Redis原理与实践总结
26
- date: 2018-07-08 22:15:12
27
- tags:
28
- - Redis
29
- categories:
30
- - 后端
31
- - 技术总结
32
- ---
33
-
34
23
本文主要对Redis的设计和实现原理做了一个介绍很总结,有些东西我也介绍的不是很详细准确,尽量在自己的理解范围内把一些知识点和关键性技术做一个描述。如有错误,还望见谅,欢迎指出。
35
24
这篇文章主要还是参考我之前的技术专栏总结而来的。欢迎查看:
36
25
@@ -110,7 +99,7 @@ dict字典与Java中的哈希表实现简直如出一辙,首先都是数组+
110
99
111
100
其中dict同时保存两个entry数组,当需要扩容时,把节点转移到第二个数组即可,平时只使用一个数组。
112
101
113
- ![ image] ( http ://zhangtielei. com/assets/photos_redis /redis_dict_structure.png)
102
+ ![ image] ( https ://java-tutorial.oss-cn-shanghai.aliyuncs. com/redis_dict_structure.png)
114
103
115
104
3 压缩链表ziplist
116
105
@@ -140,7 +129,7 @@ quicklist的结构为什么这样设计呢?总结起来,大概又是一个
140
129
141
130
但是,它不利于修改操作,每次数据变动都会引发一次内存的realloc。特别是当ziplist长度很长的时候,一次realloc可能会导致大批量的数据拷贝,进一步降低性能。
142
131
143
- ![ image] ( http ://zhangtielei. com/assets/photos_redis /redis_quicklist_structure.png)
132
+ ![ image] ( https ://java-tutorial.oss-cn-shanghai.aliyuncs. com/redis_quicklist_structure.png)
144
133
145
134
5 zset
146
135
zset其实是两种结构的合并。也就是dict和skiplist结合而成的。dict负责保存数据对分数的映射,而skiplist用于根据分数进行数据的查询(相辅相成)
@@ -158,17 +147,17 @@ sortedset是由skiplist,dict和ziplist组成的。
158
147
159
148
set是由一个叫zset的数据结构来实现的,这个zset包含一个dict + 一个skiplist。dict用来查询数据到分数(score)的对应关系,而skiplist用来根据分数查询数据(可能是范围查找)。
160
149
161
- 在本系列前面关于ziplist的文章里,我们介绍过,ziplist就是由很多数据项组成的一大块连续内存。由于sorted set的每一项元素都由数据和score组成,因此,当使用zadd命令插入一个(数据, score)对的时候,底层在相应的ziplist上就插入两个数据项:数据在前,score在后。
150
+ 在本系列前面关于ziplist的文章里,我们介绍过,ziplist就是由很多数据项组成的一大块连续内存。由于sorted set的每一项元素都由数据和score组成,因此,当使用zadd命令插入一个(数据, score)对的时候,底层在相应的ziplist上就插入两个数据项:数据在前,score在后。
162
151
163
- ![ image] ( http ://zhangtielei. com/assets/photos_redis/skiplist /redis_skiplist_example.png)
152
+ ![ image] ( https ://java-tutorial.oss-cn-shanghai.aliyuncs. com/redis_skiplist_example.png)
164
153
165
154
skiplist的节点中存着节点值和分数。并且跳表是根据节点的分数进行排序的,所以可以根据节点分数进行范围查找。
166
155
167
156
7inset
168
157
169
158
inset是一个数字结合,他使用灵活的数据类型来保持数字。
170
159
171
- ![ image ] ( http ://zhangtielei. com/assets/photos_redis/intset/redis_intset_add_example .png)
160
+ ![ ] ( https ://java-tutorial.oss-cn-shanghai.aliyuncs. com/20230406202310 .png)
172
161
173
162
新创建的intset只有一个header,总共8个字节。其中encoding = 2, length = 0。
174
163
添加13, 5两个元素之后,因为它们是比较小的整数,都能使用2个字节表示,所以encoding不变,值还是2。
@@ -200,14 +189,12 @@ hash表由dict实现
200
189
201
190
集合由inset实现。
202
191
203
- ![ image] ( https://user-gold-cdn.xitu.io/2017/9/17/2c71cff03efc96d2280d12602cc2aa92?imageView2/0/w/1280/h/960/format/webp/ignore-error/1 )
204
-
205
192
206
193
## redis server结构和数据库redisDb
207
194
208
195
1 redis服务器中维护着一个数据库名为redisdb,实际上他是一个dict结构。
209
196
210
- Redis的数据库使用字典作为底层实现,数据库的增、删、查、改都是构建在字典的操作之上的。
197
+ Redis的数据库使用字典作为底层实现,数据库的增、删、查、改都是构建在字典的操作之上的。
211
198
212
199
2 redis服务器将所有数据库都保存在服务器状态结构redisServer(redis.h/redisServer)的db数组(应该是一个链表)里:
213
200
@@ -232,18 +219,18 @@ Redis的数据库使用字典作为底层实现,数据库的增、删、查、
232
219
大的dict数组中有多个小的dict字典,他们共同负责存储redisdb的所有键值对。
233
220
234
221
同时,对应的expire字典则负责存储这些键的过期时间
235
- ![ image ] ( https://img-blog.csdn.net/20180701224321524?watermark/2/text/aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L2E3MjQ4ODg=/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70 )
222
+ ![ ] ( https://java-tutorial.oss-cn-shanghai.aliyuncs.com/20230406201529.png )
236
223
237
224
4 过期键的删除策略
238
225
239
226
2、过期键删除策略
240
- 通过前面的介绍,大家应该都知道数据库键的过期时间都保存在过期字典里,那假如一个键过期了,那么这个过期键是什么时候被删除的呢?现在来看看redis的过期键的删除策略:
227
+ 通过前面的介绍,大家应该都知道数据库键的过期时间都保存在过期字典里,那假如一个键过期了,那么这个过期键是什么时候被删除的呢?现在来看看redis的过期键的删除策略:
241
228
242
- a、定时删除:在设置键的过期时间的同时,创建一个定时器,在定时结束的时候,将该键删除;
229
+ a、定时删除:在设置键的过期时间的同时,创建一个定时器,在定时结束的时候,将该键删除;
243
230
244
- b、惰性删除:放任键过期不管,在访问该键的时候,判断该键的过期时间是否已经到了,如果过期时间已经到了,就执行删除操作;
231
+ b、惰性删除:放任键过期不管,在访问该键的时候,判断该键的过期时间是否已经到了,如果过期时间已经到了,就执行删除操作;
245
232
246
- c、定期删除:每隔一段时间,对数据库中的键进行一次遍历,删除过期的键。
233
+ c、定期删除:每隔一段时间,对数据库中的键进行一次遍历,删除过期的键。
247
234
248
235
## redis的事件模型
249
236
@@ -275,7 +262,7 @@ redis处理请求的方式基于reactor线程模型,即一个线程处理连
275
262
276
263
appendfsync同步频率的区别如下图:
277
264
278
- ![ 这里写图片描述 ] ( https://img-blog.csdn.net/20170313210401173?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQveGxnZW4xNTczODc=/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/SouthEast )
265
+ ![ ] ( https://java-tutorial.oss-cn-shanghai.aliyuncs.com/20230406201549.png )
279
266
280
267
## redis主从复制
281
268
@@ -297,7 +284,7 @@ master增量的把写命令发给slave。
297
284
298
285
### 使用setnx加expire实现加锁和时限
299
286
300
- 加锁时使用setnx设置key为1并设置超时时间,解锁时删除键
287
+ 加锁时使用setnx设置key为1并设置超时时间,解锁时删除键
301
288
302
289
tryLock(){
303
290
SETNX Key 1
@@ -312,7 +299,8 @@ master增量的把写命令发给slave。
312
299
### 使用getset加锁和获取过期时间
313
300
314
301
针对锁无法释放问题的一个解决方案基于GETSET命令来实现
315
-
302
+
303
+
316
304
思路:
317
305
318
306
SETNX(Key,ExpireTime)获取锁
@@ -326,7 +314,8 @@ master增量的把写命令发给slave。
326
314
注意:这个版本去掉了EXPIRE命令,改为通过Value时间戳值来判断过期
327
315
328
316
329
-
317
+
318
+
330
319
### 2.0的setnx可以配置过期时间。
331
320
332
321
V2.0 基于SETNX
@@ -337,11 +326,10 @@ master增量的把写命令发给slave。
337
326
release(){
338
327
DELETE Key
339
328
}
340
- Redis 2.6.12版本后SETNX增加过期时间参数,这样就解决了两条命令无法保证原子性的问题。但是设想下面一个场景:
341
- 1 . C1成功获取到了锁,之后C1因为GC进入等待或者未知原因导致任务执行过长,最后在锁失效前C1没有主动释放锁 2. C2在C1的锁超时后获取到锁,并且开始执行,这个时候C1和C2都同时在执行,会因重复执行造成数据不一致等未知情况 3. C1如果先执行完毕,则会释放C2的锁,此时可能导致另外一个C3进程获取到了锁
342
329
343
- 流程图如下
344
- ![ 2.] ( http://tech.dianwoda.com/content/images/2018/04/unsafe-lock.png )
330
+ Redis 2.6.12版本后SETNX增加过期时间参数,这样就解决了两条命令无法保证原子性的问题。但是设想下面一个场景:
331
+
332
+ 1 . C1成功获取到了锁,之后C1因为GC进入等待或者未知原因导致任务执行过长,最后在锁失效前C1没有主动释放锁 2. C2在C1的锁超时后获取到锁,并且开始执行,这个时候C1和C2都同时在执行,会因重复执行造成数据不一致等未知情况 3. C1如果先执行完毕,则会释放C2的锁,此时可能导致另外一个C3进程获取到了锁
345
333
346
334
### 使用sentx将值设为时间戳,通过lua脚本进行cas比较和删除操作
347
335
@@ -359,9 +347,10 @@ Redis 2.6.12版本后SETNX增加过期时间参数,这样就解决了两条命
359
347
end
360
348
)
361
349
}
350
+
362
351
这个方案通过指定Value为时间戳,并在释放锁的时候检查锁的Value是否为获取锁的Value,避免了V2.0版本中提到的C1释放了C2持有的锁的问题;另外在释放锁的时候因为涉及到多个Redis操作,并且考虑到Check And Set 模型的并发问题,所以使用Lua脚本来避免并发问题。
363
352
364
- 如果在并发极高的场景下,比如抢红包场景,可能存在UnixTimestamp重复问题,另外由于不能保证分布式环境下的物理时钟一致性,也可能存在UnixTimestamp重复问题,只不过极少情况下会遇到。
353
+ 如果在并发极高的场景下,比如抢红包场景,可能存在UnixTimestamp重复问题,另外由于不能保证分布式环境下的物理时钟一致性,也可能存在UnixTimestamp重复问题,只不过极少情况下会遇到。
365
354
366
355
### 分布式Redis锁:Redlock
367
356
@@ -370,13 +359,15 @@ redlock的思想就是要求一个节点获取集群中N/2 + 1个节点
370
359
371
360
372
361
### 总结
362
+
373
363
不论是基于SETNX版本的Redis单实例分布式锁,还是Redlock分布式锁,都是为了保证下特性
374
364
375
- 1 . 安全性:在同一时间不允许多个Client同时持有锁
376
- 2 . 活性
365
+ 1. 安全性:在同一时间不允许多个Client同时持有锁
366
+ 2. 活性
377
367
378
368
死锁:锁最终应该能够被释放,即使Client端crash或者出现网络分区(通常基于超时机制)
379
369
容错性:只要超过半数Redis节点可用,锁都能被正确获取和释放
370
+
380
371
## 分布式方案
381
372
382
373
1 主从复制,优点是备份简易使用。缺点是不能故障切换,并且不易扩展。
@@ -397,9 +388,7 @@ Redis cluster是一个去中心化、多实例Redis间进行数据共享的集
397
388
398
389
每个节点上都保存着其他节点的信息,通过任一节点可以访问正常工作的节点数据,因为每台机器上的保留着完整的分片信息,某些机器不正常工作不影响整体集群的工作。并且每一台redis主机都会配备slave,通过sentinel自动切换。
399
390
400
- ![ image] ( http://7xivgs.com1.z0.glb.clouddn.com/codis02.png )
401
-
402
- ## redis事务
391
+ ## redis事务
403
392
404
393
事务
405
394
MULTI 、 EXEC 、 DISCARD 和 WATCH 是 Redis 事务相关的命令。事务可以一次执行多个命令, 并且带有以下两个重要的保证:
@@ -421,7 +410,8 @@ redis事务有一个特点,那就是在2.6以前,事务的一系列操作,
421
410
因为不需要对回滚进行支持,所以 Redis 的内部可以保持简单且快速。
422
411
423
412
424
-
413
+
414
+
425
415
### redis脚本事务
426
416
427
417
Redis 脚本和事务
@@ -443,12 +433,14 @@ redis事务的ACID特性
443
433
444
434
事务具有一致性指的是,如果数据库在执行事务之前是一致的,那么在事务执行之后,无论事务是否执行成功,数据库也应该仍然一致的。
445
435
”一致“指的是数据符合数据库本身的定义和要求,没有包含非法或者无效的错误数据。redis通过谨慎的错误检测和简单的设计来保证事务一致性。
436
+
446
437
③隔离性
447
438
448
439
事务的隔离性指的是,即使数据库中有多个事务并发在执行,各个事务之间也不会互相影响,并且在并发状态下执行的事务和串行执行的事务产生的结果完全
449
440
相同。
450
441
因为redis使用单线程的方式来执行事务(以及事务队列中的命令),并且服务器保证,在执行事务期间不会对事物进行中断,因此,redis的事务总是以串行
451
442
的方式运行的,并且事务也总是具有隔离性的
443
+
452
444
④持久性
453
445
454
446
事务的耐久性指的是,当一个事务执行完毕时,执行这个事务所得的结果已经被保持到永久存储介质里面。
@@ -469,6 +461,6 @@ redis事务的ACID特性
469
461
470
462
作者是 985 硕士,蚂蚁金服 JAVA 工程师,专注于 JAVA 后端技术栈:SpringBoot、MySQL、分布式、中间件、微服务,同时也懂点投资理财,偶尔讲点算法和计算机理论基础,坚持学习和写作,相信终身学习的力量!
471
463
472
- ** 程序员3T技术学习资源:** 一些程序员学习技术的资源大礼包,关注公众号后,后台回复关键字 ** “资料”** 即可免费无套路获取。
464
+ ** 程序员3T技术学习资源:** 一些程序员学习技术的资源大礼包,关注公众号后,后台回复关键字 ** “资料”** 即可免费无套路获取。
473
465
474
- ![ ] ( https://img-blog.csdnimg.cn/20190829222750556.jpg )
466
+ ![ ] ( https://img-blog.csdnimg.cn/20190829222750556.jpg )
0 commit comments