redis是内存数据库,所以发生宕机后,数据会消失,所以数据的持久化至关重要,接下来我们看一下redis关于持久化是如何设计的。
AOF
关于日志,我们在学习mysql或者zk的时候,都知道WAL日志模式,AOF与之相反,AOF是先将数据写入内存再记录日志,
我们先了解一下AOF具体记录了什么,举个例子,比如我们执行一条命令:set hello world,AOF日志具体是这样记录的:
*3 $3 set $5 hello $5 world,*3表示命令有三个部分 ,$3 表示命令内容有3个字节
redis在记录这些命令时,并不会进行语法检查,所以先让系统执行,执行成功了也代表语法没有问题,而且也不会阻塞
当前的写操作,然后配合 always、everysec、no三种回写策略进行回写记录日志。
always是每次命令执行完紧接着进行日志记录,不可避免的会影响主线程的性能。
everysec是把日志写到缓冲区并还未执行落盘操作便返回,每隔一秒有系统执行落盘动作,所以这里存在数据丢失的风险,
可以看出everysec是redis在性能与数据可靠性之间做的权衡取了个折中。
no策略是命令执行完把日志记录到缓冲区便返回,由操作系统控制落盘时机,这样宕机时数据丢失会比较严重 ,
我们根据具体业务场景选择合适的回写策略
AOF重写
因为一个键值对可能会被反复改动,每改动一次,AOF就得增加一次上述的日志记录,那么整个日志文件会越来越大,
所以redis采取AOF重写机制避免这种情况,AOF重写是这样实现的,对数据库中的所有键值对,比如我们之前set进去的hello world,
日志记录为 set hello world,意思就是说不管你之前改动过多少次,我只记录当前的情况,这样旧日志的多条记录,在新日志里面
旧变成了1条记录,多变一,日志文件自然小了很多,还有一点不同的是,AOF的重写是由主进程程fork出来的一个bgrewriteaaof子进程
执行的,所以不会影响主进程的性能。
当AOF重写时,如果数据发生变动呢?机制是:会将变动的记录写在AOF重写缓冲区,这样等重写完再来根据缓冲区的记录继续写进,这样
便可以使用新的AOF日志代替旧的AOF日志。
接下来着重描述一下一些风险点,就是主进程在fork重写子进程的时候,子进程会拷贝父进程的页表,就是内存的虚实映射,并不会真的去
拷贝实际的物理内存。需要注意的是fork这个bgrewriteaaof子进程时,内核需要创建管理子进程的数据结构,称做PCB,
process control block进程控制块,这个过程是阻塞主进程的,而且如果redis实例的内存越大,那么我们上面提过的页表也会越大,
那么fork时间就越长,就存在阻塞主进程的风险,其次就是我们刚刚提过的在AOF重写时,数据的变动会写时复制,就是会申请一块缓冲区
以记录改动的数据,这就会造成写放大,因为父进程需要读取内存把这份数据再写入缓冲区。假如我们当时写入的数据是bigkey,那么主进程也会因为要申请大的内存空间而发生阻塞。
RDB
RDB是redis内存数据的全量快照,相较于AOF而言,RDB更小而且是二进制文件,AOF记录的是操作记录,RDB记录的是照快照这一
时刻确定性的数据,同样的RDB也存在在照快照的时候,redis实例还是需要正常接收写操作的,那么就会发生数据的更改,
同样的也是利用COW写时复制机制,从而保证数据的正确性。
快照我们总不能频率很高的一直照,总是需要时间间隔,那么当间隔时如果发生宕机,那么数据就会丢失,那么这段期间我们可以利用AOF
记录日志,等下一次快照照完,那么就可以清空这段间隔时间记录的AOF,同时也保证了数据的可靠性。
接下来也描述一下一些关于RDB的风险点,假如我们的业务场景是写多读少的场景,假设读写
比4:1,那么在rdb持久化的过程中,会有大量的写操作,达到80%,假设我们的内存是4G,
已经写了2个G,那么4:1的读写比,导致rdb持久化过程中,cow需要申请的空间为1.6g,
导致整个实例内存几乎吃满,然后再加上这时发生大量的请求进来,那么redis实例将面临OOM
风险,甚至大概率被kill掉。假如云主机开启swap,就会有一部分数据换到磁盘上,那么访问
磁盘效率明显降低,性能下降严重