Redis持久化

本文深入解析Redis的RDB和AOF两种持久化机制,包括它们的实现原理、优缺点及如何选择。RDB通过快照方式保存数据,适用于数据备份;AOF通过记录命令实现数据持久化,更利于数据安全。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

Redis是一个键值对数据库服务器,并且是内存数据库。在处理客户端请求时都是在内存中操作,如果没有将内存中的数据保存到磁盘上,那么一旦服务器宕机或者进程退出,内存中的数据就会消失。
对于只把Redis当做缓存的项目来说,数据消失问题不大,只需要重新从数据源把数据加载进来就可以。但是如果是将Redis当做存储数据库来使用,数据消失是不能被允许的。
为了解决这个问题,Redis提供了两种方式来使数据持久化,使数据可以从内存中保存到磁盘上行。

redis-client
redis-sever
内存
磁盘
RDB

RDB可以将Redis在内存中的数据状态保存到磁盘中,属于快照形式的持久化方式,默认保存的文件名为dump.rdb。
RDB持久化既可以手动执行,也可以根据配置选项定期执行,该功能可以将某个时间点上的数据库状态保存到RDB文件中。
RDB持久化所生成的文件是一个经过压缩的二进制文件,通过该文件可以还原成生成该文件时的数据库状态。

保存为
还原
数据库状态
RDB文件
RDB文件的创建

Redis有两个命令可用于手动生成RDB文件,一个是SAVE,一个是BGSAVE
SAVE命令会阻塞服务器进程,直到RDB文件创建完毕,在服务器进程阻塞期间,服务器不能处理任何命令请求。

redis> SAVE	//等待直到RDB文件创建完毕
OK
SAVE
X
X
redis-client
redis-client
redis-client
redis-sever
内存
磁盘

BGSAVE不采用直接阻塞服务器的做法,它会fork一个子进程,然后由子进程创建RDB文件,创建RDB文件后子进程退出,在此期间服务器进程(父进程)可以继续处理请求。

redis> BGSAVE	//fork子进程,由子进程创建RDB文件

BGSAVE
fork
redis-client
redis-client
redis-client
redis-sever(主进程)
内存
磁盘
redis-sever(子进程)

创建RDB文件工作由rdb.c/rdbSave函数完成,SAVEBGSAVE通过不同的方式调用该函数,以下是伪代码展示两者区别:

def SAVE():
    # 创建RDB文件
    rdbSave()


def BGSAVE():
    # 创建子进程
    pid = fork()
    if pid == 0:

        # 子进程创建RDB文件
        rdbSave()

        # 完成之后向父进程发送信号
        signal_parent()

    elif pid > 0:

        # 父进程继续处理请求,并轮询等待子进程信号
        handle_request_and_wait_signal()

    else:

        # 处理出错情况
        handle_fork_error()

虽然BGSAVE的保存工作是由子进程来完成的,服务器可以继续执行来自客户端的请求,但是在处理SAVEBGSAVEBGREWRITEAOF(该命令稍后会有说明)三个命令和其他命令有所区别。
BGSAVE执行期间,如果客户端发送SAVE命令,服务器会拒绝执行。禁止同时执行SAVE命令和BGSAVE命令是为了避免父进程和子进程同事调用rdbSave,防止竞争条件。
BGSAVE执行期间,如果客户端发送BGSAVE命令,服务器也会拒绝执行,原因也是执行两个BGSAVE会产生竞争条件。
BGSAVEBGREWRITEAOF不能不能同时执行:

  • 如果BGSAVE正在执行,客户端发送的BGREWRITEAOF命令会被延迟执行到BGSAVE完成后执行。
  • 如果BGREWRITEAOF正在执行,客户端发送的BGSAVE会被拒绝执行。
    原因是,BGSAVEBGREWRITEAOF两个命令都是由子进程执行,所以两个命令在操作方面不会产生冲突,不能同时执行是处于性能方面考虑,并发的两个子进程都同时对磁盘进行大量的写入操作会严重影响服务器的效率。
RDB文件的载入

