京东二面:Redis持久化方式有哪些?说下各自的优缺点?线上环境如何进行配置的?

数据库 Redis
如果存储在Redis中的数据相对不敏感或能便捷地重新生成,可以选择暂时关闭持久化功能,以减少资源消耗。这样即使偶发数据丢失,也能通过其他机制迅速恢复数据。比如一些数据相对于DB来说,并没有经过一些特殊处理,可以直接从DB中进行恢复,相当于直接在DB做备份。

引言

Redis作为一款高性能的键值存储系统,广泛应用于缓存、消息队列、会话存储等多种场景,成为提升应用性能的关键组件。作为内存数据库,它存储在内存中的数据在服务器重启或发生意外崩溃时将会丢失。所以需要持久化机制能够将内存中的数据保存到磁盘上,从而在Redis服务停止或重启后能够恢复数据,保证数据的安全性。

在实际开发中的一些通用方案是业务数据最终都保存在DB中,所以也可以从数据库中恢复Redis数据。但是这种方式,会造成数据库的性能瓶颈,特别是在大量Redis数据需要恢复的时候。并且Redis中缓存的数据,会经过一些特殊的处理,跟DB中表数据可能会不一致,所以从DB中恢复Redis数据还需要一些特殊的业务梳理。

所以就需要从Redis自身出发会解决这个问题,Redis设计了两种主要的持久化机制:RDB和AOF。实际上,对于持久化方案,官方文档中给出了三种方式:RDB,AOF,RDB+AOF。

图片图片

关于Redis持久化机制的官方文档:Redis persistence | Docs

Redis持久化方式

Redis提供了两种核心的持久化策略:RDB(快照)和AOF(仅追加日志),随着Redis版本的演进,还引入了混合持久化的方式,进一步优化了Redis的持久化。

RDB

RDB,即Redis Database,是一种快照式持久化方式,它通过定期创建数据集的快照来实现数据的持久存储。在预设条件(如时间间隔、键值变化次数)满足时,Redis会生成当前内存中所有数据的备份文件。当Redis服务需要重新启动时,可以直接加载这些RDB文件,从而快速恢复数据。由于RDB文件是二进制格式,加载速度通常较快。生成快照有自动触发和手动触发两种方式。

自动触发

Redis会将数据集的快照保存到名为dump.rdb的二进制文件中,保存到磁盘上。我们可以通过Redis配置(redis.conf文件),使其在数据集中至少有M个(changes)更改时,每N秒(seconds)自动保存一次数据集。

save <seconds> <changes> [<seconds> <changes> ...]
# 生成的rdb文件名称
dbfilename dump.rdb

# rdb文件保存路径,可自定义
dir ./

# rdb快照保存配置
save 3600 1 300 100 60 10000

# 如果至少进行了1次更改,那么在3600秒(1小时)后保存;
# 如果至少进行了100次更改,那么在300秒(5分钟)后保存;
# 如果至少进行了10000次更改,那么在60秒后保存。

# 关闭RDB快照
save ""

当Redis服务正常关闭时,如果没有启用AOF(Append Only File)持久化,它也会执行一次RDB快照,确保数据被保存到磁盘上。

手动触发

我们还可以通过调用SAVE或BGSAVE命令来手动触发快照保存。这两个命令的区别在于执行数据持久化操作时是否阻塞主线程。

SAVE命令

当执行SAVE命令时,Redis会直接调用rdbSave函数来创建数据快照,并将当前数据集写入到磁盘上的RDB文件中。这是一个同步操作,在保存过程期间,Redis的主进程会被阻塞,无法处理任何其他客户端的请求,直到整个保存操作完成。所以,如果数据集较大,此操作可能会导致Redis服务在较长时间内无法响应,影响服务的可用性。

BGSAVE命令

使用BGSAVE命令时,Redis会通过操作系统fork()调用创建一个子进程,由这个子进程负责执行rdbSave函数和数据持久化工作,即创建数据快照并写入RDB文件。持久化操作在子进程中进行,Redis的主进程(父进程)可以继续处理来自客户端的请求,不会因为持久化操作而阻塞。当子进程完成快照创建后,会向主进程发送一个信号,通知任务完成,然后子进程关闭。

BGSAVE命令执行流程BGSAVE命令执行流程

具体执行流程如下:

