Redis持久化
Redis是将数据存储在内存中的,要是服务宕机,所有数据将会丢失,为了防止数据丢失,Redis支持两种策略将内存中的数据写到磁盘中来防止数据丢失。
Redis提供两种持久化方式:RDB(Redis DataBase)和AOF(Append Only File)
RDB
该方式服务器进程会fork一个子进程,由子进程去做持久化操作,子进程会先将Redis所有非空数据库的数据进行拷贝到自己的内存空间,然后将这些数据写到一个临时文件,写入完毕,会使用临时文件替换掉dump.rdb文件,线程销毁
示意图
触发时机
使用相关命令保存
- SAVE命令:该命令执行时,Redis服务器会被阻塞,所以当SAVE命令正在执行时,客户端发送的所有命令请求都会被拒绝。
- BGSAVE命令:该命令的保存工作是由子进程执行的,所以在子进程创建RDB文件的过程中,Redis服务器任然可以继续处理客户端的命令请求。
自动间隔性保存
在redis.conf文件中已经默认配置了3种:
配置 | 描述 |
---|---|
save 900 1 | 服务器在900s(15min)内,对数据库进行了至少1次修改 |
save 300 10 | 服务器在300s(5min)内,对数据库进行了至少10次修改 |
save 60 10000 | 服务器在60s(1min)内,对数据库进行了至少有10000修改 |
AOF
该方式是以日志的形式记录Redis每个写操作,将Redis执行过的所有写指令记录下来(读操作不记录),只许追加文件不可以改写文件,Redis启动之后会读取appendonly.aof文件来实现重新恢复数据。默认不开启,需要将redis.conf种的appendonly no改为yes来启动。
要开启Redis的AOF持久化模式必须将appendonly
配置项改为yes
appendfsync选项的值 | 对持久化行为的影响 |
---|---|
always | 将aof_buf缓冲区中的所有内容写入并同步到AOF文件 |
everysec | 将aof_buf缓冲区中的所有内容写入到AOF文件,如果上次同步AOF文件的时间距离现在超过一秒钟,那么再次对AOF文件进行同步,并且这个同步操作是由一个线程专门负责执行的 |
no | 将aof_buf缓冲区中的所有内容写入到AOF文件,但并不对AOF文件进行同步,何时同步由操作系统来决定 |
文件写入与文件同步:可能有人不明白为什么将aof_buf的内容写到磁盘需要两步操作,这边简单介绍一下。Liunx操作系统为了提升性能,使用页缓存(page cache)。当我们将aof_buf的内容写到磁盘时(文件写入),此时数据并没有真正落盘,需要执行fsync/fdatasync命令来强制刷盘(文件同步)。
重写机制
上面介绍了AOF方式是通过记录对Redis每次的写操作,这样的话就会存在一个问题,一直添加最终会导致文件过于庞大。因此,为了避免这种状况,Redis提供了重写机制,当AOF文件得大小超过指定的阈值时,Redis会自动启用AOF文件的内容压缩,只保留可以恢复数据的最小指令集,可以使用命令bgrewriteaof
。
重写原理:AOF文件持续增长过大时,会fork出一条新进程来将文件重写(也是生成临时文件再rename),遍历服务进程的内存中的数据,每条记录会对应生成一条set语句写入到临时文件。重写aof文件的操作,并没有读取旧的aof文件,而是将整个内存中的数据库内容用命令的方式重写了到一个新的aof文件,类似快照。
触发时机:Redis会记录上一次重写时的aof大小,默认配置是当AOF文件大小是上一次的一倍并且大于64M时,会触发重写机制。
比如上一次AOF重写之后是128Mb,然后就会接着128Mb继续写AOF的日志,如果发现增长的比例超过了之前的100%(达到256Mb),就可能会触发一次rewrite。
数据不一致问题:Redis是通过创建一个子进程去做重写操作的,所以就有可能出现下面这种数据不一致情况
当子进程开始重写时,数据库中只有K1这一个键,但是当子进程完成AOF文件重写之后,服务器进程的数据库已经新增了K2、K3、K4三个键,因此,重写AOF文件和服务器当前的数据库状态并不一致。
解决方案:为了解决上面的数据不一致问题,Redis服务器引入了一个AOF重写缓冲区
如上图,这个缓冲区在服务器创建子进程的时候开始使用,当Redis执行完一个写命令之后,它会同时将这个命令发送给AOF缓冲区和AOF重写缓冲区(说明从创建子进程开始,服务器执行的所有写命令都会被记录到AOF重写缓冲区里面)。当子进程完成AOF重写之后,会向父进程发送一个信号,父进程会调用一个信号处理函数,并执行以下工作(此过程会阻塞父进程正常处理请求):
- 将AOF重写缓冲区中的所有内容写入到新的AOF文件中,这时新的AOF文件保存的数据状态将和服务器当前的数据库状态一致。
- 对新的AOF文件进行改名,原子地(atomic)覆盖现有的AOF文件,完成新旧两个AOF文件的替换。
这个信号处理函数执行完毕后,父进程给就可以继续像往常一样接受命令请求了。
fork的阻塞风险
先提出一个疑问:这个重写过程有没有潜在的阻塞风险呢?如果有的话,会在哪里阻塞?
Reids采用fork子进程重写AOF文件时,潜在的阻塞风险包括:fork子进程 和 AOF重写过程中父进程产生写入的场景,下面以此介绍。
fork子进程,fork这个瞬间一定是阻塞主进程的,fork时并不会一次性拷贝所有内存数据给子进程,fork采用操作系统提供的写时复制(Copy On Write)机制,就是为了避免一次性拷贝大量内存数据给子进程造成长时间阻塞的问题。
fork的系统开销
但fork子进程需要拷贝必须的数据结构,其中有一项就是拷贝内存页表(虚拟内存和物理内存的映射索引表),这个拷贝过程会消耗大量CPU资源,拷贝完成之前整个进程是会阻塞的,阻塞时间取决于整个实例的内存大小,实例越大,内存页表越大,fork阻塞时间越久。
拷贝内存页表完成后,子进程与父进程指向相同的内存地址空间,也就是说此时虽然产生了子进程,但是并没有申请与父进程相同的内存大小。
那什么时候父子进程才会真正内存分离呢?“写时复制”顾名思义,就是在写动作发生时,才真正拷贝内存真正的数据,这个过程中,父进程也可能会产生阻塞的风险,就是下面介绍的场景。
fork出的子进程指向父进程相同的内存地址空间,此时子进程就可以执行AOF重写,把内存中的所有数据写入到AOF文件中。但是此时父进程依旧是会有流量写入的,如果父进程操作的是一个已经存在的key,那么这个时候父进程就会真正拷贝这个key对应的内存数据,申请新的内存空间,这样逐渐地,父子进程内存数据开始分离,父子进程逐渐拥有各自独立的内存空间。
因为内存分配是以页为单位进行分配的,默认4K,如果父进程此时操作的是一个bigkey,重新申请大块内存耗时会变长,可能会产生阻塞分险。
另外,如果操作系统开启了内存大页机制(Huge Page,页面大小2M),那么父进程申请内存时阻塞的概率将会大大提高,所以在Redis机器上需要关闭Huge Page机制。
Redis每次fork生成RDB和AOF文件后,都可以在Redis Log中看到父进程重新申请了多大的内存空间。
总结
主要有以下两个分险:
分险一: Redis主线程fork创建bgrewriteaof子进程时,内核需要创建用于管理子进程的相关数据结构,这些数据结构在操作系统中通常叫做进程控制块(Process Control Block,简称PCB)。内核要把主进程的PCB内容拷贝给子进程。这个创建和拷贝过程由内核执行,是会阻塞主进程。而且,再拷贝过程中,子进程要拷贝父进程的页表,这个过程的额耗时和Redis实例的内存大小有关。如果Redis实例内存大,页表就会大,fork执行时间就会长,这就会给主线程带来阻塞风险。
分险二: bgrewriteaof子进程会和父进程共享内存。当主进程收到新写或修改操作时,主进程会申请新的内存空间,用来保存新写或修改的数据,如果操作的是bigkey,也就是数据量大的集合类型数据,那么,主进程会因为申请大空间而面临阻塞分险。因为操作系统在分配内存空间时,有查询和锁开销,这就会导致阻塞。
AOF文件的载入与数据还原
因为AOF文件包含了重建数据库状态的所有写命令,所以只要服务器载入AOF文件重新执行一遍保存的写命令,就可以还原数据库关闭之前的状态。(由于Redis命令只能在客户端上下文中执行,所以这块创建了一个伪客户端)
RDB与AOF对比
RDB的优点:
- 如果要进行大规模数据的恢复,RDB方式要比AOF方式恢复速度要快
- RDB可以最大化Redis性能,父进程做的就是fork子进程,然后继续接受客户端请求,让子进程负责持久化操作,父进程无需进程IO操作。
- RDB保存了某一时刻的数据集,非常适合用作备份,同时也非常适合灾难性恢复。
RDB的缺点:
- RDB不太适用对数据完整性要求严格的情况,尽管我们可以通过修改快照持久化的频率,但是要持久化的数据时一段时间内的整个数据集的状态,如果在还没有触发快照时,本机就宕机了,那么对数据库所做的写操作就随之消失。
- 每次进行RDB时,父进程会fork一个子进程,由子进程来进行实际的持久化操作,如果数据集庞大,那么fork子进程这个过程将非常耗时,就会出现服务器暂停客户端请求,将内存中的数据复制一份给子进程,让子进程进程持久化操作。
AOF的优点:
- AOF更好的保证数据不丢失
- AOF以append-only顺序写入,所以没有磁盘寻址的开销,写入性能非常高。
- AOF日志文件将命令以易读的方式进行记录,这个特性非常适合做灾难性误删除的紧急恢复。
AOF的缺点:
- 对于相同的数据集来说,AOF文件要比RDB文件大。
- 根据持久化策略来说,AOF的速度要慢于RDB
- 数据恢复比较慢
RDB与AOF如何选择
- 要想做到足够高的数据安全性,应该同时使用两种持久化方式。
- 如果可以接受分钟级别内的数据丢失,可以只使用RDB持久化(比如:只用做缓存)。
数据恢复机制
上面介绍了数据的持久化,而持久化的最终目录就是为了尽可能的避免数据丢失,当重启Redis服务的时候能够重新恢复数据到内存。
重启Redis时,如果dump.rdb与appendfsync.aof同时存在时,Redis会优先读取appendfsync.aof文件进行数据恢复。