redis持久化
- RDB 是一个紧凑压缩的二进制文件,代表 Redis 在某个时间点上的数据备份。非常适合备份,全量复制等场景。比如每6小时执行 bgsave 备份,并把 RDB 文件拷贝到远程机器或者文件系统中,用于灾难恢复。
- Redis 加载 RDB 恢复数据远远快于 AOF 的方式
- RDB 方式数据没办法做到实时持久化,而 AOF 方式可以做到。
AOF
AOF日志存储的是redis服务器的顺序指令序列,AOF日志只记录对内存进行修改的指令记录。redis是先执行指令再将日志存盘。
AOF追加
当 AOF 持久化功能处于打开状态时,Redis 在执行完一个写命令之后,会以协议格式(也就是RESP,即 Redis 客户端和服务器交互的通信协议 )将被执行的写命令追加到 Redis 服务端维护的 AOF 缓冲区末尾。
关于AOF的同步策略是涉及到操作系统的write函数和fsync函数的,在《Redis设计与实现》中是这样说明的
为了提高文件写入效率,在现代操作系统中,当用户调用write函数,将一些数据写入文件时,操作系统通常会将数据暂存到一个内存缓冲区里,当缓冲区的空间被填满或超过了指定时限后,才真正将缓冲区的数据写入到磁盘里。
这样的操作虽然提高了效率,但也为数据写入带来了安全问题:如果计算机停机,内存缓冲区中的数据会丢失。为此,系统提供了fsync、fdatasync同步函数,可以强制操作系统立刻将缓冲区中的数据写入到硬盘里,从而确保写入数据的安全性。
AOF重写
AOF 重写是一个有歧义的名字,实际的重写工作是针对数据库的当前值来进行的,程序既不读写、也不使用原有的 AOF 文件。
如果AOF日志太长,需要对AOF日志进行重写(bgrewriteaof
指令)。原理是开辟一个子进程对内存进行遍历,转换成一系列的redis操作指令,序列化到一个新的AOF日志文件中。序列化完毕后再将操作期间发生的增量AOF日志追加到这个新的AOF日志文件中,追加完毕后就立即替代旧的AOF日志文件了,瘦身工作就完成了。
AOF 重写可以由用户通过调用 BGREWRITEAOF 手动触发。
另外, 服务器在 AOF 功能开启的情况下, 会维持以下三个变量:
- 记录当前 AOF 文件大小的变量
aof_current_size
。 - 记录最后一次 AOF 重写之后, AOF 文件大小的变量
aof_rewrite_base_size
。 - 增长百分比变量
aof_rewrite_perc
。
每次当 serverCron 函数执行时, 它都会检查以下条件是否全部满足, 如果是的话, 就会触发自动的 AOF 重写:
- 没有 BGSAVE 命令在进行。
- 没有 BGREWRITEAOF 在进行。
- 当前 AOF 文件大小大于
server.aof_rewrite_min_size
(默认值为 1 MB)。 - 当前 AOF 文件大小和最后一次 AOF 重写后的大小之间的比率大于等于指定的增长百分比。
默认情况下, 增长百分比为 100% , 也即是说, 如果前面三个条件都已经满足, 并且当前 AOF 文件大小比最后一次 AOF 重写时的大小要大一倍的话, 那么触发自动 AOF 重写。
redis在子进程中执行AOF后台重写(bgrewriteaof
)
- 子进程进行 AOF 重写期间,Redis 进程可以继续处理客户端命令请求。
- 子进程带有父进程的内存数据拷贝副本,在不适用锁的情况下,也可以保证数据的安全性。
但是,在子进程进行 AOF 重启期间,Redis接收客户端命令,会对现有数据库状态进行修改,从而导致数据当前状态和 重写后的 AOF 文件所保存的数据库状态不一致。
为此,Redis 设置了一个 AOF 重写缓冲区,这个缓冲区在服务器创建子进程之后开始使用,当 Redis 执行完一个写命令之后,它会同时将这个写命令发送给 AOF 缓冲区和 AOF 重写缓冲区。
当子进程完成 AOF 重写工作之后,它会向父进程发送一个信号,父进程在接收到该信号之后,会调用一个信号处理函数,并执行以下工作:
- 将 AOF 重写缓冲区中的所有内容写入到新的 AOF 文件中,保证新 AOF 文件保存的数据库状态和服务器当前状态一致。
- 对新的 AOF 文件进行改名,原子地覆盖现有 AOF 文件,完成新旧文件的替换
- 继续处理客户端请求命令。
在整个 AOF 后台重写过程中,只有信号处理函数执行时会对 Redis 主进程造成阻塞,在其他时候,AOF 后台重写都不会阻塞主进程。
AOF保存模式
当程序对AOF日志文件进行写操作时,实际上是将内容写到了内核为文件描述符分配的一个内核缓存中,然后内核会异步将脏数据刷回到磁盘中。linux的glibc提供了fsync(int fd)
函数可以将指定文件的内容强制从内核缓存刷到磁盘。如果每执行一条指令就fsync一次,即执行一次文件io操作,那么会很慢,所以通常是每隔1s左右执行一次,这是在数据安全性和性能之间做的一个折衷。
Redis 目前支持三种 AOF 保存模式,它们分别是:
AOF_FSYNC_NO
:不保存。AOF_FSYNC_EVERYSEC
:每一秒钟保存一次。AOF_FSYNC_ALWAYS
:每执行一个命令保存一次。
对于三种 AOF 保存模式, 它们对服务器主进程的阻塞情况如下:
- 不保存(
AOF_FSYNC_NO
):写入和保存都由主进程执行,两个操作都会阻塞主进程。 - 每一秒钟保存一次(
AOF_FSYNC_EVERYSEC
):写入操作由主进程执行,阻塞主进程。保存操作由子线程执行,不直接阻塞主进程,但若子线程有SAVE操作正在执行,必须等待子线程完成SAVE,主线程的WRITE操作将会阻塞。
[image:D2E0A23E-77BF-4FBF-964B-300CED327180-1128-000010788BEC4F4A/F8F0D62B-6DCF-40DE-AEAB-FA7603094537.png]
- 每执行一个命令保存一次(
AOF_FSYNC_ALWAYS
):和模式 1 一样。
RDB
copy on write,写时复制
redis是单线程程序,这个线程要同时负责多个客户端套接字的并发读写操作和内存数据结构的逻辑读写。在服务线上请求的同时,redis还需要进行内存快照,内存快照要求redis必须进行文件io操作。这意味着单线程在服务线上请求的同时,还要进行文件io操作,文件io操作会拖累redis的性能。另一个问题是持久化的同时,内存数据结构还在改变,比如一个大型hash字典正在持久化,结果一个请求过来把它删掉了,此时还没持久化完。
redis使用了多进程的COW来实现快照的持久化。
由维基百科摘录:
写入时复制(英语:Copy-on-write,简称COW)是一种计算机程序设计领域的优化策略。其核心思想是,如果有多个调用者(callers)同时请求相同资源(如内存或磁盘上的数据存储),他们会共同获取相同的指针指向相同的资源,直到某个调用者试图修改资源的内容时,系统才会真正复制一份专用副本(private copy)给该调用者,而其他调用者所见到的最初的资源仍然保持不变。这过程对其他的调用者都是透明的(transparently)。此作法主要的优点是如果调用者没有修改该资源,就不会有副本(private copy)被建立,因此多个调用者只是读取操作时可以共享同一份资源。
fork
redis在持久化时会调用glibc的函数fork出一个子进程,快照持久化完全交给子进程来处理,父进程继续处理客户端的请求。子进程刚刚产生的时候,它和父进程共享内存里的代码段和数据段。这是linux的机制,为了节约内存资源尽可能让它们共享起来。在进程分离的一瞬间,内存增长几乎没有明显的变化。
子进程做数据持久化,不会修改现有内存数据结构,只是对数据结构进行遍历读取,然后写盘。但父进程需要持续服务客户端的请求,然后对内存数据结构进行不间断的修改。
这时会使用操作系统的cow机制进行数据段页面的分离。
Copy On Write技术实现原理:
fork()之后,kernel把父进程中所有的内存页的权限都设为read-only,然后子进程的地址空间指向父进程。当父子进程都只读内存时,相安无事。当其中某个进程写内存时,CPU硬件检测到内存页是read-only的,于是触发页异常中断(page-fault),陷入kernel的一个中断例程。中断例程中,kernel就会 把触发的异常的页复制一份,于是父子进程各自持有独立的一份。
随着父进程修改操作的持续进行,越来越多的共享页面被分离出来,内存就会持续增长,但是也不会超过原有数据内存的2倍大小。由于redis中的冷数据占的比例较高,所有很少会有所有页面都被分离的情况。被分离的往往只有其中一部分页面。每个页面大小只有4kb,一个redis实例里面往往有成千上万个页面。
子进程由于不会修改内存中的数据,它看到的内存数据在进程产生的一瞬间就不再改变。只需要遍历数据,写入磁盘即可。
总结:
Copy On Write技术好处是什么?
- COW技术可减少分配和复制大量资源时带来的瞬间延时。
- COW技术可减少不必要的资源分配。比如fork进程时,并不是所有的页面都需要复制,父进程的代码段和只读数据段都不被允许修改,所以无需复制。
Copy On Write技术缺点是什么?
- 如果在fork()之后,父子进程都还需要继续进行写操作,那么会产生大量的分页错误(页异常中断page-fault),这样就得不偿失。
注意
- rdbSave 会将数据库数据保存到 RDB 文件,并在保存完成之前阻塞调用者。
- SAVE 命令直接调用 rdbSave ,阻塞 Redis 主进程; BGSAVE 用子进程调用 rdbSave ,主进程仍可继续处理命令请求。
- SAVE 执行期间, AOF 写入可以在后台线程进行, BGREWRITEAOF 可以在子进程进行,所以这三种操作可以同时进行。
- 为了避免产生竞争条件, BGSAVE 执行时, SAVE 命令不能执行。
- 为了避免性能问题, BGSAVE 和 BGREWRITEAOF 不能同时执行。
运维
RDB是通过开启子进程的方式进行的,比较耗费资源。通常redis的主节点不会进行持久化操作,持久化操作主要在从节点进行。从节点是备份节点,没有来自客户端请求的压力,它的操作系统资源往往比较充沛。
混合持久化
rdb恢复内存状态会丢失大量数据,aof日志重放有很慢。
将rdb文件和aof日志存在一起。aof日志不再是全量的日志,而是自持久化开始到持久化结束这段时间发生的增量aof日志,通常这部分aof日志很小。于是在redis重启时,可以先加载rdb的内容,然后重放增量aof日志,就可以完全替代之前的aof全量日志重放,重启效率得到大幅提升。
参考文章
https://redisbook.readthedocs...
https://redisbook.readthedocs...
https://juejin.im/post/684490...
《redis深度历险》