1. 当Redis客户端发出BGSAVE命令,或者根据配置规则自动触发BGSAVE操作时,持久化进程开始

2. Redis主进程检查是否有正在进行的子进程正在执行BGSAVE或AOF rewrite操作。如果存在这种操作,为了避免资源竞争和数据一致性问题,主进程不会再次发起BGSAVE,可能直接返回一个错误或忽略此次请求。

3. 如果没有其他持久化操作正在进行,主进程会执行fork()系统调用来创建一个子进程。fork()调用在执行时会暂时阻塞主进程,直到子进程完全创建。这个过程中,Redis的内存数据不会被复制,而是通过操作系统实现的写时复制(Copy-On-Write, COW)机制共享给子进程。

4. 子进程开始工作,它将内存中的数据逐步写入到一个新的临时RDB文件中。这个过程对主进程是透明的,不会影响主进程处理客户端的读写请求。

5. 当所有数据写入完毕后,子进程会使用原子操作(通常借助操作系统提供的rename函数)将临时RDB文件替换为正式的RDB文件。保证RBD文件的完整性。

6. 完成上述步骤后,子进程会向主进程发送一个信号,告知BGSAVE操作已完成。

RDB优缺点

优点

• 快速恢复:RDB文件加载速度非常快,因为它直接将数据载入内存而无需逐条执行命令,适合用于快速重启服务后的数据恢复。

• 资源占用低:RDB文件(二进制文件)通常较小,占用磁盘空间少,备份和传输更加高效。

缺点

• 数据丢失风险:如果Redis在两次快照之间崩溃,那么从上一次快照之后的所有数据变更将会丢失。因此,RDB更适合对数据实时性要求不高,但重视恢复速度的场景。

• 无法进行部分恢复:一旦发生故障,只能整体恢复到最近一次快照的状态,无法选择性恢复特定数据。

几个常见的面试问题

• 如果我们的Redis内存的数据比较大(可能给Redis分配的内存较大),我们生成快照保存到磁盘,就会花费的时间要久一些,如果此时Redis的主进程依然还要在处理一些写数据的请求,那么我们如何保证数据一致性呢?

有上述RDB的执行流程中可以看出,主进程通过写时复制,将数据共享给子进程。在持久化操作开始时,父子进程共享相同的内存空间,直到任一进程尝试修改数据。当主进程接收到写操作请求并需要修改数据时,操作系统会为被修改的数据分配新的内存空间,保持原数据不变供子进程快照使用。即便在快照生成期间有写操作,也能保证快照数据反映的是快照操作开始那一刻的数据状态,确保了数据的一致性。

图片图片

• 在生成快照的时候,Redis服务跪了,数据会不会丢失? 在上述BGSAVE命令执行流程时,在最后的写入磁盘时,子进程会使用原子操作将临时RDB文件替换为正式的RDB文件,保证RBD文件的完整性。也就是说Redis在执行BGSAVE命令生成快照时,会先将数据写入到一个临时的RDB文件中,而不是直接覆盖原有的RDB文件。只有当新的快照完全且成功地写入磁盘之后,才会用这个新的快照文件替换掉旧的RDB文件。这一过程是原子性的,确保了要么新快照完全成功,要么不会有任何改动,从而避免了部分写入导致的文件损坏,避免数据的丢失。

• 快照的生成时间间隔是不是越小越好?如何合理的设置? 对于RDB生成快照来做数据持久化,就是通过连续的生成数据快照将数据保存到磁盘。一单服务发生故障,那么我们就可以通过磁盘上的文件恢复数据。那么,为了防止数据丢失,或者数据丢失过多时,我们可以通过减少时间间隔来实现,也就是说,最好可以一直不停的生成快照,这样就可以达到数据不丢失的目的。

图片图片

如上图,T0时刻生成了一次快照,那么在t时刻时发生了一次数据修改写入,在写入后到生成下一次快照之前,发生了故障,那么我们恢复数据时,因为T0+t时刻的快照还没生成,数据恢复时只能使用T0时刻的快照,那么在t时刻发生的数据写入就会丢失。所以t时刻的间隔如果变的更小,就会多生成快照,就有很大的几率减少数据丢失的。

那么是不是说这个生成快照的间隔越小越好,甚至说1秒生成一次快照?因为从BGSAVE的执行流程上看,它是通过fork一个子进程去实现快照的生成,并不会阻塞主进程的数据写入。