RDB文件载入是在服务器启动的时候自动执行的,所以RDB没有专门的命令来执行文件载入。只要Redis在启动时检测到RDB文件存在,它就会自动载入。
服务器启动后,日志记录中会出现一行DB loaded from disk:…就是RDB文件成功载入之后的打印记录。
另外,因为AOF文件的更新频率比RDB文件的更新频率高,所以,如果服务器开启了AOF持久化,那么服务器会优先使用AOF文件了恢复数据库状态。
流程图如下:

在这里插入图片描述

自动间隔保存

因为BGSAVE可以在不阻塞服务器的情况下执行,所以Redis允许用户通过服务器配置redis.conf,让服务器每隔一段时间执行一次BGSAVE命令。
用户可以保存多个设置条件,只要任意一个被满足,服务器就会执行BGSAVE命令。
例如:

# 900s内至少达到一条写命令
save 900 1
# 300s内至少达至10条写命令
save 300 10
# 60s内至少达到10000条写命令
save 60 10000

不过这方式来触发 RDB 持久化是很难控制的,因为设置触发的时间太短,则容易频繁写入 RDB 文件,影响服务器性能,时间设置太长则会造成数据丢失。

前面介绍了三种让服务器生成 RDB 文件的方式,无论是由主进程生成还是子进程来生成,其过程如下:

  • 生成临时 RDB 文件,并写入数据。
  • 完成数据写入,用临时文代替代正式 RDB 文件。
  • 删除原来的 DB 文件。

RDB的几个优点:

  • 与 AOF 方式相比,通过 RDB 文件恢复数据比较快。
  • RDB 文件非常紧凑,适合于数据备份。
  • 通过 RDB 进行数据备份,由于使用子进程生成,所以对 Redis 服务器性能影响较小。

RDB的几个缺点:

  • 如果服务器宕机的话,采用 RDB 的方式会造成某个时段内数据的丢失,比如我们设置 10 分钟同步一次或 5 分钟达到 1000 次写入就同步一次,那么如果还没达到触发条件服务器就死机了,那么这个时间段的数据会丢失。
  • 使用 Save 命令会造成服务器阻塞,直接数据同步完成才能接收后续请求。
  • 使用 Bgsave 命令在 Fork 子进程时,如果数据量太大,Fork 的过程也会发生阻塞,另外,Fork 子进程会耗费内存。
AOF

除了RDB,Redis还提供了AOF(Append Only File)持久化功能。与RDB持久化通过保存数据库的键值对来记录数据库状态不同,AOF持久化通过记录服务器执行的写命令来记录数据库状态。
举个例子:

redis> SET msg "hello"
OK
redis> INCR num 1
OK
redis> PUSH numbers 12 34
OK

AOF持久化保存数据库状态的方式是将服务器执行的SET、INCR、PUSH三个命令保存到AOF文件中。
服务器在启动时,通过执行AOF文件来还原数据库状态,载入完成后,日志记录中会打印一条DB loaded from append only file:…。

SET msg ''hello''
INCR num 1
PUSH numbers 12 34
fork
redis-client
redis-client
redis-client
redis-sever
AOF缓冲区
内存
AOF文件
AOF持久化的实现

AOF持久化功能的实现可分为命令追加(append)、文件写入、文件同步(sync)三个步骤。
当AOF持久化功能打开时,服务器执行完一条命令,会以协议格式将被执行的写命令追加到服务器状态的aof_buf缓冲区。
Redis服务器进程就是一个事件循环(loop),这个循环中的事件负责接收客户端的命令请求,以及向客户端发送回复。
服务器在处理文件事件时可能会执行写命令,使得一些内容被追加到aof_buf缓冲区中,所以在每次完成文件事件循环之前,会调用flushAppendOnlyFile函数,考虑是否将aof_buf缓冲区的内容写入到AOF文件中。
过程的伪代码如下:

def eventLoop():

    while True:

        # 处理文件事件,接收命令以及回复
        # 可能会将新的内容追加到aof_buf缓冲区
        processFileEvent()

        # 处理事件事件
        processTimeFileEvent()

        # 考虑是否将aof_buf缓冲区的内容写入到AOF文件中
        flushAppendOnlyFile()

