Redis之持久化

188 阅读7分钟

持久化

Redis的数据存在内存当中,如果机器发生宕机等意外情况,数据没有来得及保存就会丢失,因此持久化机制来保证数据不会丢失。

Redis的持久化机制有两种,一种是快照(RDB),另一种是AOF日志,RDB是一次全量备份,AOF是增量备份,

快照是内存数据的二进制序列化格式,AOF是内存数据修改指令的文本记录,AOF在长期运行的过程中会逐渐变的很大,

当Redis重启时加载AOF日志进行指令重放,这个过程就会十分漫长,所以AOF会被定期重写,减少AOF的大小。

image.png

快照原理

Redis是单线程程序,即意味着Redis在接收客户端的请求同时,还要进行内存快照,这就引发两个问题,

一是内存快照就需要进行文件IO操作,文件IO操作会阻塞Redis对其他客户端的响应,严重降低Redis的性能,这个问题使用fork多进程解决了,即开一个子进程进行快照,原本的Redis进程继续对外提供服务,

二是子进程持久化的过程中,如果正在备份一个很大的hash结构,此时客户端一个请求过来把这个hash结构删除,这时候持久化怎么办?Redis使用了操作系统的COW(Copy On Write)机制实现持久化,解决了这个问题。

fork(多进程)

开始持久化时,Redis会调用glibc函数fork一个子进程,持久化的任务就交给这个子进程去做,父进程继续处理客户端请求,

子进程产生时,和父进程共享内存里的代码段和数据段,linux为了节省内存资源,会尽量让父子进程共享内存,所以fork子进程的时候,内存基本没有变化,

fork函数会在父子进程同时返回,父进程里返回的是子进程的pid,子进程里则是0,如果操作系统内存不足,pid则是负数,表示fork失败,

下面是一段伪代码,代表父子进程的处理逻辑:

pid = os.fork()
if pid > 0:
    父进程继续处理客户端请求
if pid == 0:
    子进程进行持久化操作
if pid < 0:
    fork失败

子进程在持久化的过程中不会修改内存数据结构,只是对数据进行遍历,然后写入到磁盘中,但是父进程由于要继续对外提供服务,

所以有修改指令时,就要对数据结构进行修改,这时候就需要使用操作系统的COW机制进行数据段页面的分离。

image.png

数据段由多个操作系统页面组成,当父进程对其中一个页面修改时,这个页面就会被复制一份出来,然后对这个复制出来的页面进行修改,

这时候子进程看到的页面是没有变化的,因为父进程并不是在修改他们共享的那个数据页,而是复制后的。

随着父进程对越来越多的页的修改,越来越多的页也就跟着被复制出来,内存就会随着增长,redis中一般冷数据比较多,很少会出现所有页面都被复制出来的情况,

一个页面大小是4KB,一个Redis实例中可能会有成千上万个页面,一般只会发生复制部分页面的情况,最多也不会超出原有内存的2倍大小,

由于子进程看到的数据是没有变化的,这也是叫做快照持久化的原因,因为做备份的时候,它看到的数据永远都是不变的,所以叫快照,不用担心数据被篡改发生的问题。

AOF原理

AOF日志按照顺序存储着对内存数据进行修改的指令记录,假设AOF记录着从Redis启动以来,所有的修改指令记录,

那么可以利用这个AOF日志在另一台redis实例中进行重放,即执行AOF中的所有命令,便可在另一台redis实例中恢复当前实例的内存数据。

Redis收到客户端修改指令后,进行参数校验、逻辑处理,处理完毕后,将该指令记录在AOF日志中,这点不同于leveldb、hbase等存储引擎(它们是先存储日志再处理逻辑)。

在长期运行过程中,修改指令会越来越多,如果实例宕机需要通过AOF重放恢复,那么执行指令的时间就会很漫长,因此需要对AOF进行瘦身,节省空间和时间。

AOF重写

Redis提供了 bgrewriteaof 对AOF日志进行瘦身,即开辟一个子进程对内存进行遍历,转换成一系列的Redis指令,

序列化到一个新的AOF文件中,序列化完毕后再将过程中新发生的增量AOF日志追加到这个日志后面,追加完毕后即可替代老的AOF文件,瘦身完成。

fsync

Redis对AOF日志文件进行写操作时,先将内容写到了内核为日志文件分配的一个内存缓存中,随后内核将缓存中的数据刷到磁盘中,

这意味着,如果内核没来得及将数据刷到磁盘中,机器宕机了,数据就会丢失。

Linux的glibc提供了 fsync(int fd) 函数将指定的文件内容由内核缓存强制刷到磁盘里,只要Redis实时调用 fsync 就可以保证AOF日志不丢失,不过这是一个IO操作,频繁执行会影响性能。

一般在生产环境中,通常将Redis配置成1s进行一次fsync操作,这是在数据安全性和性能之间做的一个折中操作,保证高性能的同时,减少数据丢失,

同时Redis还提供了另外两种策略,一种是永不调用fsync,由操作系统决定何时刷磁盘,一种是来一个指令就刷盘一次,

前者不太安全,后者降低性能,一般这两种都不会使用在生产环境中。

线上运维

通过RDB通过开辟子进程进行快照备份,是一个耗费资源的操作,要对内存进行遍历,同时要对磁盘进行写入,将增加系统的负载,

AOF的fsync比较耗时,会降低redis性能,同时也会增加系统IO负担,因此Redis的主节点通常不进行持久化操作,持久化操作要放在从节点进行,

从节点是备份节点,没有来自客户端请求的压力,操作系统资源往往较为充沛,但是如果出现网络分区,从节点长期没有连上主节点,无法进行数据同步,此时主节点又宕机了,数据将会丢失,

因此生产环境下要做好实时监控,保证网络通常和及时修复,也可以多加几个从节点,降低网络分区概率,保证有一个从节点同步正常,防止数据丢失。

混合持久化

重启Redis时,通过RDB恢复内存状态可能会导致丢失大量数据,通过AOF进行日志重放,如果指令过多,又会导致启动耗时时间较长,

为了解决这两种问题,Redis4.0提供了一个混合持久化,即将RDB文件内容和增量AOF存在一起,AOF不再是全量的日志,而是RDB快照持久化开始到结束这段时间的增量日志,通常会较小,

因此在Redis重启或在其他实例恢复内存状态时,先加载RDB内容进行恢复,再对AOF进行重放,二者综合起来,将会使效率大大提升。

image.png