事实上,这种说法是错误的。理论上,缩短快照间隔可以减少数据丢失的风险,因为一旦系统崩溃,可以恢复到更接近崩溃时间点的数据状态。但是,过于紧密的快照间隔会引发大量的磁盘I/O操作,可能导致数据写操作堆积,反而影响数据的一致性。

并且,虽然Redis使用BGSAVE命令在子进程中生成快照,避免了主线程的长时间阻塞,但是fork子进程的操作本身是阻塞的,并且内存越大,fork操作所需的时间越长。频繁的fork会导致服务在短时间内频繁阻塞,影响响应速度。

那么,如果我们可以只对增量的数据进行快照保存,在第一次进行全量的快照保存后,后续的快照都只对增量的数据(其实这个比较很好理解,这种方案我们也很常见,比如我们在对接一些第三方系统时,在拉取数据时,我们在第一次全量拉去后,后面的数据都只会拉去变化的数据)。那么对于增量的数据,首先我们要知道那些数据变化了,此时,就需要Redis的另外一种机制:AOF了。

AOF

AOF(Append Only File)持久化是一种以日志形式记录每次写操作的方式,以此来保证数据的持久性。与RDB不同,它提供了更高的数据完整性保障,因为其记录了从Redis启动以来的所有写操作,理论上可以做到数据零丢失。

每当有写命令(如SET、HSET等)被执行时,Redis就会将该命令以明文形式追加到AOF文件的末尾。这意味着AOF文件随着时间推移会不断增长,记录着Redis服务器上的所有数据修改历史。

并且AOF采用的是一种"写后日志"机制,即Redis在处理写命令时,先执行写操作,将数据变更写入内存,然后才将该操作命令追加到AOF日志文件中。

写后日志这种机制与一些数据库系统采用的“预写日志”策略恰好相反,后者是在实际数据写入前先记录日志。

为什么采用写后日志?

1. 性能考虑:通过先执行写命令,Redis可以更快地响应客户端,因为写入内存的速度远快于写入磁盘。这样可以减少客户端等待时间。

2. 简化实现:写后日志避免了在记录日志之前对命令进行语法检查的需要。如果先记录日志再执行命令,错误的命令也可能被记录,导致在使用日志恢复数据时出现问题。而在Redis中,因为命令已经执行成功,所以写入日志的命令都是已验证过的,可以安全重放。

3. 简化恢复逻辑:在Redis重启时,通过重放AOF日志中的命令来恢复数据状态,由于这些命令都是曾经成功执行过的,理论上可以无误地重建内存数据结构,降低了数据恢复的复杂度。

当然这中机制也存在着一些潜在风险,比如数据丢失风险,如果在命令执行后、但未记录到AOF日志前发生故障,这部分数据更新将丢失。

AOF实现流程

1. 命令追加(Append): 当Redis服务器配置开启了AOF持久化功能后,每当执行一个写命令(如SET、HSET、LPUSH等),服务器并不会立即写入到磁盘,而是先将这条命令以Redis协议的格式追加到内存中的一个缓冲区,即aof_buf。

这样做是为了减少直接磁盘I/O的频率,提高写入命令的处理速度。

2. 文件写入(Write): 缓冲区中的内容并不会一直停留,而是会按照一定的策略(由配置项appendfsync控制)被写入到AOF文件中。AOF提供了三种同步策略来控制命令写入AOF文件:

• always:每个写命令都会立即同步到磁盘,最安全但性能消耗最大。

• everysec(默认):每秒执行一次fsync操作,平衡了数据安全性与性能。最多丢失一秒的数据。

• no:完全依赖于操作系统来决定何时将数据写入磁盘,性能最佳但可能丢失一秒内的数据。

3. 文件同步(Sync): 文件同步操作是将内存中已写入AOF文件的数据真正持久化到磁盘上。上述写入AOF文件策略的不同,同步操作的时机也会有所不同。文件同步系统调用是同步操作的核心,它确保了缓冲区中的数据被物理写入磁盘,从而在硬件故障时也能保证数据不丢失。

AOF重写机制

随着时间的推移,AOF文件会因为不断追加写命令而逐渐增大,这不仅占用大量磁盘空间,还会影响数据恢复的速度。AOF重写机制通过生成一个更紧凑的新AOF文件来解决这个问题,新文件仅包含重建当前数据集状态所需的最小命令集合,从而替代原有的庞大AOF文件。

