第八篇:Redis 为什么不会每次修改都写磁盘?

Redis 为什么不会每次修改都写磁盘?

上一篇我们聊了《Redis 为什么要同时支持 RDB 和 AOF?》,了解了 Redis 在持久化设计上的取舍。

RDB 通过保存某个时间点的数据快照,让 Redis 可以快速恢复数据,但是两次快照之间的数据可能丢失。AOF 通过记录每一次写操作,让 Redis 尽可能减少数据丢失,但是随着运行时间增加,日志文件会越来越大。

所以 Redis 最终选择了两种方案结合。

但是 AOF 又带来了一个新的问题:

既然 AOF 可以记录每一次修改,那为什么 Redis 不直接在每一次修改之后,把数据同步到磁盘?

这样不是最安全的吗?

例如:

客户端执行 SET

修改内存数据

写入 AOF

同步磁盘

返回成功

如果按照这个流程执行,Redis 返回成功时,数据已经真正保存到了磁盘。

看起来这是一个完美方案。

但是 Redis 并没有这么设计。

原因是:

Redis 追求的并不是绝对的数据安全,而是在接近内存速度的情况下,提供足够可靠的数据恢复能力。

如果每一次修改都等待磁盘完成,Redis 最核心的优势反而会消失。

Redis 为什么不能每次修改都写磁盘?

Redis 快的原因,大家通常会想到:

"因为 Redis 使用内存。"

这个答案没有错,但是不完整。

真正决定 Redis 性能的,是它的大部分操作都发生在内存中。

例如执行:

SET user:name redis

Redis 需要做的事情非常简单:

接收命令

解析命令

修改内存数据结构

返回结果

整个过程没有磁盘参与。

但是如果增加持久化:

接收命令

修改内存数据结构

写入磁盘

等待磁盘完成

返回结果

问题就出现了。

内存和磁盘的速度差距非常大。

CPU 访问内存可能只需要几十纳秒,而一次磁盘 IO,即使 SSD,也需要微秒甚至更长时间。

如果 Redis 每执行一次写操作,都必须等待磁盘完成,那么 Redis 的性能上限就不再由 CPU 和内存决定,而是由磁盘决定。

对于一个追求高并发的内存数据库来说,这是无法接受的。

那为什么不用 write 代替磁盘同步?

很多人可能会想到:

既然同步磁盘太慢,那只写文件不等待不就行了吗?

实际上,Redis 的 AOF 写入确实不会每次都直接等待磁盘。

因为 Linux 文件写入本身也有缓存机制。

整个过程大致如下:

Redis

|

| write()

操作系统 Page Cache

|

| fsync()

磁盘

Redis 调用 write 后,数据首先进入操作系统缓存。

例如:write(fd, buffer, size);

这个操作只是把数据交给操作系统。

此时数据并不一定已经保存到了磁盘。

如果机器突然断电:

Redis 内存

丢失

操作系统缓存

可能丢失

磁盘已保存数据

保留

所以真正保证数据落盘,需要:

fsync(fd);

fsync 会要求操作系统把缓存中的数据同步到磁盘。

因此:

write 解决的是"写入文件"。

fsync 解决的是"真正保存"。

fsync 为什么不能频繁执行?

既然 fsync 才安全,那 Redis 为什么不每次执行?

因为 fsync 太慢。

假设 Redis 每秒处理 10 万次写请求。

如果每次请求:

write()

fsync()

那么意味着磁盘需要每秒完成大量同步写操作。

更严重的是,fsync 会阻塞当前线程。

Redis 早期核心命令执行模型是单线程:

客户端请求

Redis 主线程

执行命令

返回结果

如果加入:

客户端请求

Redis 主线程

执行命令

等待 fsync

返回结果

那么一个磁盘慢操作,就会阻塞整个 Redis。

这也是 Redis 设计中非常重要的一点:

Redis 可以接受后台任务慢,但不能接受主线程慢。

因为主线程负责处理所有客户端请求。

Redis 如何解决这个问题?

Redis 的方案是:

不要让写磁盘阻塞命令执行。

也就是说:

数据修改和数据刷盘分开。

客户端请求:

执行命令

修改内存

写入 AOF 缓冲区

返回成功

后台:

AOF 缓冲区

后台任务

fsync

磁盘

这样 Redis 主线程可以继续处理新的请求。

这就是 Redis 持久化设计中的一个核心思想:

把影响性能的慢操作,从核心请求链路中移出去。

Redis 为什么选择 everysec?

既然刷盘放到了后台,那么新的问题来了:

多久刷一次?

Redis 提供了三个策略:

appendfsync always

appendfsync everysec

appendfsync no

它们代表不同的数据安全选择。

always:最高安全,但是牺牲性能

always 表示:

每一次写操作都立即 fsync。

流程:

执行命令

写 AOF

