概要
Redis是内存数据库,数据都是存储在内存中,为了避免进程退出导致数据的永久丢失,需要期将Redis中的数据以某种形式(数据或命令)从内存保存到硬盘;当下次Redis重启时,利用持久化文件实现数据恢复。除此之外,为了进行灾难备份,可以将持久化文件拷贝到一个远程位置
redis持久化的流程如下:
- 客户端向服务端发送写操作(数据在客户端的内存中)
- 数据库服务端接收到写请求的数据(数据在服务端的内存中)。
- 服务端调用write这个系统调用,将数据往磁盘上写(数据在系统内存的缓冲区中)。
- 操作系统将缓冲区中的数据转移到磁盘控制器上(数据在磁盘缓存中)。
- 磁盘控制器将数据写到磁盘的物理介质中(数据真正落到磁盘上)。
为应对以上5步操作,redis提供了两种不同的持久化方式:RDB(Redis DataBase)和AOF(Append Only File)
RDB
在指定的时间间隔能对数据进行快照存储
RDB持久化是将当前进程中的数据生成快照保存到硬盘(因此也称作快照持久化),保存的文件后缀是rdb;当Redis重新启动时,可以读取快照文件恢复数据。
触发&原理
在Redis中RDB持久化的触发分为两种:指令手动触发和 redis.conf 配置自动触发
指令手动触发
save
命令和bgsave
命令都可以生成RDB文件
save
:会阻塞当前Redis服务器,直到RDB文件创建完毕为止,线上应该禁止使用。
bgsave
:该触发方式会fork一个子进程,由子进程负责持久化过程,因此阻塞只会发生在fork子进程的时候。
自动触发
- 根据配置文件的的
save m n
配置规则自动触发; - 从节点全量复制时,主节点发送rdb文件给从节点完成复制操作,主节点会触发
bgsave
; - 执行 shutdown时,如果没有开启aof,也会触发。
redis.conf
# 时间策略
save 900 1 # 表示900 秒内如果至少有 1 个 key 的值变化,则触发RDB
save 300 10 # 表示300 秒内如果至少有 10 个 key 的值变化,则触发RDB
save 60 10000 # 表示60 秒内如果至少有 10000 个 key 的值变化,则触发RDB
# 文件名称
dbfilename dump.rdb
# 文件保存路径
dir /home/work/app/redis/data/
# 如果持久化出错,主进程是否停止写入
stop-writes-on-bgsave-error yes
# 是否压缩
rdbcompression yes
# 导入时是否检查
rdbchecksum yes
在进行快照时,Redis 会借助操作系统提供的写时复制技术(Copy-On-Write, COW),在执行快照的同时,正常处理写操作。
简单来说,bgsave 子进程是由主线程 fork 生成的,可以共享主线程的所有内存数据。bgsave 子进程运行后,开始读取主线程的内存数据,并把它们写入 RDB 文件。此时,如果主线程对这些数据也都是读操作(例如图中的键值对 A),那么,主线程和bgsave 子进程相互不影响。但是,如果主线程要修改一块数据(例如图中的键值对 C),那么,这块数据就会被复制一份,生成该数据的副本。然后,bgsave 子进程会把这个副本数据写入 RDB 文件,而在这个过程中,主线程仍然可以直接修改原来的数据。
既保证了快照的完整性,也允许主线程同时对数据进行修改,避免了对正常业务的影响。
RDB文件格式
其中文件头信息如下:
文件尾部信息:
优缺点
优势
1、执行效率高,适用于大规模数据的备份恢复。自动备份不会影响主线程工作。
2、备份的文件占用空间小。其备份的是数据快照,相对于AOF来说文件大小要小一些。
劣势
1、可能会造成部分数据丢失。因为是自动备份,所以如果修改的数据量不足以触发自动备份,同时发生
断电等异常导致redis不能正常关闭,所以也没有触发关闭的备份,那么在上一次备份到异常宕机过程中
发生的写操作就会丢失。
2、自动备份通过fork进程来执行备份操作,而fork进程会将当前进程的内存数据完整的复制一份,所以
这个过程占用的空间是原来的2倍,可能会导致内存不足
AOF
AOF(append only file):记录每次对服务器写的操作(命令),当服务器重启的时候会重新执行这些命令来恢复原始的数据。默认不开启
特点:
- 以日志的形式来记录用户请求的写操作,读操作不会记录,因为写操作才会存储
- 文件以追加的形式而不是修改的形式
- redis的aof恢复其实就是把追加的文件从开始到结尾读取 执行写操作。
AOF持久化实现
如上图所示,AOF 持久化功能的实现可以分为命令追加( append )、文件写入( write )、文件同步( sync)、文件重写(rewrite)和重启加载(load)。其流程如下:
- 所有的写命令会追加到 AOF 缓冲中。
- AOF 缓冲区根据对应的策略向硬盘进行同步操作。
- 随着 AOF 文件越来越大,需要定期对 AOF 文件进行重写,达到压缩的目的。
- 当 Redis 重启时,可以加载 AOF 文件进行数据恢复。
开启方式如下:
# 可以通过修改redis.conf配置文件中的appendonly参数开启
appendonly yes
# AOF文件的保存位置和RDB文件的位置相同,都是通过dir参数设置的。
dir ./
# 默认的文件名是appendonly.aof,可以通过appendfilename参数修改
appendfilename appendonly.aof
以 Redis 收到set testkey testvalue
命令后记录的日志为例,看看 AOF 日志的内容。其中,“*3”表示当前命令有三个部分,每部分都是由“$+数字”开头,后面紧跟着具体的命令、键或值。这里,“数字”表示这部分中的命令、键或值一共有多少字节。例如,“$3 set”表示这部分有 3 个字节,也就是“set”命令。
三种写回策略
Redis 每次结束一个事件循环之前,它都会调用 flushAppendOnlyFile
函数,判断是否需要将 AOF 缓存区中的内容写入和同步到 AOF 文件中。
flushAppendOnlyFile
函数的行为由 redis.conf 配置中的 appendfsync
选项的值来决定。该选项有三个可选值,分别是 always
、 everysec
和 no
:
always
: 同步写回:每个写命令执行完,立马同步地将日志写回磁盘;everysec
:每秒写回:每个写命令执行完,只是先把日志写到 AOF 文件的内存缓冲区,每隔一秒把缓冲区中的内容写入磁盘;no
:操作系统控制的写回:每个写命令执行完,只是先把日志写到 AOF 文件的内存缓冲区,由操作系统决定何时将缓冲区内容写回磁盘。
总结一下就是:想要获得高性能,就选择 No 策略;如果想要得到高可靠性保证,就选择Always 策略;如果允许数据有一点丢失,又希望性能别受太大影响的话,那么就选择Everysec 策略。
AOF重新机制
为了解决 AOF 文件体积膨胀的问题,Redis 提供了 AOF 文件重写( rewrite) 策略
AOF 重写机制就是在重写时,Redis 根据数据库的现状创建一个新的 AOF 文件,也就是说,读取数据库中的所有键值对,然后对每一个键值对用一条命令记录它的写入。比如说,当读取了键值对“testkey”: “testvalue”之后,重写机制会记录 set testkey testvalue
这条命令。这样,当需要恢复时,可以重新执行该命令,实现“testkey”: “testvalue”的写入。
我们知道,AOF 文件是以追加的方式,逐一记录接收到的写命令的。当一个键值对被多条写命令反复修改时,AOF 文件会记录相应的多条命令。但是,在重写的时候,是根据这个键值对当前的最新状态,为它生成对应的写入命令。这样一来,一个键值对在重写日志中只用一条命令就行了,而且,在日志恢复时,只用执行这条命令,就可以直接完成这个键
值对的写入了。
AOF重写时是否会阻塞主线程?
和 AOF 日志由主线程写回不同,重写过程是由后台线程 bgrewriteaof 来完成的,这也是为了避免阻塞主线程,导致数据库性能下降。
可以把其过程总结为:“一个拷贝,两处日志”。
“一个拷贝”就是指,每次执行重写时,主线程 fork 出后台的 bgrewriteaof 子进程。此时,fork 会把主线程的内存拷贝一份给 bgrewriteaof 子进程,这里面就包含了数据库的最新数据。然后,bgrewriteaof 子进程就可以在不影响主线程的情况下,逐一把拷贝的数据写成操作,记入重写日志。
两处日志”又是什么呢?
因为主线程未阻塞,仍然可以处理新来的操作。此时,如果有写操作,第一处日志就是指正在使用的 AOF 日志,Redis 会把这个操作写到它的缓冲区。这样一来,即使宕机了,这个 AOF 日志的操作仍然是齐全的,可以用于恢复。
而第二处日志,就是指新的 AOF 重写日志。这个操作也会被写到重写日志的缓冲区。这样,重写日志也不会丢失最新的操作。等到拷贝数据的所有操作记录重写完成后,重写日志记录的这些最新操作也会写入新的 AOF 文件,以保证数据库最新状态的记录。此时,我们就可以用新的 AOF 文件替代旧文件了。
总结来说,每次 AOF 重写时,Redis 会先执行一个内存拷贝,用于重写;然后,使用两个日志保证在重写过程中,新写入的数据不会丢失。而且,因为 Redis 采用额外的线程进行数据重写,所以,这个过程并不会阻塞主线程。
持久化优先级
如果一台服务器上有既有RDB文件,又有AOF文件,该加载谁呢?
快照多久做一次?
时间太长,出现问题丢失的数据太多,时间太短,频繁写磁盘会带来压力,频繁fork子进程在fork时会阻塞主进程。
最好的方式:增量快照,一次全量快照后,记录增量的数据。
Redis 4.0 中提出了一个混合使用 AOF 日志和内存快照的方法。简单来说,内存快照以一定的频率执行,在两次快照之间,使用 AOF 日志记录这期间的所有命令操作。这样一来,快照不用很频繁地执行,这就避免了频繁 fork 对主线程的影响。而且,AOF 日志也只用记录两次快照间的操作,也就是说,不需要记录所有操作了,因此,就不会出现文件过大的情况了,也可以避免重写开销