Skip to content

Commit 3729b6f

Browse files
committed
[docs update]完善Redis部分的内容
1 parent 1a64bb5 commit 3729b6f

File tree

8 files changed

+195
-29
lines changed

8 files changed

+195
-29
lines changed

docs/.vuepress/sidebar.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -290,6 +290,7 @@ export const sidebarConfig = sidebar({
290290
"redis-data-structures-01",
291291
"redis-data-structures-02",
292292
"redis-memory-fragmentation",
293+
"redis-common-blocking-problems-summary",
293294
"redis-cluster",
294295
],
295296
},
-4.95 KB
Binary file not shown.
Lines changed: 107 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,107 @@
1+
---
2+
title: Redis 常见阻塞原因总结
3+
category: 数据库
4+
tag:
5+
- Redis
6+
---
7+
8+
> 本文整理完善自:https://mp.weixin.qq.com/s/0Nqfq_eQrUb12QH6eBbHXA ,作者:阿Q说代码
9+
10+
## O(n) 命令阻塞
11+
12+
使用` O(n)` 命令可能会导致阻塞,例如`keys *``hgetall``lrange``smembers``zrange``sinter``sunion` 命令。这些命令时间复杂度是 O(n),有时候也会全表扫描,随着 n 的增大耗时也会越大从而导致客户端阻塞。
13+
14+
不过, 这些命令并不是一定不能使用,但是需要明确 N 的值。有遍历的需求可以使用 `hscan``sscan``zscan` 代替。
15+
16+
## SAVE 创建 RDB 快照阻塞
17+
18+
Redis 提供了两个命令来生成 RDB 快照文件:
19+
20+
- `save` : 同步保存操作,会阻塞 Redis 主线程;
21+
- `bgsave` : fork 出一个子进程,子进程执行,不会阻塞 Redis 主线程,默认选项。
22+
23+
默认情况下,Redis 默认配置会使用 `bgsave` 命令。如果手动使用 `save` 命令生成 RDB 快照文件的话,就会阻塞主线程。
24+
25+
## AOF 日志记录阻塞
26+
27+
Redis AOF 持久化机制是在执行完命令之后再记录日志,这和关系型数据库(如 MySQL)通常都是执行命令之前记录日志(方便故障恢复)不同。
28+
29+
![AOF 记录日志过程](https://oss.javaguide.cn/github/javaguide/database/redis/redis-aof-write-log-disc.png)
30+
31+
**为什么是在执行完命令之后记录日志呢?**
32+
33+
- 避免额外的检查开销,AOF 记录日志不会对命令进行语法检查;
34+
- 在命令执行完之后再记录,不会阻塞当前的命令执行。
35+
36+
这样也带来了风险(我在前面介绍 AOF 持久化的时候也提到过):
37+
38+
- 如果刚执行完命令 Redis 就宕机会导致对应的修改丢失;
39+
- **可能会阻塞后续其他命令的执行(AOF 记录日志是在 Redis 主线程中进行的)**
40+
41+
## AOF 刷盘阻塞
42+
43+
开启 AOF 持久化后每执行一条会更改 Redis 中的数据的命令,Redis 就会将该命令写入到 AOF 缓冲区 `server.aof_buf` 中,然后再根据 `appendfsync` 配置来决定何时将其同步到硬盘中的 AOF 文件。
44+
45+
在 Redis 的配置文件中存在三种不同的 AOF 持久化方式( `fsync`策略),它们分别是:
46+
47+
```bash
48+
appendfsync always #每次有数据修改发生时都会调用fsync函数同步AOF文件,fsync完成后线程返回,这样会严重降低Redis的速度
49+
appendfsync everysec #每秒钟调用fsync函数同步一次AOF文件
50+
appendfsync no #让操作系统决定何时进行同步,一般为30秒一次
51+
```
52+
53+
当调用 fsync 函数同步 AOF 文件时,需要等待,直到写入完成。当磁盘压力太大的时候,主线程可能会被阻塞。
54+
55+
## AOF 重写阻塞
56+
57+
1. fork 出一条子线程来将文件重写,在执行 `BGREWRITEAOF` 命令时,Redis 服务器会维护一个 AOF 重写缓冲区,该缓冲区会在子线程创建新 AOF 文件期间,记录服务器执行的所有写命令。
58+
2. 当子线程完成创建新 AOF 文件的工作之后,服务器会将重写缓冲区中的所有内容追加到新 AOF 文件的末尾,使得新的 AOF 文件保存的数据库状态与现有的数据库状态一致。
59+
3. 最后,服务器用新的 AOF 文件替换旧的 AOF 文件,以此来完成 AOF 文件重写操作。
60+
61+
阻塞就是出现在第 2 步的过程中,将缓冲区中新数据写到新文件的过程中会产生**阻塞**
62+
63+
相关阅读:[Redis AOF重写阻塞问题分析](https://cloud.tencent.com/developer/article/1633077)
64+
65+
## 大 Key 问题
66+
67+
如果一个 key 对应的 value 所占用的内存比较大,那这个 key 就可以看作是 bigkey。具体多大才算大呢?有一个不是特别精确的参考标准:string 类型的 value 超过 10 kb,复合类型的 value 包含的元素超过 5000 个(对于复合类型的 value 来说,不一定包含的元素越多,占用的内存就越多)。
68+
69+
大 key 造成的阻塞问题如下:
70+
71+
- 客户端超时阻塞:由于 Redis 执行命令是单线程处理,然后在操作大 key 时会比较耗时,那么就会阻塞 Redis,从客户端这一视角看,就是很久很久都没有响应。
72+
- 引发网络阻塞:每次获取大 key 产生的网络流量较大,如果一个 key 的大小是 1 MB,每秒访问量为 1000,那么每秒会产生 1000MB 的流量,这对于普通千兆网卡的服务器来说是灾难性的。
73+
- 阻塞工作线程:如果使用 del 删除大 key 时,会阻塞工作线程,这样就没办法处理后续的命令。
74+
75+
### 查找大 key
76+
77+
当我们在使用 Redis 自带的 `--bigkeys` 参数查找大 key 时,最好选择在从节点上执行该命令,因为主节点上执行时,会**阻塞**主节点。
78+
79+
- 我们还可以使用 SCAN 命令来查找大 key;
80+
81+
- 通过分析 RDB 文件来找出 big key,这种方案的前提是 Redis 采用的是 RDB 持久化。网上有现成的工具:
82+
83+
- - redis-rdb-tools:Python 语言写的用来分析 Redis 的 RDB 快照文件用的工具
84+
- rdb_bigkeys:Go 语言写的用来分析 Redis 的 RDB 快照文件用的工具,性能更好。
85+
86+
### 删除大 key
87+
88+
删除操作的本质是要释放键值对占用的内存空间。
89+
90+
释放内存只是第一步,为了更加高效地管理内存空间,在应用程序释放内存时,**操作系统需要把释放掉的内存块插入一个空闲内存块的链表**,以便后续进行管理和再分配。这个过程本身需要一定时间,而且会**阻塞**当前释放内存的应用程序。
91+
92+
所以,如果一下子释放了大量内存,空闲内存块链表操作时间就会增加,相应地就会造成 Redis 主线程的阻塞,如果主线程发生了阻塞,其他所有请求可能都会超时,超时越来越多,会造成 Redis 连接耗尽,产生各种异常。
93+
94+
删除大 key 时建议采用分批次删除和异步删除的方式进行。
95+
96+
## 清空数据库
97+
98+
清空数据库和上面 bigkey 删除也是同样道理,`flushdb``flushall` 也涉及到删除和释放所有的键值对,也是 Redis 的阻塞点。
99+
100+
## 集群扩容
101+
102+
Redis 集群可以进行节点的动态扩容缩容,这一过程目前还处于半自动状态,需要人工介入。
103+
104+
在扩缩容的时候,需要进行数据迁移。而 Redis 为了保证迁移的一致性,迁移所有操作都是同步操作。
105+
106+
执行迁移时,两端的 Redis 均会进入时长不等的阻塞状态,对于小 Key,该时间可以忽略不计,但如果一旦 Key 的内存使用过大,严重的时候会触发集群内的故障转移,造成不必要的切换。
107+

docs/database/redis/redis-questions-01.md

Lines changed: 51 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -524,9 +524,13 @@ Redis 提供 6 种数据淘汰策略:
524524

525525
### 怎么保证 Redis 挂掉之后再重启数据可以进行恢复?
526526

527-
很多时候我们需要持久化数据也就是将内存中的数据写入到硬盘里面,大部分原因是为了之后重用数据(比如重启机器、机器故障之后恢复数据),或者是为了防止系统故障而将数据备份到一个远程位置。
527+
使用缓存的时候,我们经常需要对内容中的数据进行持久化也就是将内存中的数据写入到硬盘里面。大部分原因是为了之后重用数据(比如重启机器、机器故障之后恢复数据),或者是为了防止系统故障而将数据备份到一个远程位置。
528528

529-
Redis 不同于 Memcached 的很重要一点就是,Redis 支持持久化,而且支持两种不同的持久化操作。**Redis 的一种持久化方式叫快照(snapshotting,RDB),另一种方式是只追加文件(append-only file, AOF)**。这两种方法各有千秋,下面我会详细这两种持久化方法是什么,怎么用,如何选择适合自己的持久化方法。
529+
Redis 不同于 Memcached 的很重要一点就是,Redis 支持持久化,而且支持 3 种持久化方式:
530+
531+
- 快照(snapshotting,RDB)
532+
- 只追加文件(append-only file, AOF)
533+
- RDB 和 AOF 的混合持久化(Redis 4.0 新增)
530534

531535
### 什么是 RDB 持久化?
532536

@@ -553,36 +557,52 @@ Redis 提供了两个命令来生成 RDB 快照文件:
553557
554558
### 什么是 AOF 持久化?
555559

556-
与快照持久化相比,AOF 持久化的实时性更好,因此已成为主流的持久化方案。默认情况下 Redis 没有开启 AOF(append only file)方式的持久化,可以通过 appendonly 参数开启:
560+
与快照持久化相比,AOF 持久化的实时性更好。默认情况下 Redis 没有开启 AOF(append only file)方式的持久化(Redis 6.0 之后已经默认是开启了),可以通过 appendonly 参数开启:
557561

558562
```bash
559563
appendonly yes
560564
```
561565

562-
开启 AOF 持久化后每执行一条会更改 Redis 中的数据的命令,Redis 就会将该命令写入到内存缓存 `server.aof_buf` 中,然后再根据 `appendfsync` 配置来决定何时将其同步到硬盘中的 AOF 文件。
566+
开启 AOF 持久化后每执行一条会更改 Redis 中的数据的命令,Redis 就会将该命令写入到AOF 缓冲区 `server.aof_buf` 中,然后再根据 `appendfsync` 配置来决定何时将其同步到硬盘中的 AOF 文件。
563567

564568
AOF 文件的保存位置和 RDB 文件的位置相同,都是通过 dir 参数设置的,默认的文件名是 `appendonly.aof`
565569

566-
在 Redis 的配置文件中存在三种不同的 AOF 持久化方式,它们分别是:
570+
在 Redis 的配置文件中存在三种不同的 AOF 持久化方式`fsync`策略),它们分别是:
567571

568572
```bash
569-
appendfsync always #每次有数据修改发生时都会写入AOF文件,这样会严重降低Redis的速度
570-
appendfsync everysec #每秒钟同步一次,显式地将多个写命令同步到硬盘
571-
appendfsync no #让操作系统决定何时进行同步
573+
appendfsync always #每次有数据修改发生时都会调用fsync函数同步AOF文件,fsync完成后线程返回,这样会严重降低Redis的速度
574+
appendfsync everysec #每秒钟调用fsync函数同步一次AOF文件
575+
appendfsync no #让操作系统决定何时进行同步,一般为30秒一次
572576
```
573577

574-
为了兼顾数据和写入性能,用户可以考虑 `appendfsync everysec` 选项 ,让 Redis 每秒同步一次 AOF 文件,Redis 性能几乎没受到任何影响。而且这样即使出现系统崩溃,用户最多只会丢失一秒之内产生的数据。当硬盘忙于执行写入操作的时候,Redis 还会优雅的放慢自己的速度以便适应硬盘的最大写入速度。
578+
为了兼顾数据和写入性能,可以考虑 `appendfsync everysec` 选项 ,让 Redis 每秒同步一次 AOF 文件,Redis 性能几乎没受到任何影响。而且这样即使出现系统崩溃,用户最多只会丢失一秒之内产生的数据。当硬盘忙于执行写入操作的时候,Redis 还会优雅的放慢自己的速度以便适应硬盘的最大写入速度。
579+
580+
从 Redis 7.0.0 开始,Redis 使用了 **Multi Part AOF** 机制。顾名思义,Multi Part AOF 就是将原来的单个 AOF 文件拆分成多个 AOF 文件。在 Multi Part AOF 中,AOF 文件被分为三种类型,分别为:
581+
582+
- BASE:表示基础 AOF 文件,它一般由子进程通过重写产生,该文件最多只有一个。
583+
- INCR:表示增量 AOF 文件,它一般会在 AOFRW 开始执行时被创建,该文件可能存在多个。
584+
- HISTORY:表示历史 AOF 文件,它由 BASE 和 INCR AOF 变化而来,每次 AOFRW 成功完成时,本次 AOFRW 之前对应的 BASE 和 INCR AOF 都将变为 HISTORY,HISTORY 类型的 AOF 会被 Redis 自动删除。
585+
586+
Multi Part AOF 不是重点,了解即可,详细介绍可以看看阿里开发者的[Redis 7.0 Multi Part AOF 的设计和实现](https://zhuanlan.zhihu.com/p/467217082) 这篇文章。
587+
588+
**相关 issue**[Redis 的 AOF 方式 #783](https://github.com/Snailclimb/JavaGuide/issues/783)
589+
590+
### AOF工作基本流程是怎样的?
591+
592+
AOF 持久化功能的实现可以简单分为 4 步:
575593

576-
**相关 issue**
594+
1. 命令写入(append):所有的写命令会追加到 AOF 缓冲区中。
595+
2. 文件同步(sync):AOF缓冲区根据对应的 fsync 策略向硬盘做同步操作。
596+
3. 文件重写(rewrite):随着AOF文件越来越大,需要定期对AOF文件进行重写,达到压缩的目的。
597+
4. 重启加载(load):当 Redis 重启时,可以加载 AOF 文件进行数据恢复。
577598

578-
- [Redis 的 AOF 方式 #783](https://github.com/Snailclimb/JavaGuide/issues/783)
579-
- [Redis AOF 重写描述不准确 #1439](https://github.com/Snailclimb/JavaGuide/issues/1439)
599+
![](https://oss.javaguide.cn/github/javaguide/database/redis/aof-work-process.png)
580600

581-
### AOF 日志是如何实现的
601+
### AOF 为什么是在执行完命令之后记录日志
582602

583603
关系型数据库(如 MySQL)通常都是执行命令之前记录日志(方便故障恢复),而 Redis AOF 持久化机制是在执行完命令之后再记录日志。
584604

585-
![AOF 记录日志过程](./images/redis-aof-write-log-disc.png)
605+
![AOF 记录日志过程](https://oss.javaguide.cn/github/javaguide/database/redis/redis-aof-write-log-disc.png)
586606

587607
**为什么是在执行完命令之后记录日志呢?**
588608

@@ -604,6 +624,18 @@ AOF 重写是一个有歧义的名字,该功能是通过读取数据库中的
604624

605625
Redis 7.0 版本之前,如果在重写期间有写入命令,AOF 可能会使用大量内存,重写期间到达的所有写入命令都会写入磁盘两次。
606626

627+
**相关 issue**[Redis AOF 重写描述不准确 #1439](https://github.com/Snailclimb/JavaGuide/issues/1439)
628+
629+
### Redis 4.0 对于持久化机制做了什么优化?
630+
631+
由于 RDB 和 AOF 各有优势,于是,Redis 4.0 开始支持 RDB 和 AOF 的混合持久化(默认关闭,可以通过配置项 `aof-use-rdb-preamble` 开启)。
632+
633+
如果把混合持久化打开,AOF 重写的时候就直接把 RDB 的内容写到 AOF 文件开头。这样做的好处是可以结合 RDB 和 AOF 的优点, 快速加载同时避免丢失过多的数据。当然缺点也是有的, AOF 里面的 RDB 部分是压缩格式不再是 AOF 格式,可读性较差。
634+
635+
官方文档地址:https://redis.io/topics/persistence
636+
637+
![](https://oss.javaguide.cn/github/javaguide/database/redis/redis4.0-persitence.png)
638+
607639
### 如何选择 RDB 和 AOF?
608640

609641
关于 RDB 和 AOF 的优缺点,官网上面也给了比较详细的说明[Redis persistence](https://redis.io/docs/manual/persistence/),这里结合自己的理解简单总结一下。
@@ -619,15 +651,11 @@ Redis 7.0 版本之前,如果在重写期间有写入命令,AOF 可能会使
619651
- RDB 文件是以特定的二进制格式保存的,并且在 Redis 版本演进中有多个版本的 RDB,所以存在老版本的 Redis 服务不兼容新版本的 RDB 格式的问题。
620652
- AOF 以一种易于理解和解析的格式包含所有操作的日志。你可以轻松地导出 AOF 文件进行分析,你也可以直接操作 AOF 文件来解决一些问题。比如,如果执行`FLUSHALL`命令意外地刷新了所有内容后,只要 AOF 文件没有被重写,删除最新命令并重启即可恢复之前的状态。
621653

622-
### Redis 4.0 对于持久化机制做了什么优化?
654+
综上:
623655

624-
由于 RDB 和 AOF 各有优势,于是,Redis 4.0 开始支持 RDB 和 AOF 的混合持久化(默认关闭,可以通过配置项 `aof-use-rdb-preamble` 开启)。
625-
626-
如果把混合持久化打开,AOF 重写的时候就直接把 RDB 的内容写到 AOF 文件开头。这样做的好处是可以结合 RDB 和 AOF 的优点, 快速加载同时避免丢失过多的数据。当然缺点也是有的, AOF 里面的 RDB 部分是压缩格式不再是 AOF 格式,可读性较差。
627-
628-
官方文档地址:https://redis.io/topics/persistence
629-
630-
![](https://oss.javaguide.cn/github/javaguide/database/redis/redis4.0-persitence.png)
656+
- Redis 保存的数据丢失一些也没什么影响的话,可以选择使用 RDB。
657+
- 不建议单独使用 AOF,因为时不时地创建一个 RDB 快照可以进行数据库备份、更快的重启以及解决 AOF 引擎错误。
658+
- 如果保存的数据要求安全性比较高的话,建议同时开启 RDB 和 AOF 持久化或者开启 RDB 和 AOF 混合持久化。
631659

632660
## 参考
633661

@@ -636,3 +664,4 @@ Redis 7.0 版本之前,如果在重写期间有写入命令,AOF 可能会使
636664
- Redis 命令手册:https://www.redis.com.cn/commands.html
637665
- WHY Redis choose single thread (vs multi threads): [https://medium.com/@jychen7/sharing-redis-single-thread-vs-multi-threads-5870bd44d153](https://medium.com/@jychen7/sharing-redis-single-thread-vs-multi-threads-5870bd44d153)
638666
- The difference between AOF and RDB persistence:https://www.sobyte.net/post/2022-04/redis-rdb-and-aof/
667+
- Redis AOF 持久化详解 - 程序员历小冰:http://remcarpediem.net/article/376c55d8/

docs/database/redis/redis-questions-02.md

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -432,7 +432,11 @@ Cache Aside Pattern 中遇到写请求是这样的:更新 DB,然后直接删
432432
1. **缓存失效时间变短(不推荐,治标不治本)** :我们让缓存数据的过期时间变短,这样的话缓存就会从数据库中加载数据。另外,这种解决办法对于先操作缓存后操作数据库的场景不适用。
433433
2. **增加 cache 更新重试机制(常用)**: 如果 cache 服务当前不可用导致缓存删除失败的话,我们就隔一段时间进行重试,重试次数可以自己定。如果多次重试还是失败的话,我们可以把当前更新失败的 key 存入队列中,等缓存服务可用之后,再将缓存中对应的 key 删除即可。
434434
435-
相关文章推荐:[缓存和数据库一致性问题,看这篇就够了 - 水滴与银弹](https://mp.weixin.qq.com/s?__biz=MzIyOTYxNDI5OA==&mid=2247487312&idx=1&sn=fa19566f5729d6598155b5c676eee62d&chksm=e8beb8e5dfc931f3e35655da9da0b61c79f2843101c130cf38996446975014f958a6481aacf1&scene=178&cur_album_id=1699766580538032128#rd)
435+
相关文章推荐:[缓存和数据库一致性问题,看这篇就够了 - 水滴与银弹](https://mp.weixin.qq.com/s?__biz=MzIyOTYxNDI5OA==&mid=2247487312&idx=1&sn=fa19566f5729d6598155b5c676eee62d&chksm=e8beb8e5dfc931f3e35655da9da0b61c79f2843101c130cf38996446975014f958a6481aacf1&scene=178&cur_album_id=1699766580538032128#rd)。
436+
437+
### 哪些情况可能会导致 Redis 阻塞?
438+
439+
单独抽了一篇文章来总结可能会导致 Redis 阻塞的情况:[Redis 常见阻塞原因总结](./redis-memory-fragmentation.md)。
436440
437441
## Redis 集群
438442

0 commit comments

Comments
 (0)