持久化
一、认识
Redis
持久化 是指将 Redis
中的数据保存到磁盘,以便在 Redis
服务器重启或崩溃时能够恢复数据 (Redis
所有数据保持在内存中, 对数据的更新将异步地保存到磁盘上)。Redis
提供了几种持久化方式,通过不同的机制保证数据的持久化和高效性。根据需求的不同,持久化方式的选择也有所不同。Redis
的持久化机制 主要有两种:
一、RDB
数据库快照: RDB
是 Redis
的一种持久化方式,通过在指定时间间隔内生成数据的快照,并将其保存为二进制文件(通常是 dump.rdb
文件)。RDB
适合于数据不需要频繁更新或对持久化性能有较高要求的场景。RDB
特点: 周期性存储, RDB
会在指定的时间间隔内生成快照,将数据库的当前状态保存到磁盘; 全量存储, RDB
存储的是整个数据库的快照,不支持增量保存; 低延迟读取, 由于 RDB
是全量存储的二进制文件,恢复数据时比 AOF
更快速; 适用于备份, RDB
适合做周期性的备份,或者在 Redis
重启后恢复数据。RDB
缺陷: 数据丢失风险, 由于 RDB
是周期性存储的,如果 Redis
崩溃或发生故障,可能会丢失上次保存后的所有数据(比如上次保存和故障发生之间的数据)。因此, RDB
, 适合定期备份,内存消耗低,恢复快,但可能丢失最后一次保存后的数据。
-
配置:
RDB
存储可以通过配置文件设置条件,指定何时生成快照:save 900 1 # 900秒(15分钟)内至少有1个键发生变化时,进行快照保存
save 300 10 # 300秒(5分钟)内至少有10个键发生变化时,进行快照保存
save 60 10000 # 60秒内至少有10000个键发生变化时,进行快照保存
dir ./ # 指定了 RDB 文件的存放目录,此处设置为当前工作目录。在生产环境中通常建议使用绝对路径以避免路径问题。
dbfilename bump.rdb # 设置 RDB 文件的文件名为 bump.rdb,这意味着生成的快照文件不会采用默认的 dump.rdb 名称,便于根据业务或版本需求进行区分管理。
rdbchecksum yes # 开启校验功能,生成的 RDB 文件末尾会包含校验码。在恢复数据时,Redis 会使用这个校验码检查文件的完整性,确保数据没有被损坏。
rdbcompression yes # 启用数据压缩,在生成 RDB 文件时会对数据进行压缩,从而节省磁盘空间。需要注意的是,压缩可能会增加 CPU 的负担,因此在高性能需求的场景下需要权衡使用。
stop-writes-on-bgsave-error yes # 当 Redis 在后台执行 BGSAVE(异步保存 RDB 文件)过程中遇到错误时,会停止接收写命令。这是为了避免在持久化失败后继续写入数据,可能导致数据的不一致性或丢失,从而提高系统的安全性。我们在实际项目开发中, 一般不使用自动生成
dump.rdb
的配置,因为不受控制。我们的最佳配置如下:dir /bigdiskpath # 指定一个很大存储空间的目录
dbfilename dump-${port}.rdb # 基于端口来区分 dump.rdb 文件
rdbcompression yes # 启用数据压缩,在生成 RDB 文件时会对数据进行压缩,从而节省磁盘空间
stop-writes-on-bgsave-error yes # 当 Redis 在后台执行 BGSAVE(异步保存 RDB 文件)过程中遇到错误时,会停止接收写命令 -
触发:
Redis
的RDB
(快照) 持久化方式主要用于定期将内存中的数据保存到磁盘上的一个二进制文件中,其触发方式主要包括以下几种:-
自动触发: 基于
save
配置规则, 在redis.conf
中,可以配置多个save
规则,如:save 900 1 # 如果 900 秒内至少有 1 个 key 被修改,则触发一次 RDB 快照。
save 300 10 # 如果 300 秒内至少有 10 个 key 被修改,则触发快照。
save 60 10000 # 如果 60 秒内至少有 10000 个 key 被修改,则触发快照。只要满足任一规则,
Redis
就会自动调用BGSAVE
命令,在后台fork
出子进程生成RDB
文件,而不会阻塞主进程。 -
手动触发:
-
SAVE
: 使用SAVE
命令也可以生成RDB
快照,但这是一个同步操作,会阻塞Redis
进程直到保存完成,因此在生产环境中较少使用。 -
BGSAVE
: 手动执行BGSAVE
命令,在后台fork
出子进程生成RDB
文件,而不会阻塞主进程。适合在需要即时备份数据时使用,同时不会影响正常业务。
-
-
主从复制初次同步: 当一个从节点刚连接到主节点时,从节点会请求一个完整的数据快照以进行初次同步。此时,主节点会生成一次
RDB
快照并将其传送给从节点。
-
-
文件策略: 默认情况下,
Redis
的RDB
文件命名为dump.rdb
。文件存放位置由redis.conf
中的dir
参数指定,确保所有快照文件都位于预设的目录中,便于管理和备份。-
原子性与临时文件机制: 1. 临时文件写入, 在执行
RDB
快照时,Redis
会先在子进程中将数据写入一个临时文件(通常带有临时标识,如dump.rdb.tmp
)。2. 原子替换, 完成写入后,临时文件会通过原子性操作(rename
)替换原有的RDB
文件。这种策略确保了在快照过程中,即使发生异常,原有的RDB
文件仍然保持有效,避免了部分写入或损坏的风险; 文件替换是瞬间完成的,避免在读取文件时出现不一致的状态。 -
文件覆盖与备份策略: 覆盖更新, 每次成功生成
RDB
快照后,旧的快照文件会被新生成的文件所覆盖。这意味着Redis
始终只保留最近一次成功生成的全量快照。备份建议, 由于覆盖策略可能导致历史快照丢失,建议在生产环境中定期备份RDB
文件,以防止误操作或系统故障导致的数据恢复问题。 -
压缩配置: 可以通过
rdbcompression
参数决定是否对RDB
文件进行压缩。开启压缩可以显著减少磁盘空间的占用,但在快照生成时可能会增加CPU
开销。
-
二、AOF
追加文件日志: AOF
是 Redis
的另一种持久化方式,它通过记录所有写操作来实现持久化。每次对 Redis
的写操作都会追加到 AOF
文件中,从而可以通过重放 AOF
文件来恢复数据。AOF
持久化保证了较高的数据持久性,但它对磁盘和性能的要求较高。AOF
特点: 增量存储, AOF
会记录每次写操作的命令,因此数据恢复时可以通过执行这些命令来重建数据; 高度可靠, 由于每次写入操作都被记录,AOF
可以保证数据的持久性。即使 Redis
崩溃,AOF
文件也可以恢复所有已执行的操作; 慢写操作, AOF
文件会不断追加写操作,因此其写入性能相对较差,尤其在高并发写操作时。AOF
缺陷: 性能开销, AOF
操作会记录每个写操作,频繁的写入会增加 I/O
开销,导致性能下降; AOF
文件增大, AOF
文件随着写入操作的增加会变得非常大,尤其在没有优化的情况下。因此, AOF
, 适合对数据可靠性要求高的应用,能够保证不丢失写操作,但可能会导致性能下降。
-
配置:
AOF
持久化可以通过以下方式进行配置appendonly yes # 启用 AOF 持久化
appendfsync everysec # 每秒同步一次 AOF 文件
appendfsync always # 每次写入操作都同步(更可靠,但性能差)
appendfsync no # 不主动同步(性能最佳,但风险最大) -
策略:
Redis AOF
持久化机制提供了三种不同的appendfsync
策略,用于平衡数据安全性和写入性能:-
always
策略: 每执行一条写命令后,Redis
都会立即调用fsync
将数据写入磁盘。数据几乎不会丢失,安全性最高。性能开销大,频繁的系统调用可能严重影响写入性能,不适合高并发场景。 -
everysec
策略:Redis
每秒钟执行一次fsync
操作,将这一秒内的所有写命令统一写入磁盘。在大多数场景下,性能和数据安全性取得了较好的平衡;即使发生故障,也最多丢失最近1
秒的数据。在故障发生时,可能会丢失最后一秒的数据。 -
no
策略: 不主动调用fsync
,完全依赖操作系统的磁盘缓存刷新机制,由操作系统决定何时将数据从缓存写入磁盘。性能最高,因为减少了大量的系统调用。数据安全性最低,操作系统调度不当时可能会导致较大范围的数据丢失。
-
-
重写: 由于
AOF
会不断增加写操作命令,文件可能会变得非常大。Redis
提供了一个AOF
重写(bgrewriteaof
)机制,定期重新生成一个精简版本的AOF
文件,去掉冗余的命令, 来减少硬盘占用量, 加速恢复速度。Redis
会在后台线程中执行bgrewriteaof
操作,压缩AOF
文件,删除不必要的命令,使AOF
文件更小。AOF
触发方式 如下:-
自动触发:
AOF
文件的大小超过某个阈值时,Redis
会自动触发AOF
重写。比如AOF
信息aof_current_size > auto-aof-rewrite-min-size && aof_current_size - aof_base_size/aof_base_size > auto-aof-rewrite-percentage
同时满足时,Redis
会自动触发AOF
重写auto-aof-rewrite-min-size # AOF 文件重写需要的尺寸
auto-aof-rewrite-percentage # AOF 文件增长率我们实际项目的
AOF
配置如下:dir /bigdiskpath # 指定一个很大存储空间的目录
appendonly yes # 启用 AOF 持久化
appendfsync everysec # 使用 everysec 同步策略
appendfilename "appendonly-${port}.aof" # AOF 文件命名
no-appendfsync-on-rewrite yes # AOF 重写时,是否需要做正常的 append 操作, yes 为不做 append 操作
auto-aof-rewrite-min-size 100 # AOF 文件重写需要的尺寸
auto-aof-rewrite-percentage 64mb # AOF 文件增长率 -
手动触发: 手动执行
bgrewriteaof
命令进行重写。
Redis
使用fork
出一个子进程来执行AOF
重写任务,利用操作系统的写时复制(copy-on-write
)机制,使得子进程可以基于当前内存数据生成新的 AOF 文件,而不会阻塞主进程对客户端请求的响应。子进程遍历内存中的所有数据,将恢复当前数据状态所需的最简命令(例如SET key value
)依次写入一个临时的AOF
文件。这样生成的新文件只包含能够重构出当前数据状态的必要命令,相比原来的AOF
文件更加精简。在子进程重写期间,主进程仍然处理客户端的写请求,这些新写入的命令会被保存在一个增量缓冲区中。重写完成后,这些在重写过程中产生的命令会追加到新AOF
文件的末尾,确保没有任何数据丢失。当新AOF
文件生成完毕,并且增量缓冲区中的数据也已经追加进去后,Redis
使用原子性操作(如rename
)将旧的AOF
文件替换为新的文件。这种原子替换保证了在任何时刻,Redis
读取的都是一个完整且一致的AOF
文件。 -
-
AOF
追加阻塞:Redis
的AOF
(Append Only File
)持久化机制通过将每次写命令追加到文件中,来实现数据的持久化。但在这一过程中,会存在 追加阻塞 的问题。Redis
的appendfsync
参数提供了三种策略:always
, 每次写操作后都立即调用fsync
,将数据同步到磁盘。这种模式保证了数据安全性,但每次写操作都需等待磁盘同步完成,容易导致阻塞;everysec
, 每秒执行一次fsync
操作,是一种折中的选择。这样可以在保证数据相对安全的前提下,降低每次写入的阻塞时间;no
, 不主动调用fsync
,而是让操作系统决定何时刷新磁盘缓冲区。这种方式性能最好,但数据的实时持久化安全性较低。由于fsync
是一个同步调用,在执行该操作期间,Redis
主线程会被阻塞,无法处理其他命令请求。尤其在appendfsync always
模式下,每个写操作都会等待磁盘同步完成,从而直接导致请求延迟。在高并发场景中,如果磁盘的响应较慢(例如使用性能较低的机械硬盘),频繁的fsync
操作会使得大量命令处于等待状态,从而显著影响Redis
的整体吞吐量和响应速度。为了防止AOF
文件过大,Redis
会定期进行AOF
重写操作。虽然重写过程采用后台fork
机制,但在合并最后缓冲区内容时仍可能出现短暂阻塞,因此可以考虑在系统低负载时进行重写。-
阻塞定位:
-
Redis
命令info all / info persistence
命令: 可以查看AOF
状态、当前AOF
文件大小、AOF fsync
延迟、最后一次fsync
操作的状态(如aof_last_write_status
)等信息,从中判断是否有频繁的fsync
阻塞现象。 -
Redis
命令latency latest / latency history
: 可以获取最近的延迟记录,看看是否有和fsync
或AOF
相关的延迟条目。Redis
的延迟监控能够帮助你快速定位是否存在由于fsync
导致的命令阻塞问题。 -
使用系统工具(如
iostat
、vmstat
或dstat
)监控磁盘I/O
性能,确认磁盘响应是否存在瓶颈。若磁盘延迟较高,fsync
操作的等待时间自然会拉长,从而导致AOF
阻塞。
-
-
阻塞优化:
-
不要和高硬盘负载服务部署在一起: 比如存储服务、消息队列
-
使用
SSD
或者高性能存储设备可以降低fsync
操作的延迟,从根本上减少阻塞时间 -
单机多实例持久化目录, 可以考虑分盘、或者资源限制、甚至可以使用
CGroup
类似技术来限制硬盘资源的分配 -
通常推荐使用
appendfsync everysec
策略,这样可以在保证大部分数据安全的同时,避免每次写操作都被阻塞 -
为了防止
AOF
文件过大,Redis
会定期进行AOF
重写操作。虽然重写过程采用后台fork
机制,但在合并最后缓冲区内容时仍可能出现短暂阻塞,因此可以考虑在系统低负载时进行重写。
-
-
三、AOF
和 RDB
混合使用: Redis
允许同时启用 RDB
和 AOF
持久化机制,以便结合两者的优点。RDB
用于周期性生成数据的快照,而 AOF
记录每次写操作,提供更高的持久化保证。组合优点: 备份与数据恢复的双重保障, RDB
提供较快的数据恢复速度,而 AOF
提供较高的数据持久性,二者结合能提供更强的可靠性; 适应不同需求, 可以根据业务需求调整 RDB
和 AOF
的配置,使得数据恢复和性能能够达到一个平衡点。
-
配置:
save 900 1 # 启用 RDB 快照
appendonly yes # 启用 AOF 持久化
appendfsync everysec # 每秒同步一次 AOF 文件 -
优先级: 如果
Redis
重新启动后, 同时有dump.rdb
文件和.aof
文件。AOF
文件的加载优先级高于dump.rdb
文件。在默认配置下(即启用了AOF
持久化),如果Redis
重启时同时存在dump.rdb
文件和.aof
文件,Redis
会优先加载AOF
文件。这是因为AOF
文件记录了所有写操作,相较于dump.rdb
快照,AOF
文件通常包含了最新的数据变更,从而能够更准确地恢复出最近的数据状态。如果AOF
加载成功,则dump.rdb
文件不会被使用。
Redis
提供了两种持久化方式(RDB
和 AOF
), 选择何种持久化方式取决于以下几个因素:
-
数据丢失容忍度: 如果业务中可以接受一定的丢失,可以选择
RDB
;如果要求较高的数据可靠性,则AOF
更合适。 -
性能要求:
AOF
会有更多的写操作和I/O
开销,可能会影响性能。如果性能是优先考虑的因素,可以考虑主要使用RDB
。 -
恢复速度:
RDB
恢复数据时较快,而AOF
恢复时需要执行所有操作,速度较慢。 -
内存占用:
AOF
文件会随操作积累,可能占用较多磁盘空间,而RDB
的文件较小,但会周期性生成快照。
所示, RDB
适合定期备份,内存消耗低,恢复快,但可能丢失最后一次保存后的数据; AOF
, 适合对数据可靠性要求高的应用,能够保证不丢失写操作,但可能会导致性能下降; 另外, 结合 RDB
和 AOF
的优点,既能提供数据备份,又能确保高可靠性。选择合适的持久化策略可以根据业务的具体需求进行调整,保证 Redis
的高效和可靠性。
二、扩展
2.1 fork 操作
Redis
中的 fork()
操作主要用于创建子进程来执行后台任务,从而避免阻塞主进程。这一机制在持久化(如 RDB
快照生成、AOF
重写)和复制初始同步等场景中非常关键。 fork()
是同步操作, 且内存越大, 耗时越长。基本原理: 1. 进程克隆:通过 fork()
,Redis
会复制当前进程的内存、文件描述符等状态,生成一个子进程。子进程一开始与父进程几乎完全相同。2. Copy-On-Write
(写时复制)机制:在 fork()
后,父子进程共享同一块物理内存。只有在任一进程尝试修改某个内存页时,内核才会为该页分配新的物理内存副本,确保彼此数据互不影响。注意事项: 内存占用峰值, 在 fork()
后如果父进程持续进行写操作,可能导致大量内存页被复制,从而在短时间内大幅增加内存使用量; 系统资源要求, fork()
操作需要操作系统分配额外资源,特别是在大数据量场景下,可能会对系统性能产生影响,需要合理监控和配置系统参数。改善 fork()
: 1. 可以控制 Redis
实例最大可用内存 maxmemory
; 2. 合理配置 Linux
内存粉皮儿策略 vm.overcommit_memory = 1
; 3. 降低 fork()
频率, 比如放宽 AOF
重写自动触发时机, 不必要的全量复制。Redis
fork()
主要用途:
-
RDB
快照生成(BGSAVE
): 当Redis
需要生成RDB
快照时,通过fork()
创建子进程,在子进程中遍历内存数据并写入临时文件,避免阻塞主进程继续响应客户端请求。 -
AOF
重写(BGREWRITEAOF
): 类似地,AOF
重写时利用fork()
生成子进程,将内存中的数据转换为一系列重构数据状态所需的最简命令,形成新的AOF
文件。 -
复制初次同步: 在主从复制场景中,新从节点连接时,主节点会
fork()
子进程生成数据快照供从节点同步。
2.2 子进程开销
子进程开销如下:
-
内存占用与写时复制开销:
-
初始开销较低:
fork()
时,子进程与父进程共享内存,不会立即复制整个内存空间。 -
写时复制:一旦父子进程对共享内存进行写操作,内核会为这些内存页创建副本。这在高写负载场景下,会导致大量内存页被复制,从而使内存占用瞬间增加,甚至可能引发内存不足问题。
-
-
CPU
与系统调用开销:-
fork()
操作本身较快:单次fork()
调用通常开销较小,但频繁的fork
操作(比如频繁的RDB
或AOF
重写)会给CPU
带来不小的负担。 -
增量复制与同步操作:子进程生成持久化文件时需要遍历整个数据集,并与主进程期间产生的增量数据进行合并,这也会消耗一定的
CPU
资源。
-
-
磁盘
I/O
影响: 子进程在生成快照或重写AOF
文件时会进行大量磁盘写操作,如果磁盘I/O
能力不足,可能会拖慢整个过程,进而影响主进程与整体系统的响应能力。
2.3 子进程优化
-
减少
fork
期间的写操作: 尽量降低fork
后父进程对内存大块区域的写操作,可以通过优化业务逻辑、延迟不必要的内存修改等方式,减少写时复制的触发。 -
合理规划持久化策略:
-
调整持久化频率:根据业务场景调整
RDB
的save
规则以及AOF
重写的触发条件,避免过于频繁的fork
操作。 -
分布持久化任务:对于写操作较多的应用,可以考虑采用异步持久化或使用混合持久化方案,从而降低单次
fork
带来的开销。
-
-
优化内存管理:
-
使用高效内存分配器:
Redis
默认采用jemalloc
,它在处理大内存块和减少碎片方面表现优异,有助于降低fork
时的内存复制开销。 -
监控内存碎片:定期监控
Redis
的内存使用情况,及时进行内存优化,防止内存碎片导致fork
时内存开销激增。
-
-
调整操作系统参数:
-
内存
overcommit
策略:配置Linux
的内存overcommit
参数(如vm.overcommit_memory
),合理的设置可以在fork
时降低因内存预分配不足而导致的问题。 -
提升磁盘
I/O
性能:使用SSD
或其他高性能磁盘,确保持久化文件写入不会成为性能瓶颈。
-
-
不要和高硬盘负载服务部署在一起: 比如存储服务、消息队列
-
单机多实例持久化目录: 可以考虑分盘、或者资源限制、甚至可以使用
CGroup
类似技术来限制硬盘资源的分配