flushAppendOnlyFile函数由服务器配置appendfsync选项来确定:

appendfsync选项值flushAppendOnlyFile函数行为
always将aof_buf缓冲区的所有内容写入并同步到AOF文件中
everysec(默认)将aof_buf缓冲区的所有内容写入到AOF文件中,如果上次同步AOF文件距离现在超过一秒钟,再次对AOF文件进行同步,这个同步操作专门由一个线程操作
no将aof_buf缓冲区的所有内容写入到AOF文件中,但是并不同步AOF文件,合适同步头操作系统决定

为了提高文件的写入效率,现代操作系统中,当用户调用write函数,将一些数据写入文件,操作系统通常会将文件保存在一个内存缓冲区中,等到缓冲区的空间被填满、或者超过了制定时限后,才真正的将缓冲区中的内容写入到磁盘。
这种做法虽然提高了效率,但是有存在安全性问题,如果计算机停机,那么保存在缓冲区中的内容将会丢失。
为此,系统提供了fsync和fdatasync两个函数,可以强制让缓冲区中的内容立刻写入磁盘,从而确保数据的安全性。

  • always:客户端的每一个写操作都保存到 AOF 文件当中,这种策略很安全,但是每个写操作都有 IO 操作,所以也很慢。
  • everysec:appendfsync 的默认写入策略,每秒写入一次 AOF 文件,因此,可能会丢失 1s 内的数据。
  • no:Redis 服务器不负责写入 AOF,而是交由操作系统来处理什么时候写入 AOF 文件。更快,但也是最不安全的选择,不推荐使用。
AOF文件的载入与数据还原

因为AOF文件中包含了重建数据库状态需要的所有命令,所以服务器只需要读入并重新执行AOF中保存的写命令,就可以恢复数据库。
读取AOF的步骤如下:
在这里插入图片描述

  1. 创建一个不带网络连接的伪客户端(fake client),因为Redis只能在客户端上下文中执行,而载入AOF文件时所使用的命令直接来自AOF文件而不是网络连接,所以服务器使用一个不带网络连接的伪客户端来执行AOF文件中的写命令,伪客户端执行命令的效果和来自一般客户端执行的命令效果一样。
  2. 从AOF中读取一条写命令。
  3. 使用伪客户端执行写命令。
  4. 重复2和3步骤,知道AOF文件中的写命令执行完毕。
AOF重写

因为AOF持久化通过保存写命令来记录数据库状态,随着时间的推移,写命令越来越多,对应生成的AOF文件内容也会越来越多,文件体积也会随之增大。如果不加以控制可能会对Redis服务器甚至整个计算机造成影响。并且AOF文件越大,恢复数据所使用的时间也就越多。
举个例子:

reids> incr num 1
reids> incr num 2
reids> incr num 3
reids> incr num 4
reids> incr num 5
reids> incr num 6
...
reids> incr num 1000

光记录这个num就需要1000条命令。为了解决AOF文件膨胀的问题,Redis提供了AOF文件重(rewrite)写功能。通过该功能,Redis会创建一个新的AOF文件来替换原来的AOF文件。两个文件保存的数据库状态相同,但是新的AOF文件中没有冗余的命令,所以新的文件要比旧的文件体积小的多。

AOF文件重写实现

虽然Redis将生成的新的AOF文件替换旧的AOF文件的功能叫做“AOF文件重写”,但实际上,AOF文件重写并不会对现有的AOF文件做任何的读取、分析或写入操作,这个功能是通过读取数据库现有状态来实现的。
如上面的例子,要保存num的状态要向AOF文件中写入1000条数据。如果服务器想用尽量少的命令来保存num的状态,最简单、高效的方法不是去读取、分析现有的AOF文件中的命令,而是直接读取数据库中num键的值,然后用一条SET num 1000命令来代替原来的1000条命令。
从数据库中读取键现有的状态,然后用一条命令去记录键值对代替原来的多条命令,这就是实现AOF文件从写的原理。

