乐趣区

关于redis:redis持久化详解

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 重写:

  1. 没有 BGSAVE 命令在进行。
  2. 没有 BGREWRITEAOF 在进行。
  3. 以后 AOF 文件大小大于 server.aof_rewrite_min_size(默认值为 1 MB)。
  4. 以后 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 保留模式,它们别离是:

  1. AOF_FSYNC_NO:不保留。
  2. AOF_FSYNC_EVERYSEC:每一秒钟保留一次。
  3. AOF_FSYNC_ALWAYS:每执行一个命令保留一次。

对于三种 AOF 保留模式,它们对服务器主过程的阻塞状况如下:

  1. 不保留(AOF_FSYNC_NO):写入和保留都由主过程执行,两个操作都会阻塞主过程。
  2. 每一秒钟保留一次(AOF_FSYNC_EVERYSEC):写入操作由主过程执行,阻塞主过程。保留操作由子线程执行,不间接阻塞主过程,但若子线程有 SAVE 操作正在执行,必须期待子线程实现 SAVE,主线程的 WRITE 操作将会阻塞。

[image:D2E0A23E-77BF-4FBF-964B-300CED327180-1128-000010788BEC4F4A/F8F0D62B-6DCF-40DE-AEAB-FA7603094537.png]

  1. 每执行一个命令保留一次(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 深度历险》

退出移动版