mirror of
https://github.com/Snailclimb/JavaGuide
synced 2025-06-16 18:10:13 +08:00
[docs update]添加redis后台线程+完善AOF 工作基本流程和重写
This commit is contained in:
parent
4058335f2d
commit
053c142747
1
docs/database/redis/images/aof-rewrite.drawio
Normal file
1
docs/database/redis/images/aof-rewrite.drawio
Normal file
@ -0,0 +1 @@
|
|||||||
|
<mxfile host="Electron" modified="2023-03-25T08:01:22.932Z" agent="5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) draw.io/20.3.0 Chrome/104.0.5112.114 Electron/20.1.3 Safari/537.36" etag="02156dhSlPyPTH9l8v31" version="20.3.0" type="device"><diagram id="cLKaGq3Gi8W_Nx-YzgXy" name="Page-1">7VZNj9MwEP01Pm6VxE3bPTb9ACEWkIoEe3TjSWJw4uK42+7+euxk0nxtVywITlxa+3n8bM97Mwqhq/z8RrNDdqc4SBJ4/EzomgSB73sz++eQxxqZL25rINWCY1AL7MQTIOghehQcyl6gUUoaceiDsSoKiE0PY1qrUz8sUbJ/6oGlMAJ2MZNj9IvgJqvRRTBv8bcg0qw52Z/h+3LWBONLyoxxdepAdEPoSitl6lF+XoF0yWvyUu/bXlm9XExDYX5lQ5gcow98b8TN8cf6M/d3357UDbI8MHnEBy8/bslmRm5nZDEnmymJIhKhhqV5bNKi1bHg4Jh9QqNTJgzsDix2qydrBItlJpe4nKjCoLL+zM2FlCslla64KPg8hLnFS6PVd2hWClUAbt6yXEhnoHdgIs1EUdrb3KlCNeTqqKuzM2OsL4KQLu2PzYT7cQHlJFUqlcAOopzEKq8W4rIK3SY1ux12+cMgwhMwRaANnK/m3r8oaksBVA5GW0qv2dCYAKsgmOP81HrKn4c1lnX95GEgQx+nF+5WajtAtV+hfDBSfiQyFHzpSsjOYsnKUsR9XVsTeHYGZ2G+4oob3zt8EuJsfe6ErR9xUh8JfFSDg8TaazUSv+jksQCdBIfeOL8NpkEyIx7613gu53jCJyXsBS/60kVfX0oHstXXx13dIh0QTemAKBgQGaZTMCOiygKXZ/++K+jIFRpO2lb32BxWtPdsb9t9zxNMirRwhrECgi3jyNWNsP10iQu54NxxRBpK8cT2FZ/zwsG9qXplGJFwPegb4UuFiM0fydqW2/XR9SK4WrU33iToqeH/mVmaEJUkJfwV+ab/2/m/aOfDKg1un2nnwTPtZha+upvbafuNUPuk/dKim58=</diagram></mxfile>
|
1
docs/database/redis/images/aof-work-process.drawio
Normal file
1
docs/database/redis/images/aof-work-process.drawio
Normal file
@ -0,0 +1 @@
|
|||||||
|
<mxfile host="Electron" modified="2023-03-25T06:53:36.610Z" agent="5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) draw.io/20.3.0 Chrome/104.0.5112.114 Electron/20.1.3 Safari/537.36" etag="XVuhvrMoR4qwnl8eKIhj" version="20.3.0" type="device"><diagram id="NaMnIawQRtq_GJTarwnL" name="Page-1">7Vptc+MmEP41fIxH6F0fJce69iZpO5N27u5Th0hY1kUWroRj+359AYElLDnxxXHsyWUy48ACC2If9tlFAtZ4vv5UocXslqS4AKaRroF1DUwTQsNl/7hk00g8P2gEWZWnslMruMt/YCk0pHSZp7jWOlJCCpovdGFCyhInVJOhqiIrvduUFPqsC5ThnuAuQUVf+iVP6ayR+qbXyn/DeTZTM0NXPt89Sh6yiixLOR8wrdiN49hvmudI6ZIPWs9QSlYdkTUB1rgihDal+XqMC763atuacfGe1u26K1zSQwag4N+x/8et+c/vf3+d3qR3FN4WV7ZcG92o/cAp2x5ZJRWdkYyUqJi00kg8M+ZaDVZr+9wQsmBCyITfMaUbaWu0pISJZnReyFa24GrzVY4XlW+8MnJU9XrdbbzeyNqUlDRG87zggs+YRhXKy5ot/5aURLbfkWWV8GlnlDIEmY4Vsh+2KfyHd6hHGSFZgdEir0cJmYuGpBZd42mjnRW7+h0zkjPUtCIPW5wwE0bN/vFN22sWKarVyvbZQqEfVRmmT/Qzt+BhhxKTOWZ7xMZVuEA0f9TXgeTpyLb9WoSwggTJTwBGnvZHVCzVsywWmJ2BIRzdoHvmLTTboyLPSlZO2PbgigkecUVzdhxD2TDP07SBGa7zH+he6OPWX5CcWY8rdyLgXCt7NyCD9kXgo+APHG0dw5gUpBLboVyDAgx/arwGAw5OPnHrN7pQ2n+G+3iQ2q+MkdkoOhggUtdffMM7Xch0WjNg7iJoO+XLQQV7oAITBwQQRNdgYoMoAqENJj6IYiFxgB+C0OhBrnVMHGmrWU7x3QIJa68YeelA7ILH5fW8KDr2ShH2p8n2yKuWkpT4IpC2F0Y9z7MXGVBRsGRuy5b1VcuD0JCyWYcDbfdEzsX7YKOLYSPzQDayzslGfs9xrCp26j/I6Gxk5D1HRtAIoO52Lp2dzB7Iwj9jMPFANAaBJejIBZGgLH8MovCkvGQYvuXH75qXLPPSeCk4Ly+1VPRNY6JfkpesA3kJGsMge6mfkeD07B1wupauolmYHHWCULkfK0/rTZl8UN7ZKC94Nv+CvqeBBh5HeUqzzqOBPv50fGgNZGuMDC2ep/FCBIJYsqLvgInLU7XIPxdhTv0EJ+87kXN2fdIQYVpvSZgqs9yNmVwQMFB4MqmPXDCJOSh8gY4QgnDMCwFDkK+a+tz7mujAMHWw977RAT0WQHTxYfsD+PCcAXwYp8JHP6buE1iZhvx+n5NVgeo6T3TL6gHWLhc9H6Cd37gvj5P6xu6Y0hk46Up2JOW4ju5pHHMHIU341ot++oqCHUXGjqI9YRRDBNp0usmg5PAFO8aT6/LMI/sbWn9WaFb8ujFgn4Er/HHxcd4oEO55LdMNA6EesR15L78ZHHDCW3pnEF5nSoO3bU+kwZcFy9O+PJRvck+fFx9H/P33hwVB6YffOp/fcp70W8YImratua2rI/3WG3gqux9eTgKeePji9SELPsL4pHnFL/D60LGtkacnFo7v9BML0xy9aWrhDaQWbkHltmomd/9bEtVwVQtrsk02oLtYiz1S7ayUif/8xsIRWaotLjM8IWGJqifT1SD8jB7RJ/5Nk5r1vjpyTo+/9A6gSJFZZhxsp/rOphKfT42SUs0GGnBsR++CnNmb6kgeRmgHzlJ0uO8dOjp6HLDH03WOh3a2WL1ztiLxJ3RS5oBIKZXWD5gmM1VRX1tdxpX4K5w3c+eWx/YGsnh/IPeDP3/UWLX9MK3xyu3Xf9bkfw==</diagram></mxfile>
|
@ -7,13 +7,15 @@ tag:
|
|||||||
|
|
||||||
> 本文整理完善自:https://mp.weixin.qq.com/s/0Nqfq_eQrUb12QH6eBbHXA ,作者:阿Q说代码
|
> 本文整理完善自:https://mp.weixin.qq.com/s/0Nqfq_eQrUb12QH6eBbHXA ,作者:阿Q说代码
|
||||||
|
|
||||||
## O(n) 命令阻塞
|
这篇文章会详细总结一下可能导致 Redis 阻塞的情况,这些情况也是影响 Redis 性能的关键因素,使用 Redis 的时候应该格外注意!
|
||||||
|
|
||||||
|
## O(n) 命令
|
||||||
|
|
||||||
使用` O(n)` 命令可能会导致阻塞,例如`keys *` 、`hgetall`、`lrange`、`smembers`、`zrange`、`sinter` 、`sunion` 命令。这些命令时间复杂度是 O(n),有时候也会全表扫描,随着 n 的增大耗时也会越大从而导致客户端阻塞。
|
使用` O(n)` 命令可能会导致阻塞,例如`keys *` 、`hgetall`、`lrange`、`smembers`、`zrange`、`sinter` 、`sunion` 命令。这些命令时间复杂度是 O(n),有时候也会全表扫描,随着 n 的增大耗时也会越大从而导致客户端阻塞。
|
||||||
|
|
||||||
不过, 这些命令并不是一定不能使用,但是需要明确 N 的值。有遍历的需求可以使用 `hscan`、`sscan`、`zscan` 代替。
|
不过, 这些命令并不是一定不能使用,但是需要明确 N 的值。有遍历的需求可以使用 `hscan`、`sscan`、`zscan` 代替。
|
||||||
|
|
||||||
## SAVE 创建 RDB 快照阻塞
|
## SAVE 创建 RDB 快照
|
||||||
|
|
||||||
Redis 提供了两个命令来生成 RDB 快照文件:
|
Redis 提供了两个命令来生成 RDB 快照文件:
|
||||||
|
|
||||||
@ -22,7 +24,9 @@ Redis 提供了两个命令来生成 RDB 快照文件:
|
|||||||
|
|
||||||
默认情况下,Redis 默认配置会使用 `bgsave` 命令。如果手动使用 `save` 命令生成 RDB 快照文件的话,就会阻塞主线程。
|
默认情况下,Redis 默认配置会使用 `bgsave` 命令。如果手动使用 `save` 命令生成 RDB 快照文件的话,就会阻塞主线程。
|
||||||
|
|
||||||
## AOF 日志记录阻塞
|
## AOF
|
||||||
|
|
||||||
|
### AOF 日志记录阻塞
|
||||||
|
|
||||||
Redis AOF 持久化机制是在执行完命令之后再记录日志,这和关系型数据库(如 MySQL)通常都是执行命令之前记录日志(方便故障恢复)不同。
|
Redis AOF 持久化机制是在执行完命令之后再记录日志,这和关系型数据库(如 MySQL)通常都是执行命令之前记录日志(方便故障恢复)不同。
|
||||||
|
|
||||||
@ -38,21 +42,21 @@ Redis AOF 持久化机制是在执行完命令之后再记录日志,这和关
|
|||||||
- 如果刚执行完命令 Redis 就宕机会导致对应的修改丢失;
|
- 如果刚执行完命令 Redis 就宕机会导致对应的修改丢失;
|
||||||
- **可能会阻塞后续其他命令的执行(AOF 记录日志是在 Redis 主线程中进行的)**。
|
- **可能会阻塞后续其他命令的执行(AOF 记录日志是在 Redis 主线程中进行的)**。
|
||||||
|
|
||||||
## AOF 刷盘阻塞
|
### AOF 刷盘阻塞
|
||||||
|
|
||||||
开启 AOF 持久化后每执行一条会更改 Redis 中的数据的命令,Redis 就会将该命令写入到 AOF 缓冲区 `server.aof_buf` 中,然后再根据 `appendfsync` 配置来决定何时将其同步到硬盘中的 AOF 文件。
|
开启 AOF 持久化后每执行一条会更改 Redis 中的数据的命令,Redis 就会将该命令写入到 AOF 缓冲区 `server.aof_buf` 中,然后再根据 `appendfsync` 配置来决定何时将其同步到硬盘中的 AOF 文件。
|
||||||
|
|
||||||
在 Redis 的配置文件中存在三种不同的 AOF 持久化方式( `fsync`策略),它们分别是:
|
在 Redis 的配置文件中存在三种不同的 AOF 持久化方式( `fsync`策略),它们分别是:
|
||||||
|
|
||||||
```bash
|
1. `appendfsync always`:主线程调用 `write` 执行写操作后,后台线程( `aof_fsync` 线程)立即会调用 `fsync` 函数同步 AOF 文件(刷盘),`fsync` 完成后线程返回,这样会严重降低 Redis 的性能(`write` + `fsync`)。
|
||||||
appendfsync always #每次有数据修改发生时都会调用fsync函数同步AOF文件,fsync完成后线程返回,这样会严重降低Redis的速度
|
2. `appendfsync everysec` :主线程调用 `write` 执行写操作后立即返回,由后台线程( `aof_fsync` 线程)每秒钟调用 `fsync` 函数(系统调用)同步一次 AOF 文件(`write`+`fsync`,`fsync`间隔为 1 秒)
|
||||||
appendfsync everysec #每秒钟调用fsync函数同步一次AOF文件
|
3. `appendfsync no` :主线程调用 `write` 执行写操作后立即返回,让操作系统决定何时进行同步,Linux 下一般为 30 秒一次(`write`但不`fsync`,`fsync` 的时机由操作系统决定)。
|
||||||
appendfsync no #让操作系统决定何时进行同步,一般为30秒一次
|
|
||||||
```
|
|
||||||
|
|
||||||
当调用 fsync 函数同步 AOF 文件时,需要等待,直到写入完成。当磁盘压力太大的时候,主线程可能会被阻塞。
|
当后台线程( `aof_fsync` 线程)调用 `fsync` 函数同步 AOF 文件时,需要等待,直到写入完成。当磁盘压力太大的时候,会导致 `fsync` 操作发生阻塞,主线程调用 `write` 函数时也会被阻塞。`fsync` 完成后,主线程执行 `write` 才能成功返回。
|
||||||
|
|
||||||
## AOF 重写阻塞
|
关于 AOF 工作流程的详细介绍可以查看:[Redis持久化机制详解](./redis-persistence.md),有助于理解 AOF 刷盘阻塞。
|
||||||
|
|
||||||
|
### AOF 重写阻塞
|
||||||
|
|
||||||
1. fork 出一条子线程来将文件重写,在执行 `BGREWRITEAOF` 命令时,Redis 服务器会维护一个 AOF 重写缓冲区,该缓冲区会在子线程创建新 AOF 文件期间,记录服务器执行的所有写命令。
|
1. fork 出一条子线程来将文件重写,在执行 `BGREWRITEAOF` 命令时,Redis 服务器会维护一个 AOF 重写缓冲区,该缓冲区会在子线程创建新 AOF 文件期间,记录服务器执行的所有写命令。
|
||||||
2. 当子线程完成创建新 AOF 文件的工作之后,服务器会将重写缓冲区中的所有内容追加到新 AOF 文件的末尾,使得新的 AOF 文件保存的数据库状态与现有的数据库状态一致。
|
2. 当子线程完成创建新 AOF 文件的工作之后,服务器会将重写缓冲区中的所有内容追加到新 AOF 文件的末尾,使得新的 AOF 文件保存的数据库状态与现有的数据库状态一致。
|
||||||
@ -62,7 +66,7 @@ appendfsync no #让操作系统决定何时进行同步,一般为30秒
|
|||||||
|
|
||||||
相关阅读:[Redis AOF重写阻塞问题分析](https://cloud.tencent.com/developer/article/1633077)。
|
相关阅读:[Redis AOF重写阻塞问题分析](https://cloud.tencent.com/developer/article/1633077)。
|
||||||
|
|
||||||
## 大 Key 问题
|
## 大 Key
|
||||||
|
|
||||||
如果一个 key 对应的 value 所占用的内存比较大,那这个 key 就可以看作是 bigkey。具体多大才算大呢?有一个不是特别精确的参考标准:string 类型的 value 超过 10 kb,复合类型的 value 包含的元素超过 5000 个(对于复合类型的 value 来说,不一定包含的元素越多,占用的内存就越多)。
|
如果一个 key 对应的 value 所占用的内存比较大,那这个 key 就可以看作是 bigkey。具体多大才算大呢?有一个不是特别精确的参考标准:string 类型的 value 超过 10 kb,复合类型的 value 包含的元素超过 5000 个(对于复合类型的 value 来说,不一定包含的元素越多,占用的内存就越多)。
|
||||||
|
|
||||||
@ -105,3 +109,52 @@ Redis 集群可以进行节点的动态扩容缩容,这一过程目前还处
|
|||||||
|
|
||||||
执行迁移时,两端的 Redis 均会进入时长不等的阻塞状态,对于小 Key,该时间可以忽略不计,但如果一旦 Key 的内存使用过大,严重的时候会触发集群内的故障转移,造成不必要的切换。
|
执行迁移时,两端的 Redis 均会进入时长不等的阻塞状态,对于小 Key,该时间可以忽略不计,但如果一旦 Key 的内存使用过大,严重的时候会触发集群内的故障转移,造成不必要的切换。
|
||||||
|
|
||||||
|
## Swap(内存交换)
|
||||||
|
|
||||||
|
什么是 Swap?Swap 直译过来是交换的意思,Linux中的Swap常被称为内存交换或者交换分区。类似于 Windows 中的虚拟内存,就是当内存不足的时候,把一部分硬盘空间虚拟成内存使用,从而解决内存容量不足的情况。因此,Swap 分区的作用就是牺牲硬盘,增加内存,解决 VPS 内存不够用或者爆满的问题。
|
||||||
|
|
||||||
|
Swap 对于Redis来说是非常致命的,Redis保证高性能的一个重要前提是所有的数据在内存中。如果操作系统把Redis使用的部分内存换出硬盘,由于内存与硬盘读写的速度并几个数量级,会导致发生交换后的Redis性能急剧下降。
|
||||||
|
|
||||||
|
识别 Redis 发生 Swap 的检查方法如下:
|
||||||
|
|
||||||
|
1、查询Redis进程号
|
||||||
|
|
||||||
|
```bash
|
||||||
|
reids-cli -p 6383 info server | grep process_id
|
||||||
|
process_id: 4476
|
||||||
|
```
|
||||||
|
|
||||||
|
2、根据进程号查询内存交换信息
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cat /proc/4476/smaps | grep Swap
|
||||||
|
Swap: 0kB
|
||||||
|
Swap: 0kB
|
||||||
|
Swap: 4kB
|
||||||
|
Swap: 0kB
|
||||||
|
Swap: 0kB
|
||||||
|
.....
|
||||||
|
```
|
||||||
|
|
||||||
|
如果交换量都是0KB或者个别的是4KB,则正常。
|
||||||
|
|
||||||
|
预防内存交换的方法:
|
||||||
|
|
||||||
|
- 保证机器充足的可用内存
|
||||||
|
- 确保所有Redis实例设置最大可用内存(maxmemory),防止极端情况Redis内存不可控的增长
|
||||||
|
- 降低系统使用swap优先级,如`echo 10 > /proc/sys/vm/swappiness`
|
||||||
|
|
||||||
|
## CPU 竞争
|
||||||
|
|
||||||
|
Redis是典型的CPU密集型应用,不建议和其他多核CPU密集型服务部署在一起。当其他进程过度消耗CPU时,将严重影响Redis的吞吐量。
|
||||||
|
|
||||||
|
可以通过`reids-cli --stat`获取当前Redis使用情况。通过`top`命令获取进程对CPU的利用率等信息 通过`info commandstats`统计信息分析出命令不合理开销时间,查看是否是因为高算法复杂度或者过度的内存优化问题。
|
||||||
|
|
||||||
|
## 网络问题
|
||||||
|
|
||||||
|
连接拒绝、网络延迟,网卡软中断等网络问题也可能会导致 Redis 阻塞。
|
||||||
|
|
||||||
|
## 参考
|
||||||
|
|
||||||
|
- Redis阻塞的6大类场景分析与总结:https://mp.weixin.qq.com/s/eaZCEtTjTuEmXfUubVHjew
|
||||||
|
- Redis开发与运维笔记-Redis的噩梦-阻塞:https://mp.weixin.qq.com/s/TDbpz9oLH6ifVv6ewqgSgA
|
||||||
|
@ -28,7 +28,7 @@ Redis 不同于 Memcached 的很重要一点就是,Redis 支持持久化,而
|
|||||||
|
|
||||||
### 什么是 RDB 持久化?
|
### 什么是 RDB 持久化?
|
||||||
|
|
||||||
Redis 可以通过创建快照来获得存储在内存里面的数据在某个时间点上的副本。Redis 创建快照之后,可以对快照进行备份,可以将快照复制到其他服务器从而创建具有相同数据的服务器副本(Redis 主从结构,主要用来提高 Redis 性能),还可以将快照留在原地以便重启服务器的时候使用。
|
Redis 可以通过创建快照来获得存储在内存里面的数据在 **某个时间点** 上的副本。Redis 创建快照之后,可以对快照进行备份,可以将快照复制到其他服务器从而创建具有相同数据的服务器副本(Redis 主从结构,主要用来提高 Redis 性能),还可以将快照留在原地以便重启服务器的时候使用。
|
||||||
|
|
||||||
快照持久化是 Redis 默认采用的持久化方式,在 `redis.conf` 配置文件中默认有此下配置:
|
快照持久化是 Redis 默认采用的持久化方式,在 `redis.conf` 配置文件中默认有此下配置:
|
||||||
|
|
||||||
@ -59,19 +59,44 @@ Redis 提供了两个命令来生成 RDB 快照文件:
|
|||||||
appendonly yes
|
appendonly yes
|
||||||
```
|
```
|
||||||
|
|
||||||
开启 AOF 持久化后每执行一条会更改 Redis 中的数据的命令,Redis 就会将该命令写入到 AOF 缓冲区 `server.aof_buf` 中,然后再根据持久化方式( `fsync`策略)的配置来决定何时将其同步到硬盘中的 AOF 文件。
|
开启 AOF 持久化后每执行一条会更改 Redis 中的数据的命令,Redis 就会将该命令写入到 AOF 缓冲区 `server.aof_buf` 中,然后再写入到 AOF 文件中(此时还在系统内核缓存区为同步到磁盘),最后再根据持久化方式( `fsync`策略)的配置来决定何时将系统内核缓存区的数据同步到硬盘中的。
|
||||||
|
|
||||||
AOF 文件的保存位置和 RDB 文件的位置相同,都是通过 dir 参数设置的,默认的文件名是 `appendonly.aof`。
|
只有同步到磁盘中才算持久化保存了,否则依然存在数据丢失的风险,比如说:系统内核缓存区的数据还未同步,磁盘机器就宕机了,那这部分数据就算丢失了。
|
||||||
|
|
||||||
|
AOF 文件的保存位置和 RDB 文件的位置相同,都是通过 `dir` 参数设置的,默认的文件名是 `appendonly.aof`。
|
||||||
|
|
||||||
|
### AOF 工作基本流程是怎样的?
|
||||||
|
|
||||||
|
AOF 持久化功能的实现可以简单分为 5 步:
|
||||||
|
|
||||||
|
1. **命令追加(append)** :所有的写命令会追加到 AOF 缓冲区中。
|
||||||
|
2. **文件写入(write)** :将 AOF 缓冲区的数据写入到 AOF 文件中。这一步需要调用`write`函数(系统调用),`write`将数据写入到了系统内核缓冲区之后直接返回了(延迟写)。注意!!!此时并没有同步到磁盘。
|
||||||
|
3. **文件同步(fsync)** :AOF 缓冲区根据对应的持久化方式( `fsync` 策略)向硬盘做同步操作。这一步需要调用 `fsync` 函数(系统调用), `fsync` 针对单个文件操作,对其进行强制硬盘同步,`fsync` 将阻塞直到写入磁盘完成后返回,保证了数据持久化。
|
||||||
|
4. **文件重写(rewrite)** :随着 AOF 文件越来越大,需要定期对 AOF 文件进行重写,达到压缩的目的。
|
||||||
|
5. **重启加载(load)** :当 Redis 重启时,可以加载 AOF 文件进行数据恢复。
|
||||||
|
|
||||||
|
> Linux 系统直接提供了一些函数用于对文件和设备进行访问和控制,这些函数被称为 **系统调用(syscall)**。
|
||||||
|
|
||||||
|
这里对上面提到的一些 Linux 系统调用再做一遍解释:
|
||||||
|
|
||||||
|
- `write` :写入系统内核缓冲区之后直接返回(仅仅是写到缓冲区),不会立即同步到硬盘。虽然提高了效率,但也带来了数据丢失的风险。同步硬盘操作通常依赖于系统调度机制,Linux 内核通常为 30s 同步一次,具体值取决于写出的数据量和 I/O 缓冲区的状态。
|
||||||
|
- `fsync` : `fsync`用于强制刷新系统内核缓冲区(同步到到磁盘),确保写磁盘操作结束才会返回。
|
||||||
|
|
||||||
|
AOF 工作流程图如下:
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
### 持久化方式有哪些?
|
||||||
|
|
||||||
在 Redis 的配置文件中存在三种不同的 AOF 持久化方式( `fsync`策略),它们分别是:
|
在 Redis 的配置文件中存在三种不同的 AOF 持久化方式( `fsync`策略),它们分别是:
|
||||||
|
|
||||||
```bash
|
1. `appendfsync always`:主线程调用 `write` 执行写操作后,后台线程( `aof_fsync` 线程)立即会调用 `fsync` 函数同步 AOF 文件(刷盘),`fsync` 完成后线程返回,这样会严重降低 Redis 的性能(`write` + `fsync`)。
|
||||||
appendfsync always #每次有数据修改发生时都会调用fsync函数同步AOF文件,fsync完成后线程返回,这样会严重降低Redis的速度
|
2. `appendfsync everysec` :主线程调用 `write` 执行写操作后立即返回,由后台线程( `aof_fsync` 线程)每秒钟调用 `fsync` 函数(系统调用)同步一次 AOF 文件(`write`+`fsync`,`fsync`间隔为 1 秒)
|
||||||
appendfsync everysec #每秒钟调用fsync函数同步一次AOF文件
|
3. `appendfsync no` :主线程调用 `write` 执行写操作后立即返回,让操作系统决定何时进行同步,Linux 下一般为 30 秒一次(`write`但不`fsync`,`fsync` 的时机由操作系统决定)。
|
||||||
appendfsync no #让操作系统决定何时进行同步,一般为30秒一次
|
|
||||||
```
|
|
||||||
|
|
||||||
为了兼顾数据和写入性能,可以考虑 `appendfsync everysec` 选项 ,让 Redis 每秒同步一次 AOF 文件,Redis 性能几乎没受到任何影响。而且这样即使出现系统崩溃,用户最多只会丢失一秒之内产生的数据。当硬盘忙于执行写入操作的时候,Redis 还会优雅的放慢自己的速度以便适应硬盘的最大写入速度。
|
可以看出:**这 3 种持久化方式的主要区别在于 `fsync` 同步 AOF 文件的时机(刷盘)**。
|
||||||
|
|
||||||
|
为了兼顾数据和写入性能,可以考虑 `appendfsync everysec` 选项 ,让 Redis 每秒同步一次 AOF 文件,Redis 性能收到的影响较小。而且这样即使出现系统崩溃,用户最多只会丢失一秒之内产生的数据。当硬盘忙于执行写入操作的时候,Redis 还会优雅的放慢自己的速度以便适应硬盘的最大写入速度。
|
||||||
|
|
||||||
从 Redis 7.0.0 开始,Redis 使用了 **Multi Part AOF** 机制。顾名思义,Multi Part AOF 就是将原来的单个 AOF 文件拆分成多个 AOF 文件。在 Multi Part AOF 中,AOF 文件被分为三种类型,分别为:
|
从 Redis 7.0.0 开始,Redis 使用了 **Multi Part AOF** 机制。顾名思义,Multi Part AOF 就是将原来的单个 AOF 文件拆分成多个 AOF 文件。在 Multi Part AOF 中,AOF 文件被分为三种类型,分别为:
|
||||||
|
|
||||||
@ -83,17 +108,6 @@ Multi Part AOF 不是重点,了解即可,详细介绍可以看看阿里开
|
|||||||
|
|
||||||
**相关 issue** :[Redis 的 AOF 方式 #783](https://github.com/Snailclimb/JavaGuide/issues/783)。
|
**相关 issue** :[Redis 的 AOF 方式 #783](https://github.com/Snailclimb/JavaGuide/issues/783)。
|
||||||
|
|
||||||
### AOF 工作基本流程是怎样的?
|
|
||||||
|
|
||||||
AOF 持久化功能的实现可以简单分为 4 步:
|
|
||||||
|
|
||||||
1. 命令写入(append):所有的写命令会追加到 AOF 缓冲区中。
|
|
||||||
2. 文件同步(sync):AOF 缓冲区根据对应的 fsync 策略向硬盘做同步操作。
|
|
||||||
3. 文件重写(rewrite):随着 AOF 文件越来越大,需要定期对 AOF 文件进行重写,达到压缩的目的。
|
|
||||||
4. 重启加载(load):当 Redis 重启时,可以加载 AOF 文件进行数据恢复。
|
|
||||||
|
|
||||||

|
|
||||||
|
|
||||||
### AOF 为什么是在执行完命令之后记录日志?
|
### AOF 为什么是在执行完命令之后记录日志?
|
||||||
|
|
||||||
关系型数据库(如 MySQL)通常都是执行命令之前记录日志(方便故障恢复),而 Redis AOF 持久化机制是在执行完命令之后再记录日志。
|
关系型数据库(如 MySQL)通常都是执行命令之前记录日志(方便故障恢复),而 Redis AOF 持久化机制是在执行完命令之后再记录日志。
|
||||||
@ -114,17 +128,26 @@ AOF 持久化功能的实现可以简单分为 4 步:
|
|||||||
|
|
||||||
当 AOF 变得太大时,Redis 能够在后台自动重写 AOF 产生一个新的 AOF 文件,这个新的 AOF 文件和原有的 AOF 文件所保存的数据库状态一样,但体积更小。
|
当 AOF 变得太大时,Redis 能够在后台自动重写 AOF 产生一个新的 AOF 文件,这个新的 AOF 文件和原有的 AOF 文件所保存的数据库状态一样,但体积更小。
|
||||||
|
|
||||||
AOF 重写是一个有歧义的名字,该功能是通过读取数据库中的键值对来实现的,程序无须对现有 AOF 文件进行任何读入、分析或者写入操作。
|

|
||||||
|
|
||||||
在执行 `BGREWRITEAOF` 命令时,Redis 服务器会维护一个 AOF 重写缓冲区,该缓冲区会在子进程创建新 AOF 文件期间,记录服务器执行的所有写命令。当子进程完成创建新 AOF 文件的工作之后,服务器会将重写缓冲区中的所有内容追加到新 AOF 文件的末尾,使得新的 AOF 文件保存的数据库状态与现有的数据库状态一致。最后,服务器用新的 AOF 文件替换旧的 AOF 文件,以此来完成 AOF 文件重写操作。
|
> AOF 重写(rewrite) 是一个有歧义的名字,该功能是通过读取数据库中的键值对来实现的,程序无须对现有 AOF 文件进行任何读入、分析或者写入操作。
|
||||||
|
|
||||||
|
由于 AOF 重写会进行大量的写入操作,为了避免对 Redis 正常处理命令请求造成影响,Redis 将 AOF 重写程序放到子进程里执行。
|
||||||
|
|
||||||
|
AOF 文件重写期间,Redis 还会维护一个 **AOF 重写缓冲区**,该缓冲区会在子进程创建新 AOF 文件期间,记录服务器执行的所有写命令。当子进程完成创建新 AOF 文件的工作之后,服务器会将重写缓冲区中的所有内容追加到新 AOF 文件的末尾,使得新的 AOF 文件保存的数据库状态与现有的数据库状态一致。最后,服务器用新的 AOF 文件替换旧的 AOF 文件,以此来完成 AOF 文件重写操作。
|
||||||
|
|
||||||
|
开启 AOF 重写功能,可以调用 `BGREWRITEAOF` 命令手动执行,也可以设置下面两个配置项,让程序自动决定触发时机:
|
||||||
|
|
||||||
|
- `auto-aof-rewrite-min-size` :如果 AOF 文件大小小于该值,则不会触发 AOF 重写。默认值为 64 MB;
|
||||||
|
- `auto-aof-rewrite-percentage`:执行 AOF 重写时,当前 AOF 大小(aof_current_size)和上一次重写时 AOF 大小(aof_base_size)的比值。如果当前 AOF 文件大小增加了这个百分比值,将触发 AOF 重写。将此值设置为 0 将禁用自动 AOF 重写。默认值为 100。
|
||||||
|
|
||||||
Redis 7.0 版本之前,如果在重写期间有写入命令,AOF 可能会使用大量内存,重写期间到达的所有写入命令都会写入磁盘两次。
|
Redis 7.0 版本之前,如果在重写期间有写入命令,AOF 可能会使用大量内存,重写期间到达的所有写入命令都会写入磁盘两次。
|
||||||
|
|
||||||
Redis 7.0 版本之后,AOF 重写机制得到了优化改进。下面这段内容摘自阿里开发者的[从Redis7.0发布看Redis的过去与未来](https://mp.weixin.qq.com/s/RnoPPL7jiFSKkx3G4p57Pg) 这篇文章。
|
Redis 7.0 版本之后,AOF 重写机制得到了优化改进。下面这段内容摘自阿里开发者的[从 Redis7.0 发布看 Redis 的过去与未来](https://mp.weixin.qq.com/s/RnoPPL7jiFSKkx3G4p57Pg) 这篇文章。
|
||||||
|
|
||||||
> AOF 重写期间的增量数据如何处理一直是个问题,在过去写期间的增量数据需要在内存中保留,写结束后再把这部分增量数据写入新的AOF文件中以保证数据完整性。可以看出来AOF 写会额外消耗内存和磁盘IO,这也是Redis AOF 写的痛点,虽然之前也进行过多次改进但是资源消耗的本质问题一直没有解决。
|
> AOF 重写期间的增量数据如何处理一直是个问题,在过去写期间的增量数据需要在内存中保留,写结束后再把这部分增量数据写入新的 AOF 文件中以保证数据完整性。可以看出来 AOF 写会额外消耗内存和磁盘 IO,这也是 Redis AOF 写的痛点,虽然之前也进行过多次改进但是资源消耗的本质问题一直没有解决。
|
||||||
>
|
>
|
||||||
> 阿里云的Redis企业版在最初也遇到了这个问题,在内部经过多次迭代开发,实现了Multi-part AOF机制来解决,同时也贡献给了社区并随此次7.0发布。具体方法是采用base(全量数据)+inc(增量数据)独立文件存储的方式,彻底解决内存和IO资源的浪费,同时也支持对历史AOF文件的保存管理,结合AOF文件中的时间信息还可以实现PITR按时间点恢复(阿里云企业版Tair已支持),这进一步增强了Redis的数据可靠性,满足用户数据回档等需求。
|
> 阿里云的 Redis 企业版在最初也遇到了这个问题,在内部经过多次迭代开发,实现了 Multi-part AOF 机制来解决,同时也贡献给了社区并随此次 7.0 发布。具体方法是采用 base(全量数据)+inc(增量数据)独立文件存储的方式,彻底解决内存和 IO 资源的浪费,同时也支持对历史 AOF 文件的保存管理,结合 AOF 文件中的时间信息还可以实现 PITR 按时间点恢复(阿里云企业版 Tair 已支持),这进一步增强了 Redis 的数据可靠性,满足用户数据回档等需求。
|
||||||
|
|
||||||
**相关 issue** :[Redis AOF 重写描述不准确 #1439](https://github.com/Snailclimb/JavaGuide/issues/1439)
|
**相关 issue** :[Redis AOF 重写描述不准确 #1439](https://github.com/Snailclimb/JavaGuide/issues/1439)
|
||||||
|
|
||||||
@ -161,6 +184,8 @@ Redis 7.0 版本之后,AOF 重写机制得到了优化改进。下面这段内
|
|||||||
|
|
||||||
## 参考
|
## 参考
|
||||||
|
|
||||||
|
- 《Redis 设计与实现》
|
||||||
- Redis persistence - Redis 官方文档:https://redis.io/docs/management/persistence/
|
- Redis persistence - Redis 官方文档:https://redis.io/docs/management/persistence/
|
||||||
- The difference between AOF and RDB persistence:https://www.sobyte.net/post/2022-04/redis-rdb-and-aof/
|
- The difference between AOF and RDB persistence:https://www.sobyte.net/post/2022-04/redis-rdb-and-aof/
|
||||||
- Redis AOF 持久化详解 - 程序员历小冰:http://remcarpediem.net/article/376c55d8/
|
- Redis AOF 持久化详解 - 程序员历小冰:http://remcarpediem.net/article/376c55d8/
|
||||||
|
- Redis RDB 与 AOF 持久化 · Analyze:https://wingsxdu.com/posts/database/redis/rdb-and-aof/
|
@ -444,7 +444,7 @@ Redis 通过 **IO 多路复用程序** 来监听来自客户端的大量连接
|
|||||||
- Redis 的性能瓶颈不在 CPU ,主要在内存和网络;
|
- Redis 的性能瓶颈不在 CPU ,主要在内存和网络;
|
||||||
- 多线程就会存在死锁、线程上下文切换等问题,甚至会影响性能。
|
- 多线程就会存在死锁、线程上下文切换等问题,甚至会影响性能。
|
||||||
|
|
||||||
相关阅读:[为什么 Redis 选择单线程模型](https://draveness.me/whys-the-design-redis-single-thread/) 。
|
相关阅读:[为什么 Redis 选择单线程模型?](https://draveness.me/whys-the-design-redis-single-thread/) 。
|
||||||
|
|
||||||
### Redis6.0 之后为何引入了多线程?
|
### Redis6.0 之后为何引入了多线程?
|
||||||
|
|
||||||
@ -476,6 +476,39 @@ io-threads-do-reads yes
|
|||||||
- [Redis 6.0 新特性-多线程连环 13 问!](https://mp.weixin.qq.com/s/FZu3acwK6zrCBZQ_3HoUgw)
|
- [Redis 6.0 新特性-多线程连环 13 问!](https://mp.weixin.qq.com/s/FZu3acwK6zrCBZQ_3HoUgw)
|
||||||
- [Redis 多线程网络模型全面揭秘](https://segmentfault.com/a/1190000039223696)(推荐)
|
- [Redis 多线程网络模型全面揭秘](https://segmentfault.com/a/1190000039223696)(推荐)
|
||||||
|
|
||||||
|
### Redis 后台线程了解吗?
|
||||||
|
|
||||||
|
我们虽然经常说 Redis 是单线程模型(主要逻辑是单线程完成的),但实际还有一些后台线程用于执行一些比较耗时的操作:
|
||||||
|
|
||||||
|
- 通过 `bio_close_file` 后台线程来释放 AOF / RDB 等过程中产生的临时文件资源。
|
||||||
|
- 通过 `bio_aof_fsync` 后台线程调用 `fsync` 函数将系统内核缓冲区还未同步到到磁盘的数据强制刷到磁盘( AOF 文件)。
|
||||||
|
- 通过 `bio_lazy_free`后台线程释放大对象(已删除)占用的内存空间.
|
||||||
|
|
||||||
|
在`bio.h` 文件中有定义(Redis 6.0 版本,源码地址:https://github.com/redis/redis/blob/6.0/src/bio.h):
|
||||||
|
|
||||||
|
```java
|
||||||
|
#ifndef __BIO_H
|
||||||
|
#define __BIO_H
|
||||||
|
|
||||||
|
/* Exported API */
|
||||||
|
void bioInit(void);
|
||||||
|
void bioCreateBackgroundJob(int type, void *arg1, void *arg2, void *arg3);
|
||||||
|
unsigned long long bioPendingJobsOfType(int type);
|
||||||
|
unsigned long long bioWaitStepOfType(int type);
|
||||||
|
time_t bioOlderJobOfType(int type);
|
||||||
|
void bioKillThreads(void);
|
||||||
|
|
||||||
|
/* Background job opcodes */
|
||||||
|
#define BIO_CLOSE_FILE 0 /* Deferred close(2) syscall. */
|
||||||
|
#define BIO_AOF_FSYNC 1 /* Deferred AOF fsync. */
|
||||||
|
#define BIO_LAZY_FREE 2 /* Deferred objects freeing. */
|
||||||
|
#define BIO_NUM_OPS 3
|
||||||
|
|
||||||
|
#endif
|
||||||
|
```
|
||||||
|
|
||||||
|
关于 Redis 后台线程的详细介绍可以查看 [Redis 6.0 后台线程有哪些?](https://juejin.cn/post/7102780434739626014) 这篇就文章。
|
||||||
|
|
||||||
## Redis 内存管理
|
## Redis 内存管理
|
||||||
|
|
||||||
### Redis 给缓存数据设置过期时间有啥用?
|
### Redis 给缓存数据设置过期时间有啥用?
|
||||||
|
@ -141,10 +141,6 @@ Demo 级别的,一般就是你本地启动了玩玩儿的?,没人生产用
|
|||||||
|
|
||||||
这样的好处在于,你任何一个机器宕机了,没事儿,其它机器(节点)还包含了这个 queue 的完整数据,别的 consumer 都可以到其它节点上去消费数据。坏处在于,第一,这个性能开销也太大了吧,消息需要同步到所有机器上,导致网络带宽压力和消耗很重!RabbitMQ 一个 queue 的数据都是放在一个节点里的,镜像集群下,也是每个节点都放这个 queue 的完整数据。
|
这样的好处在于,你任何一个机器宕机了,没事儿,其它机器(节点)还包含了这个 queue 的完整数据,别的 consumer 都可以到其它节点上去消费数据。坏处在于,第一,这个性能开销也太大了吧,消息需要同步到所有机器上,导致网络带宽压力和消耗很重!RabbitMQ 一个 queue 的数据都是放在一个节点里的,镜像集群下,也是每个节点都放这个 queue 的完整数据。
|
||||||
|
|
||||||
## 如何解决消息挤压问题?
|
|
||||||
|
|
||||||
**临时紧急扩容**。先修复 consumer 的问题,确保其恢复消费速度,然后将现有 consumer 都停掉。新建一个 topic,partition 是原来的 10 倍,临时建立好原先 10 倍的 queue 数量。然后写一个临时的分发数据的 consumer 程序,这个程序部署上去消费积压的数据,消费之后不做耗时的处理,直接均匀轮询写入临时建立好的 10 倍数量的 queue。接着临时征用 10 倍的机器来部署 consumer,每一批 consumer 消费一个临时 queue 的数据。这种做法相当于是临时将 queue 资源和 consumer 资源扩大 10 倍,以正常的 10 倍速度来消费数据。等快速消费完积压数据之后,得恢复原先部署的架构,重新用原先的 consumer 机器来消费消息。
|
|
||||||
|
|
||||||
## 如何解决消息队列的延时以及过期失效问题?
|
## 如何解决消息队列的延时以及过期失效问题?
|
||||||
|
|
||||||
RabbtiMQ 是可以设置过期时间的,也就是 TTL。如果消息在 queue 中积压超过一定的时间就会被 RabbitMQ 给清理掉,这个数据就没了。那这就是第二个坑了。这就不是说数据会大量积压在 mq 里,而是大量的数据会直接搞丢。我们可以采取一个方案,就是批量重导,这个我们之前线上也有类似的场景干过。就是大量积压的时候,我们当时就直接丢弃数据了,然后等过了高峰期以后,比如大家一起喝咖啡熬夜到晚上 12 点以后,用户都睡觉了。这个时候我们就开始写程序,将丢失的那批数据,写个临时程序,一点一点的查出来,然后重新灌入 mq 里面去,把白天丢的数据给他补回来。也只能是这样了。假设 1 万个订单积压在 mq 里面,没有处理,其中 1000 个订单都丢了,你只能手动写程序把那 1000 个订单给查出来,手动发到 mq 里去再补一次。
|
RabbtiMQ 是可以设置过期时间的,也就是 TTL。如果消息在 queue 中积压超过一定的时间就会被 RabbitMQ 给清理掉,这个数据就没了。那这就是第二个坑了。这就不是说数据会大量积压在 mq 里,而是大量的数据会直接搞丢。我们可以采取一个方案,就是批量重导,这个我们之前线上也有类似的场景干过。就是大量积压的时候,我们当时就直接丢弃数据了,然后等过了高峰期以后,比如大家一起喝咖啡熬夜到晚上 12 点以后,用户都睡觉了。这个时候我们就开始写程序,将丢失的那批数据,写个临时程序,一点一点的查出来,然后重新灌入 mq 里面去,把白天丢的数据给他补回来。也只能是这样了。假设 1 万个订单积压在 mq 里面,没有处理,其中 1000 个订单都丢了,你只能手动写程序把那 1000 个订单给查出来,手动发到 mq 里去再补一次。
|
||||||
|
Loading…
x
Reference in New Issue
Block a user