AOF重写可以通过手动执行BGREWRITEAOF命令来触发,也可以通过配置自动触发。自动触发的条件通常是AOF文件大小增长到一定比例(如通过auto-aof-rewrite-percentage配置)或达到一定的最小尺寸(如通过auto-aof-rewrite-min-size配置)。

auto-aof-rewrite-percentage 100
auto-aof-rewrite-min-size 64mb

类似RDB的BGSAVE命令的工作机制,当重写命令发出后,Redis会通过fork()系统调用创建一个子进程。子进程开始遍历Redis内存中的数据结构,对每个键值对执行相应最少的写命令(如SET、HMSET等),将这些命令写入到一个新的临时AOF文件中。这个过程中,子进程不会处理任何网络请求,也不会影响主进程处理客户端命令的能力。

与RDB不同的是,在子进程重写期间,主进程会继续接收和处理客户端的写请求。为了避免这些新命令丢失,主进程会将它们追加到一个内存缓冲区(AOF重写缓冲区)中。

一旦子进程完成重写,会将临时AOF文件替换掉旧的AOF文件。(为了保证数据一致性,这一过程通常需要原子操作。)在新AOF文件替换旧文件后,主进程会将之前累积在AOF重写缓冲区中的命令追加到新AOF文件的末尾,确保所有数据变更都得到记录。

AOF重写流程AOF重写流程

如何配置AOF

默认情况下,Redis是没有开启AOF的,可以通过配置redis.conf文件来开启AOF持久化。

# 开启AOF,默认为no,关闭
appendonly yes

# AOF文件名
appendfilename "appendonly.aof"

# 控制AOF fsync策略
appendfsync everysec

# 文件重写策略
aof-rewrite-incremental-fsync yes

# AOF重写触发条件,当AOF文件增长到上一次重写后大小的百分比时触发
auto-aof-rewrite-percentage 100

# 触发重写的最小AOF文件大小
auto-aof-rewrite-min-size 64mb

# 在Redis启动时如何加载AOF文件,默认为yes
aof-load-truncated yes

# aof重写期间是否同步
no-appendfsync-on-rewrite no

AOF的优缺点

优点

• 几乎可以实现数据的零丢失,即使在服务器崩溃后也能恢复到最新状态。

• 通过重写机制可以有效控制文件大小,且在极端情况下,即使AOF文件损坏,也往往能通过跳过错误命令来恢复大部分数据。

缺点

• AOF文件会不断膨胀,占用大量磁盘空间。

• AOF在服务器重启时需要逐条执行命令来恢复数据,这会消耗更多时间。

几个常见的面试题

• AOF重写会阻塞主进程吗? 由上述AOF重写流程中,主进程执行AOF重写命令时,类似RDB的BGSAVE命令,也会fork出来一个子进程用于AOF的重写,此时并不会阻塞主进程的读写操作。当然,在fork子进程时会阻塞。

• 当AOF重写时,此时的写操作的数据会不会丢失? 由AOF重写流程,在子进程执行重写AOF时,如果此时有数据写操作,此时仍然由主进程完成,同时主进程再把数据写入内存后,会把命令写到一个内存缓冲区,也就是AOF重写缓冲区,在子进程完成AOF重写,替换旧的AOF文件,生成新的AOF文件之后,会通知主进程,此时主进程会讲重写缓冲区的命令追加到新的AOF文件之后。这样就保证数据的完整性。

• 为什么AOF重写不复用原AOF日志? AOF重写不直接复用原AOF日志的原因主要是为了确保数据的一致性和提高重写效率。AOF重写的目的在于生成一个尽可能小的新AOF文件,该文件仅包含重建当前数据集状态所需的最少命令集。直接编辑原AOF文件难以直接去除冗余命令(比如某条数据可能有数次的变更后产生,其以前的命令就意义不大了)。从AOF重写流程上看,Redis会遍历内存数据,将可以实现数据的最简洁的命令写入到临时的AOF文件。

Redis在执行AOF重写时,主进程仍继续处理客户端命令。如果直接修改原AOF文件,就需要复杂的并发控制来防止数据混乱,而使用新文件重写可以避免这种复杂性。通过子进程执行重写,与主进程的命令处理互不影响。

