Skip to content

Commit f4f8cb0

Browse files
committed
modify db and cache
1 parent 691605d commit f4f8cb0

File tree

26 files changed

+1064
-1604
lines changed

26 files changed

+1064
-1604
lines changed

docs/cache/Redis原理与实践总结.md

Lines changed: 44 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -1,36 +1,25 @@
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

115104
3 压缩链表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

145134
5 zset
146135
zset其实是两种结构的合并。也就是dict和skiplist结合而成的。dict负责保存数据对分数的映射,而skiplist用于根据分数进行数据的查询(相辅相成)
@@ -158,17 +147,17 @@ sortedset是由skiplist,dict和ziplist组成的。
158147

159148
set是由一个叫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

165154
skiplist的节点中存着节点值和分数。并且跳表是根据节点的分数进行排序的,所以可以根据节点分数进行范围查找。
166155

167156
7inset
168157

169158
inset是一个数字结合,他使用灵活的数据类型来保持数字。
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

208195
1 redis服务器中维护着一个数据库名为redisdb,实际上他是一个dict结构。
209196

210-
Redis的数据库使用字典作为底层实现,数据库的增、删、查、改都是构建在字典的操作之上的。
197+
Redis的数据库使用字典作为底层实现,数据库的增、删、查、改都是构建在字典的操作之上的。
211198

212199
2 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

237224
4 过期键的删除策略
238225

239226
2、过期键删除策略
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

276263
appendfsync同步频率的区别如下图:
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

382373
1 主从复制,优点是备份简易使用。缺点是不能故障切换,并且不易扩展。
@@ -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
事务
405394
MULTI 、 EXEC 、 DISCARD 和 WATCH 是 Redis 事务相关的命令。事务可以一次执行多个命令, 并且带有以下两个重要的保证:
@@ -421,7 +410,8 @@ redis事务有一个特点,那就是在2.6以前,事务的一系列操作,
421410
因为不需要对回滚进行支持,所以 Redis 的内部可以保持简单且快速。
422411

423412

424-
413+
414+
425415
### redis脚本事务
426416

427417
Redis 脚本和事务
@@ -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

Comments
 (0)