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+ * [ 分布式锁实现] ( #分布式锁实现 )
109 * [ 使用setnx加expire实现加锁和时限] ( #使用setnx加expire实现加锁和时限 )
1110 * [ 使用getset加锁和获取过期时间] ( #使用getset加锁和获取过期时间 )
1211 * [ 2.0的setnx可以配置过期时间。] ( #20的setnx可以配置过期时间。 )
1312 * [ 使用sentx将值设为时间戳,通过lua脚本进行cas比较和删除操作] ( #使用sentx将值设为时间戳,通过lua脚本进行cas比较和删除操作 )
1413 * [ 分布式Redis锁:Redlock] ( #分布式redis锁:redlock )
1514 * [ 总结] ( #总结 )
16- * [ 分布式方案] ( #分布式方案 )
17- * [ redis事务] ( #redis事务 )
15+ * [ 分布式方案] ( #分布式方案 )
16+ * [ redis事务] ( #redis事务 )
1817 * [ redis脚本事务] ( #redis脚本事务 )
19- * [ 微信公众号] ( #微信公众号 )
18+ * [ 微信公众号] ( #微信公众号 )
2019 * [ Java技术江湖] ( #java技术江湖 )
2120 * [ 个人公众号:黄小斜] ( #个人公众号:黄小斜 )
2221
2322
24- ---
25- title: Redis原理与实践总结
26- date: 2018-07-08 22:15:12
27- tags:
28- - Redis
29- categories:
30- - 后端
31- - 技术总结
32- ---
33-
3423本文主要对Redis的设计和实现原理做了一个介绍很总结,有些东西我也介绍的不是很详细准确,尽量在自己的理解范围内把一些知识点和关键性技术做一个描述。如有错误,还望见谅,欢迎指出。
3524这篇文章主要还是参考我之前的技术专栏总结而来的。欢迎查看:
3625
@@ -110,7 +99,7 @@ dict字典与Java中的哈希表实现简直如出一辙,首先都是数组+
11099
111100其中dict同时保存两个entry数组,当需要扩容时,把节点转移到第二个数组即可,平时只使用一个数组。
112101
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)
114103
1151043 压缩链表ziplist
116105
@@ -140,7 +129,7 @@ quicklist的结构为什么这样设计呢?总结起来,大概又是一个
140129
141130但是,它不利于修改操作,每次数据变动都会引发一次内存的realloc。特别是当ziplist长度很长的时候,一次realloc可能会导致大批量的数据拷贝,进一步降低性能。
142131
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)
144133
1451345 zset
146135zset其实是两种结构的合并。也就是dict和skiplist结合而成的。dict负责保存数据对分数的映射,而skiplist用于根据分数进行数据的查询(相辅相成)
@@ -158,17 +147,17 @@ sortedset是由skiplist,dict和ziplist组成的。
158147
159148set是由一个叫zset的数据结构来实现的,这个zset包含一个dict + 一个skiplist。dict用来查询数据到分数(score)的对应关系,而skiplist用来根据分数查询数据(可能是范围查找)。
160149
161- 在本系列前面关于ziplist的文章里,我们介绍过,ziplist就是由很多数据项组成的一大块连续内存。由于sorted set的每一项元素都由数据和score组成,因此,当使用zadd命令插入一个(数据, score)对的时候,底层在相应的ziplist上就插入两个数据项:数据在前,score在后。
150+ 在本系列前面关于ziplist的文章里,我们介绍过,ziplist就是由很多数据项组成的一大块连续内存。由于sorted set的每一项元素都由数据和score组成,因此,当使用zadd命令插入一个(数据, score)对的时候,底层在相应的ziplist上就插入两个数据项:数据在前,score在后。
162151
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)
164153
165154skiplist的节点中存着节点值和分数。并且跳表是根据节点的分数进行排序的,所以可以根据节点分数进行范围查找。
166155
1671567inset
168157
169158inset是一个数字结合,他使用灵活的数据类型来保持数字。
170159
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)
172161
173162新创建的intset只有一个header,总共8个字节。其中encoding = 2, length = 0。
174163添加13, 5两个元素之后,因为它们是比较小的整数,都能使用2个字节表示,所以encoding不变,值还是2。
@@ -200,14 +189,12 @@ hash表由dict实现
200189
201190集合由inset实现。
202191
203- ![ image] ( https://user-gold-cdn.xitu.io/2017/9/17/2c71cff03efc96d2280d12602cc2aa92?imageView2/0/w/1280/h/960/format/webp/ignore-error/1 )
204-
205192
206193## redis server结构和数据库redisDb
207194
2081951 redis服务器中维护着一个数据库名为redisdb,实际上他是一个dict结构。
209196
210- Redis的数据库使用字典作为底层实现,数据库的增、删、查、改都是构建在字典的操作之上的。
197+ Redis的数据库使用字典作为底层实现,数据库的增、删、查、改都是构建在字典的操作之上的。
211198
2121992 redis服务器将所有数据库都保存在服务器状态结构redisServer(redis.h/redisServer)的db数组(应该是一个链表)里:
213200
@@ -232,18 +219,18 @@ Redis的数据库使用字典作为底层实现,数据库的增、删、查、
232219大的dict数组中有多个小的dict字典,他们共同负责存储redisdb的所有键值对。
233220
234221同时,对应的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 )
236223
2372244 过期键的删除策略
238225
2392262、过期键删除策略
240- 通过前面的介绍,大家应该都知道数据库键的过期时间都保存在过期字典里,那假如一个键过期了,那么这个过期键是什么时候被删除的呢?现在来看看redis的过期键的删除策略:
227+ 通过前面的介绍,大家应该都知道数据库键的过期时间都保存在过期字典里,那假如一个键过期了,那么这个过期键是什么时候被删除的呢?现在来看看redis的过期键的删除策略:
241228
242- a、定时删除:在设置键的过期时间的同时,创建一个定时器,在定时结束的时候,将该键删除;
229+ a、定时删除:在设置键的过期时间的同时,创建一个定时器,在定时结束的时候,将该键删除;
243230
244- b、惰性删除:放任键过期不管,在访问该键的时候,判断该键的过期时间是否已经到了,如果过期时间已经到了,就执行删除操作;
231+ b、惰性删除:放任键过期不管,在访问该键的时候,判断该键的过期时间是否已经到了,如果过期时间已经到了,就执行删除操作;
245232
246- c、定期删除:每隔一段时间,对数据库中的键进行一次遍历,删除过期的键。
233+ c、定期删除:每隔一段时间,对数据库中的键进行一次遍历,删除过期的键。
247234
248235## redis的事件模型
249236
@@ -275,7 +262,7 @@ redis处理请求的方式基于reactor线程模型,即一个线程处理连
275262
276263appendfsync同步频率的区别如下图:
277264
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 )
279266
280267## redis主从复制
281268
@@ -297,7 +284,7 @@ master增量的把写命令发给slave。
297284
298285### 使用setnx加expire实现加锁和时限
299286
300- 加锁时使用setnx设置key为1并设置超时时间,解锁时删除键
287+ 加锁时使用setnx设置key为1并设置超时时间,解锁时删除键
301288
302289 tryLock(){
303290 SETNX Key 1
@@ -312,7 +299,8 @@ master增量的把写命令发给slave。
312299### 使用getset加锁和获取过期时间
313300
314301针对锁无法释放问题的一个解决方案基于GETSET命令来实现
315-
302+
303+
316304 思路:
317305
318306 SETNX(Key,ExpireTime)获取锁
@@ -326,7 +314,8 @@ master增量的把写命令发给slave。
326314 注意:这个版本去掉了EXPIRE命令,改为通过Value时间戳值来判断过期
327315
328316
329-
317+
318+
330319### 2.0的setnx可以配置过期时间。
331320
332321 V2.0 基于SETNX
@@ -337,11 +326,10 @@ master增量的把写命令发给slave。
337326 release(){
338327 DELETE Key
339328 }
340- Redis 2.6.12版本后SETNX增加过期时间参数,这样就解决了两条命令无法保证原子性的问题。但是设想下面一个场景:
341- 1 . C1成功获取到了锁,之后C1因为GC进入等待或者未知原因导致任务执行过长,最后在锁失效前C1没有主动释放锁 2. C2在C1的锁超时后获取到锁,并且开始执行,这个时候C1和C2都同时在执行,会因重复执行造成数据不一致等未知情况 3. C1如果先执行完毕,则会释放C2的锁,此时可能导致另外一个C3进程获取到了锁
342329
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进程获取到了锁
345333
346334### 使用sentx将值设为时间戳,通过lua脚本进行cas比较和删除操作
347335
@@ -359,9 +347,10 @@ Redis 2.6.12版本后SETNX增加过期时间参数,这样就解决了两条命
359347 end
360348 )
361349 }
350+
362351这个方案通过指定Value为时间戳,并在释放锁的时候检查锁的Value是否为获取锁的Value,避免了V2.0版本中提到的C1释放了C2持有的锁的问题;另外在释放锁的时候因为涉及到多个Redis操作,并且考虑到Check And Set 模型的并发问题,所以使用Lua脚本来避免并发问题。
363352
364- 如果在并发极高的场景下,比如抢红包场景,可能存在UnixTimestamp重复问题,另外由于不能保证分布式环境下的物理时钟一致性,也可能存在UnixTimestamp重复问题,只不过极少情况下会遇到。
353+ 如果在并发极高的场景下,比如抢红包场景,可能存在UnixTimestamp重复问题,另外由于不能保证分布式环境下的物理时钟一致性,也可能存在UnixTimestamp重复问题,只不过极少情况下会遇到。
365354
366355### 分布式Redis锁:Redlock
367356
@@ -370,13 +359,15 @@ redlock的思想就是要求一个节点获取集群中N/2 + 1个节点
370359
371360
372361### 总结
362+
373363不论是基于SETNX版本的Redis单实例分布式锁,还是Redlock分布式锁,都是为了保证下特性
374364
375- 1 . 安全性:在同一时间不允许多个Client同时持有锁
376- 2 . 活性
365+ 1. 安全性:在同一时间不允许多个Client同时持有锁
366+ 2. 活性
377367
378368 死锁:锁最终应该能够被释放,即使Client端crash或者出现网络分区(通常基于超时机制)
379369 容错性:只要超过半数Redis节点可用,锁都能被正确获取和释放
370+
380371## 分布式方案
381372
3823731 主从复制,优点是备份简易使用。缺点是不能故障切换,并且不易扩展。
@@ -397,9 +388,7 @@ Redis cluster是一个去中心化、多实例Redis间进行数据共享的集
397388
398389每个节点上都保存着其他节点的信息,通过任一节点可以访问正常工作的节点数据,因为每台机器上的保留着完整的分片信息,某些机器不正常工作不影响整体集群的工作。并且每一台redis主机都会配备slave,通过sentinel自动切换。
399390
400- ![ image] ( http://7xivgs.com1.z0.glb.clouddn.com/codis02.png )
401-
402- ## redis事务
391+ ## redis事务
403392
404393事务
405394MULTI 、 EXEC 、 DISCARD 和 WATCH 是 Redis 事务相关的命令。事务可以一次执行多个命令, 并且带有以下两个重要的保证:
@@ -421,7 +410,8 @@ redis事务有一个特点,那就是在2.6以前,事务的一系列操作,
421410 因为不需要对回滚进行支持,所以 Redis 的内部可以保持简单且快速。
422411
423412
424-
413+
414+
425415### redis脚本事务
426416
427417Redis 脚本和事务
@@ -443,12 +433,14 @@ redis事务的ACID特性
443433
444434 事务具有一致性指的是,如果数据库在执行事务之前是一致的,那么在事务执行之后,无论事务是否执行成功,数据库也应该仍然一致的。
445435 ”一致“指的是数据符合数据库本身的定义和要求,没有包含非法或者无效的错误数据。redis通过谨慎的错误检测和简单的设计来保证事务一致性。
436+
446437③隔离性
447438
448439 事务的隔离性指的是,即使数据库中有多个事务并发在执行,各个事务之间也不会互相影响,并且在并发状态下执行的事务和串行执行的事务产生的结果完全
449440 相同。
450441 因为redis使用单线程的方式来执行事务(以及事务队列中的命令),并且服务器保证,在执行事务期间不会对事物进行中断,因此,redis的事务总是以串行
451442 的方式运行的,并且事务也总是具有隔离性的
443+
452444④持久性
453445
454446 事务的耐久性指的是,当一个事务执行完毕时,执行这个事务所得的结果已经被保持到永久存储介质里面。
@@ -469,6 +461,6 @@ redis事务的ACID特性
469461
470462作者是 985 硕士,蚂蚁金服 JAVA 工程师,专注于 JAVA 后端技术栈:SpringBoot、MySQL、分布式、中间件、微服务,同时也懂点投资理财,偶尔讲点算法和计算机理论基础,坚持学习和写作,相信终身学习的力量!
471463
472- ** 程序员3T技术学习资源:** 一些程序员学习技术的资源大礼包,关注公众号后,后台回复关键字 ** “资料”** 即可免费无套路获取。
464+ ** 程序员3T技术学习资源:** 一些程序员学习技术的资源大礼包,关注公众号后,后台回复关键字 ** “资料”** 即可免费无套路获取。
473465
474- ![ ] ( https://img-blog.csdnimg.cn/20190829222750556.jpg )
466+ ![ ] ( https://img-blog.csdnimg.cn/20190829222750556.jpg )
0 commit comments