如果在尝试修改原AOF文件的过程中遇到错误或系统崩溃,可能会导致AOF文件损坏,进而影响数据恢复。使用独立的新文件进行重写,即便过程中出现问题,原AOF文件依然可用。

并且,原AOF文件可能非常大,直接在原文件上进行编辑操作可能会非常缓慢,影响Redis的服务性能。另外,文件系统的直接编辑操作通常比顺序写入新文件更耗时,尤其是在有大量随机读写的情况下。

RDB与AOF的混合使用

为了同时获得RDB的快速恢复速度和AOF的高度数据完整性,Redis支持将这两种持久化策略结合起来使用。混合使用RDB和AOF,取其二者的优点,可以实现数据安全性和恢复效率的最佳平衡。

在混合持久化模式下,Redis首先利用RDB文件快速恢复到某个时间点的数据库状态,然后通过重放AOF日志来补充RDB快照之后的数据变更。RDB作为基础恢复,RDB快照提供了某一时间点的全量数据视图,重启时可以迅速加载到内存,快速使服务可用。而AOF补充细节,AOF日志用于填补从RDB快照以来的数据变动,使得Redis能够逐步恢复到崩溃或重启前的最新状态。

比如,针对RDB快照设置生成间隔的示例,我们使用AOF记录写命令,当某一时刻宕机后,恢复数据时,我们可以从AOF中恢复上一次备份到这次宕机时保存的写操作对应的数据,当数据恢复之后,再使用RDB进行生成快照,然后再清空AOF文件。

图片图片

Redis重启时判断是否开启aof,如果开启了aof,那么就优先加载aof文件;

这种方式的优势在于:结合RDB的快速加载能力和AOF的细粒度恢复,达到了数据安全性和服务恢复速度的双重优化。根据业务对数据安全等级和恢复时间的要求,可以灵活调整RDB与AOF的比重和策略。通过合理配置RDB保存频率和AOF重写策略,可以有效管理磁盘空间使用,避免不必要的资源浪费。

生产环境的建议

在生产环境进行Redis持久化时,我们可以从以下几个方面考虑:

1. 如果存储在Redis中的数据相对不敏感或能便捷地重新生成,可以选择暂时关闭持久化功能,以减少资源消耗。这样即使偶发数据丢失,也能通过其他机制迅速恢复数据。比如一些数据相对于DB来说,并没有经过一些特殊处理,可以直接从DB中进行恢复,相当于直接在DB做备份。

2. 在同一台服务器部署多Redis实例时,要注意协调各实例的持久化操作(如RDB快照或AOF重写),避免它们同时进行,以免引发内存、CPU或I/O资源的竞争,推荐采取串行执行的方式,确保系统稳定运行。

3. 如果配置了Redis的主从,我们可以指定一个或多个从节点专门负责数据备份处理,这样主节点就能专注于处理客户端请求,提高整体服务的响应速度和可用性。

4. 结合使用RDB和AOF两种持久化方式,可以实现数据恢复的速度与完整性之间的一种最佳平衡。RDB提供快速恢复的能力,而AOF则能更详尽地记录每一步操作,两者互补,确保数据完整性。

责任编辑:武晓燕 来源: 码农Academy
相关推荐

2019-10-22 10:48:48

Redis集群架构

2022-09-13 14:42:35

Redis内存函数

2023-09-26 10:17:35

楼宇自动化传感器

2010-07-13 17:02:18

SQL Server

2023-05-26 00:00:00

Redis持久化方式

2023-03-06 08:41:32

CPU使用率排查

2020-05-13 17:12:21

大数据分布式引擎

2021-12-16 23:40:33

部署ReactTypeScript

2019-11-18 16:20:48

RedisRDB数据库

2017-10-23 13:20:37

2023-03-27 15:37:43

自动化测试开发

2010-04-19 17:27:53

无线上网猫

2022-07-26 11:17:38

京东 APP新图标品牌升级

2013-09-23 09:10:14

2023-10-08 08:46:29

Java遍历方式

2023-09-12 10:49:44

Redis数据库

2010-02-22 16:05:40

Python配置

2010-01-20 13:29:40

C++环境

2011-09-02 14:37:32

UbuntuAndroid

2013-05-31 10:29:22

备份磁带加密加密密钥加密
点赞
收藏

51CTO技术栈公众号