AOF后台重写

因为AOF文件重写有大量的I\O操作,Redis使用单线程来处理命令请求,如果重写AOF文件为同步操作,那么线程将被长时间阻塞,在重写期间,服务器将无法处理客户端请求。
作为一种辅助操作,Redis并不希望再重写期间造成无法处理命令请求,所以最好将重新任务放到子进程中执行。这样做可以达到两个目的:

  • 子进程重写AOF文件期间,父进程可以继续处理客户端命令。
  • 子进程带有带有服务器进程的数据副本,使用子进程而不是线程,可以避免在使用锁的情况下,保证数据的安全性。

使用子进程也会出现一个问题,就是在AOF重写期间,服务器进程还需继续处理命令请求,新的命令请求也可能有写操作,这样使数据库当前状态与AOF重写的状态不一致。
为了解决这个不一致的问题,Redis服务器设置了AOF重写缓冲区,当Redis创建子进程执行AOF重写之后,服务器执行完一个写命令,会将这个写命令同时追发送到AOF缓冲区和AOF重写缓冲区。
也就是说,在子进程执行AOF重写期间,服务器进程需要执行三个工作:

  1. 执行客户端发来的命令
  2. 将执行完的写命令追加的AOF缓冲区
  3. 将执行完的写命令追加的AOF重写缓冲区
    在这里插入图片描述
    这样一来可以保证:
  • AOF缓冲区的内容被定期写入和同步到AOF文件,对现有的AOF文件处理工作照常进行。
  • 重创建子进程之后,服务器执行的写命令都会被记录到AOF重写缓冲区。

当子进程完成AOF重写之后,会向父进程发送信号,父进程收到信号之后,会执行以下工作:

  1. 将AOF重写缓冲区中的所有内容写入到AOF文件,这时新的AOF文件中的内容保存的数据库状态和服务器数据库的状态一致。
  2. 对新的AOF文件改名,覆盖现有的AOF文件,完成新旧AOF文件的替换。

处理完成之后,父进程继续接受命令请求。
在整个AOF重写过程中,只有父进程在收到信号后执行的工作会对服务器造成阻塞,这将AOF重写造成的对服务器的影响降到最低。
以上AOF后台重写,也即使BGREWRITEAOF的实现原理。

AOF 文件损坏
在写入 AOF 日志文件时,如果 Redis 服务器宕机,则 AOF 日志文件文件会出格式错误。
在重启 Redis 服务器时,Redis 服务器会拒绝载入这个 AOF 文件,可以通过以下步骤修复 AOF 并恢复数据:

  • 备份现在 AOF 文件,以防万一。
  • 使用 redis-check-aof 命令修复 AOF 文件,该命令格式如下:
# 修复aof日志文件
$ redis-check-aof -fix file.aof
  • 重启 Redis 服务器,加载已经修复的 AOF 文件,恢复数据。

AOF 的优点:

  • AOF 只是追加日志文件,因此对服务器性能影响较小,速度比 RDB 要快,消耗的内存较少。

AOF 的缺点:

  • AOF 方式生成的日志文件太大,即使通过 AFO 重写,文件体积仍然很大。
  • 恢复数据的速度比 RDB 慢。
选择 RDB 还是 AOF 呢?

通过下面的表示,我们可以从几个方面对比一下 RDB 与 AOF,在应用时,要根据自己的实际需求,选择 RDB 或者 AOF。
其实,如果想要数据足够安全,可以两种方式都开启,但两种持久化方式同时进行 IO 操作,会严重影响服务器性能,因此有时候不得不做出选择。
在这里插入图片描述

小结: 上面讲了一大堆 Redis 的持久化机制的知识,其实,如果你只是单纯把 Redis 作为缓存服务器,那么可以完全不用考虑持久化。
但是,在如今的大多数服务器架构中,Redis 不单单只是扮演一个缓存服务器的角色,还可以作为数据库,保存我们的业务数据,此时,我们则需要好好了解有关 Redis 持久化策略的区别与选择。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值