fsync

返回成功

这种方式数据安全最高。

因为客户端收到成功响应时,数据已经保存到了磁盘。

但是性能最低。

因为每一个请求都可能等待磁盘。

对于高并发业务,这种方式基本无法发挥 Redis 的优势。

everysec:性能和安全的平衡

everysec 是 Redis 默认策略。

流程:

客户端请求

修改内存

写入 AOF

返回成功

后台每秒 fsync

它的核心思想:

允许极少量数据丢失,换取更高性能。

例如:

10:00:00

写入数据

10:00:01

后台刷盘

如果 Redis 在中间发生异常,最多丢失最近一秒的数据。

对于大部分互联网业务:

用户登录状态

热点数据

缓存信息

非核心计数

这种程度的数据损失通常可以接受。

所以 Redis 选择:

不是保证每一次操作绝对安全,而是保证大多数情况下数据可靠。

no:完全交给操作系统

no 模式:

Redis 只负责:

write()

什么时候同步磁盘,由操作系统决定。

这种方式性能最高。

但是风险也最高。

因为 Redis 无法控制:

操作系统什么时候刷盘

缓存中积累了多少未保存数据

所以生产环境通常不会使用。

AOF Rewrite:日志为什么不会无限增长?

解决刷盘问题之后,AOF 又出现了新的问题。

AOF 记录的是操作过程。

例如:

SET count 1

SET count 2

SET count 3

SET count 4

这些命令都会保存。

但是 Redis 最终状态:

count = 4

前面的:

SET count 1

SET count 2

SET count 3

实际上已经没有意义。

如果 Redis 长时间运行:

大量历史命令

AOF 文件不断增长

恢复时间越来越长

所以 Redis 引入了:

AOF Rewrite

Rewrite 并不是简单压缩文件,而是根据当前内存状态重新生成 AOF。

例如:

原来的:

SET count 1

SET count 2

SET count 3

SET count 4

重新生成:

SET count 4

恢复时只需要执行最终状态。

为什么 Rewrite 不会影响 Redis?

如果 Rewrite 在主线程执行:

Redis 主线程

生成 AOF 文件

无法处理请求

这显然不可接受。

所以 Redis 使用子进程完成 Rewrite。

流程:

Redis 主进程

继续处理客户端请求

子进程生成新的 AOF

期间新的写操作

进入重写缓冲区

合并数据

替换旧文件

Redis 再一次体现了自己的设计原则:

耗时操作异步化,核心路径保持简单。

Redis 为什么最终选择这种持久化方式?

如果 Redis 每次修改都立即写磁盘:

数据安全最高。

但是性能会明显下降。

如果 Redis 完全不考虑磁盘:

性能最高。

但是数据无法恢复。

所以 Redis 最终选择:

内存快速执行

后台异步刷盘

保证数据恢复能力

RDB 解决的是:

如何快速保存某个时间点的数据。

AOF 解决的是:

如何记录数据变化过程。

everysec 解决的是:

如何避免磁盘影响 Redis 性能。

Rewrite 解决的是:

如何控制 AOF 文件规模。

Redis 从来没有追求一个绝对完美的方案,而是在性能、可靠性和复杂度之间寻找一个工程上的平衡。

这也是 Redis 能够长期保持高性能,同时又具备持久化能力的原因。

上一篇:《Redis 为什么要同时支持 RDB 和 AOF?》

下一篇:《Redis 为什么会发生缓存穿透、击穿、雪崩?》

相关推荐
微露清风6 小时前
浅析 MySQL 索引
android·数据库·mysql
沈浩(种子思维作者)6 小时前
心理的研究,我从何来?
数据库·人工智能·量子计算
糖果店的幽灵6 小时前
【langgraph 从入门到精通graphApi 篇】节点与边 —— 图的骨架
java·数据库·人工智能·redis·langgraph
z落落7 小时前
SQL 表的基本操作 数据库增删改查(CRUD)+SQL 进阶语法详解(标识列插入、模糊查询、Union、变量、运算、别名)
数据库·sql
Lucis__9 小时前
基于Cache替换算法的LRU缓存实现
数据结构·c++·算法·缓存·lru
代码的小搬运工10 小时前
网络请求(NSURL、NSURLRequest、NSURLSessionDataTask、协议回调与 JSON 数据的基本流向)
网络·数据库·json
Cx330❀16 小时前
【MySQL基础】一文吃透“表的约束”:从 Null/Default 到主外键的终极安全法则
linux·服务器·数据库·c++·mysql·安全
c2385616 小时前
第二篇:《测试指挥官:可视化单题自测框架(含 assert 实操)》
java·数据库·c++·算法·安全性测试
我科绝伦(Huanhuan Zhou)16 小时前
MySQL极端场景数据丢失防护:从Crash-Safe到异地备份
数据库·mysql