Redis持久化有两种方式:快照和AOF日志
快照是全量备份,是内存数据的二进制序列化形式,在存储上非常紧凑
AOF是连续的增量备份,是内存数据修改的指令记录文本。AOF在长时间的运行过程中,会变得很大,重启的时候,时间会很长很长。需要定期进行AOF重写,给AOF日志瘦身。
快照原理
Redis 是单线程的,需要同时负责多个客户端套接字的读写操作和内存数据结构逻辑的读写。
在服务线上的同时还需要进行内存快照,内存快照需要进行IO操作,但文件的IO操作不能使用多路复用的API。
这意味着单线程同时需要服务线上的请求还需要进行文件的IO操作,文件的IO操作会严重拖垮服务器的性能 , 且为了不阻塞线上的业务, 需要一边持久化,一边响应客户端的请求,持久化的同时,内存结构发生了变化, 比如一个字典正在持久化,突然来一个请求将它删除了,, 这要怎么办呢?
redis使用操作系统的多进程COW(copy on write),来实现持久化.
fork
redis 在持久化的时候会调用glic的函数fork产生一个子进程,快照持久化会交给子进程进行处理,父进程继续处理客户端请求。子进程刚刚创建的时候,它和父进程共享内存中的代码段和数据段,这是linux系统的机制,为了节约内存,所以尽可能的让它们共享起来。当进程分离的一瞬间,内存的增长几乎没有变化。
子进程做数据持久化,他不会修改现有的内存数据结构,只是对数据进行读取,然后保存到磁盘中。父进程会继续持续处理客户端的请求,然后对于内存数据结构进行修改。
在这个时候就会使用操作系统的COW机制进行数据段页面的分离,数据段时由很多操作系统的页面组成的,当父进程对其中一个页面的数据进行修改,会将被共享的页面复制一份分离出来,然后对这个复制的页面进行修改,这时子进程相应的页面是没有变化的,还是进程产生一瞬间的数据。
随着父进程修改操作的持续进行,产生的共享页面越来越多,内存就会不断增加,但是不会超过原有数据内存的2倍 ,另外redis实例中冷数据占比往往是比较高的,所以很少出现所有的页面都会被分离,被分离的往往只是其中一部分页面。每个页面只有4k, 一个redis实例中一般有成千上万个页面。
子进程因为数据没有变化,她能看到的内存数据在 进程产生的一瞬间 就凝固了,再也不会变化了,这也是为什么叫快照的原因。接下来子进程就可以愉快的进行序列化写入磁盘了。
AOF原理
AOF是存储redis服务器顺序指令集合,AOF只存储对内存进行修改的指令。
假如AOF记录了Redis实例创建以来所有修改性的指令,就可以通过对一个空的redis实例顺序执行AOF中的所有指令,也就是重放,来恢复redis当前 实例的内存数据结构状态。
Redis接收到客户端发来的指令,会进行参数校验,如果没问题, 就会将该指令存储到AOF 日志中,就是先存到磁盘,然后再执行指令。这样遇到突发宕机的情况,对存储的AOF进行重放也会恢复到宕机前的状态。
redis长期运行的情况下,AOF日志会很大。如果宕机,重放AOF是非常耗费时间的,需要对AOF进行瘦身。
AOF重新
bgrewriteaof 指令用于对 AOF 日志进行瘦身,
原理就是开辟一个新的子进程,对内存进行遍历产生转化成新的指令,序列化到新的AOF中,序列化后,再将操作期间发生的增量AOF记录追加到新的AOF记录中,追加完毕后,新的AOF代替旧的AOF日志文件。
fsync
AOF是以文件的形式存在的,程序对AOF入职进行写操作的时候,实际上就是内容写道内核为文件描述分配的一个内存缓存中,然后内核会将脏数据刷回到磁盘中,
这就意味着机器宕机时,AOF内容没有来得及写到磁盘中,会出现日志丢失,怎么办?
linux的glibc提供了fsync函数,将指定的文件强制从内核刷到磁盘中,只要redis调用fsync函数就一定会将文件写道磁盘中,但是他是磁盘IO操作,很慢。如果执行一条指令就fsync一次,效率很低。
所以生产环境通常1s中执行一次fsync指令,尽可能的减少数据少丢失。
运维
快照是通过开启子进程的方式进行的,是一个比较耗费资源的操作。
遍历整个内存,大块写入磁盘 会加重系统负载。
AOF的fsync是一个比较耗时的操作,会降低redis 的性能。
所以通常redis 的主节点是不尽兴持久化操作的,都是从节点进行操作的,因为没有来自客户端的请求,有足够的资源进行处理。
但是如果进行网络分区,需要保持网络 通畅。
混合持久化
快照+自持久化后的AOF日志文件