redis是一个内存数据库,数据保存在内存中,但是我们都知道内存的数据变化是很快的,也容易发生丢失。Redis为我们提供两种持久化的机制,分别是快照RDB和AOF。注意:在redis4.0时候新增了混合持久化。将rbd的内容和增量和aof放在一起。这里的aof是rbd开始到结束之间增量的日志。
快照(rbd):
执行快照操作Redis必须进行io读写操作。文件 IO 操作会严重拖垮服务器请求的性能,而且如果在存的过程中突然修改某个值或者删除,就会造成信息的不准确。Redis使用了COW机制(写时复制)来实现持久化。
Cow实现原理:
fork()之后,kernel把父进程中所有的内存页的权限都设为read-only,然后子进程的地址空间指向父进程。当父子进程都只读内存时,相安无事。当其中某个进程写内存时,CPU硬件检测到内存页是read-only的,于是触发页异常中断(page-fault),陷入kernel的一个中断例程。中断例程中,kernel就会把触发的异常的页复制一份,于是父子进程各自持有独立的一份。
Cow好处:
1.COW技术可减少分配和复制大量资源时带来的瞬间延时。
2.COW技术可减少不必要的资源分配。比如fork进程时,并不是所有的页面都需要复制,父进程的代码段和只读数据段都不被允许修改,所以无需复制。
Cow缺点:
1.如果在fork()之后,父子进程都还需要继续进行写操作,那么会产生大量的分页错误(页异常中断page-fault,就需要将触发的异常的内存页复制一份),这样就得不偿失。
Reids中的cow:
Redis在持久化时,如果是采用BGSAVE命令或者BGREWRITEAOF的方式,那Redis会fork出一个子进程来读取数据,从而写到磁盘中。
总体来看,Redis还是读操作比较多。如果子进程存在期间,发生了大量的写操作,那可能就会出现很多的分页错误(页异常中断page-fault),这样就得耗费不少性能在复制上。
而在rehash阶段上,写操作是无法避免的。所以Redis在fork出子进程之后,将负载因子阈值提高,尽量减少写操作,避免不必要的内存写入操作,最大限度地节约内存
AOF:
(AOF 日志存储的是 Redis 服务器的顺序指令序列,AOF 日志只记录对内存进行修改的指令记录。)持久化功能的实现可以分为命令追加、文件写入、文件同步三个步骤。
命令追加:
当AOF持久化功能打开时,服务器在执行完一个写命令之后,会以协议格式将被执行的写命令追加到服务器状态的aof_buf缓冲区的末尾。
AOF文件的写入与同步:
每当服务器常规任务函数被执行、 或者事件处理器被执行时,flushAppendOnlyFile 函数都会被调用, 这个函数执行以下两个工作:
1.WRITE:根据条件,将 aof_buf 中的缓存写入到 AOF 文件。
2.SAVE:根据条件,调用 fsync 或 fdatasync 函数,将 AOF 文件保存到磁盘中。
两个步骤都需要根据一定的条件来执行, 而这些条件由 AOF 所使用的保存模式来决定, 以下小节就来介绍 AOF 所使用的三种保存模式, 以及在这些模式下, 步骤 WRITE 和 SAVE 的调用条件。
Redis 目前支持三种 AOF 保存模式,它们分别是:
AOF_FSYNC_NO :不保存。
AOF_FSYNC_EVERYSEC:每一秒钟保存一次。
AOF_FSYNC_ALWAYS :每执行一个命令保存一次。
第一种模式:
每次调用 flushAppendOnlyFile 函数,WRITE 都会被执行,但 SAVE 会被略过。以下三种场景的 SAVE 操作都会引起 Redis 主进程阻塞。
1.Redis 被关闭
2.AOF 功能被关闭
3.系统的写缓存被刷新(可能是缓存已经被写满,或者定期保存操作被执行)
第二种模式:
SAVE 原则上每隔一秒钟就会执行一次, 因为 SAVE 操作是由后台子线程调用的, 所以它不会引起服务器主进程阻塞。但是在flushAppendOnlyFile 函数被调用后会出现四种情况:
第三种模式:
每次执行完一个命令之后, WRITE 和 SAVE 都会被执行。另外,因为 SAVE 是由 Redis 主进程执行的,所以在 SAVE 执行期间,主进程会被阻塞,不能接受命令请求。
AOF 文件的读取和数据还原 :
AOF 文件保存了 Redis 的数据库状态, 而文件里面包含的都是符合 Redis 通讯协议格式的命令文本。这也就是说, 只要根据 AOF
文件里的协议, 重新执行一遍里面指示的所有命令, 就可以还原 Redis 的数据库状态了。
Redis 读取 AOF 文件并还原数据库的详细步骤如下:
1.创建一个不带网络连接的伪客户端(fake client)。
2.读取 AOF 所保存的文本,并根据内容还原出命令、命令的参数以及命令的个数。
3.根据命令、命令的参数和命令的个数,使用伪客户端执行该命令。
4.执行 2 和 3 ,直到 AOF 文件中的所有命令执行完毕。
完成第 4 步之后, AOF 文件所保存的数据库就会被完整地还原出来。
注意, 因为 Redis 的命令只能在客户端的上下文中被执行, 而 AOF 还原时所使用的命令来自于 AOF 文件, 而不是网络, 所以程序使用了一个没有网络连接的伪客户端来执行命令。 伪客户端执行命令的效果, 和带网络连接的客户端执行命令的效果, 完全一样。
Aof重写
Redis 在长期运行的过程中,AOF 的日志会越变越长。如果实例宕机重启,重放整个 AOF 日志会非常耗时,就需要对AOF进行重写。
Redis 提供了 bgrewriteaof 指令用于对 AOF 日志进行瘦身。其原理就是开辟一个子进程对内存进行遍历转换成一系列
Redis 的操作指令,序列化到一个新的 AOF 日志文件中。序列化完毕后再将操作期间发生的增量 AOF 日志追加到这个新的 AOF
日志文件中,追加完毕后就立即替代旧的 AOF
日志文件。使用子进程也有一个问题需要解决,就是AOF重写期间如果有新的写命令进来,不能漏掉,那样会数据不一致。
于是Redis服务器设置了一个AOF重写缓冲区 最后流程变为:
1.执行客户端发来的命令
2.将执行的写命令追加到AOF缓冲区
3.将执行后的写命令追加到AOF重写缓冲区
这样一来可以保证:
1.AOF缓冲区的内容会定期被写入和同步到AOF文件,对现有AOF文件的处理工作会照常进行。
2.从创建子进程开始,服务器执行的所有写命令会被记录到AOF重写缓冲区里面
3.当子进程完成AOF重写工作之后,它会向父进程发送一个信号,父进程收到信号后,会调用一个信号处理函数,并执行以下工作:
1.将AOF重写缓冲区中的所有内容写入新的AOF文件中,这时新AOF文件锁保存的数据库状态和服务器当前状态一致
2.对新的AOF文件进行改名,原子性操作地覆盖现有的AOF文件,完成新旧AOF文件的替换。
这个信号函数执行完毕以后,父进程就可以继续像往常一样接受命令请求了,在整个AOF后台重写过程中,只有信号处理函数执行时会对服务器进程造成阻塞,其他时候都可以继续处理请求,这样AOF重写对服务器性能造成的影响降到了最低。