type
status
date
slug
summary
tags
category
icon
password

1、概述

Redis是一个内存数据库,所有的数据将保存在内存中,这与传统的MySQL、Oracle、SqlServer等关系型数据库直接把数据保存到硬盘相比,Redis的读写效率非常高。但是保存在内存中也有一个很大的缺陷,一旦断电或者宕机,内存数据库中的内容将会全部丢失。为了弥补这一缺陷,Redis提供了把内存数据持久化到硬盘文件,以及通过备份文件来恢复数据的功能,即Redis持久化机制。有两种方式:
持久化方式
描述
优点
缺点
适合场景
AOF
每当Redis接受到会修改数据集的命令时,就会把命令以追加的形式添加到AOF文件。
数据安全,详细记录到每一条写指令
文件体积大,数据恢复速度慢
文件小,恢复速度快,适合用于定时快照备份和主从全量数据同步的场景。
RDB
按照指定时间间隔以快照的形式保存了某个时间点的Redis数据。
文件体积小,便于快速备份和恢复,启动速度快
数据安全性低,隔一段时间才持久化一次,宕机容易造成数据的丢失
文件大,但是大部分情况保留了原始的命令操作,适合用于对数据完整性和安全性更高的场景中。
另外还有一种混合模式,同时开启 AOF 和 RDB 功能:
  1. RDB 以一定的频率执行。
  1. 在两次 RDB 期间,使用 AOF 日志记录这期间的所有命令操作。
  1. 下一次 RDB 的时候,清空 AOF 日志。
notion image
在恢复数据的时候,Redis会优先使用AOF日志来恢复数据,因为AOF保存的文件比RDB文件更完整。

2、AOF

Redis 每执行一条写操作命令,就把该命令以追加的方式写入到一个文件里,然后重启 Redis 的时候,先去读取这个文件里的命令,并且执行它,执行完文件的所有命令后数据库就被还原了,这就是 AOF 功能。注意只会记录写操作命令,读操作命令是不会被记录的。

2.1 AOF文件创建过程

AOF 的操作步骤为:
  1. 命令追加(append):当AOF持久化功能打开时,服务器在执行完一个写命令之后,会以协议格式将被执行的写命令追加到服务器状态的 aof_buf 缓冲区的末尾。(先写内存,再写日志。这样可以保证只有正确执行的命令才会记录到日志上。)
  1. 文件写入(write):根据条件将 aof_buf 中的缓存写入到 AOF 文件(仍然在内存中)。
  1. 文件同步(sync):调用 fsync 或 fdatasync 函数将 AOF 文件保存到磁盘中。
至于这两个函数什么时候调用,也就是说何时把 aof_buf 缓冲区的内容写入 AOF 文件并写回磁盘,AOF提供三种策略:
配置项
写回时机
优点
缺点
Always
每次执行完一个命令之后, write 和 sync 都会被执行。也就是说将aof_buf缓冲区的所有内容写入并同步到AOF文件,每个写命令同步写入磁盘
可靠性高,数据基本不丢失
性能较差
Everysec
默认策略,sync 每隔一秒钟就会执行一次。也就是说将aof_buf缓存区的内容写入AOF文件,每秒同步一次,该操作由一个线程专门负责。
性能适中
宕机丢失1s的数据
No
write 都会被执行, 但 sync 会被略过。也就是说将aof_buf缓存区的内容写入AOF文件,什么时候同步由操作系统来决定
性能好
宕机丢失数据多

2.2 AOF重写

AOF 日志是一个文件,随着执行的写操作命令越来越多,文件的大小会越来越大。
如果当 AOF 日志文件过大就会带来性能问题,比如重启 Redis 后,需要读 AOF 文件的内容以恢复数据,如果文件过大,整个恢复的过程就会很慢。
所以,Redis 为了避免 AOF 文件越写越大,提供了 AOF 重写机制,当 AOF 文件的大小超过所设定的阈值后,Redis 就会启用 AOF 重写机制,来压缩 AOF 文件。
AOF 重写机制原理:AOF 重写不会去读旧的 AOF 文件,而是读取当前数据库中的所有键值对,然后将每一个键值对用一条命令记录到「新的 AOF 文件」,等到全部记录完后,就将新的 AOF 文件替换掉现有的 AOF 文件
notion image

2.1.2 AOF重写缓冲区

重写 AOF 日志过程中,如果主进程修改了已经存在 key-value,此时这个 key-value 数据在子进程的内存数据就跟主进程的内存数据不一致了。为了解决这种数据不一致问题,Redis 设置了一个 AOF 重写缓冲区,这个缓冲区在创建 bgrewriteaof 子进程之后开始使用。
在重写 AOF 期间,当 Redis 执行完一个写命令之后,它会同时将这个写命令写入到 「AOF 缓冲区」和 「AOF 重写缓冲区」
AOF 缓冲区和 AOF 重写缓冲区的区别:
  • AOF 缓冲区:是正常使用 AOF 作为数据落地中间地带,所有的数据先到 AOF 缓冲区再到 AOF 文件中。
  • AOF 重写缓冲区:AOF 重写时,redis还要继续接收数据,这个数据就写到 AOF 重写缓冲区,当 AOF 重写完成时,主进程在把 AOF 重写缓冲区的数据写到 AOF 缓冲区,最后 fsync 到 AOF 文件中。在 AOF 重写过程中,AOF 重写缓存区会一直保持从重写开始到结束所有写命令,而 AOF 缓存区会不断重复【写入—>更新到aof文件—>清除—>写入】这一过程,因而只会保存一部分的命令,所以两者的内容不一致。

2.1.2 AOF重写过程

  1. 主线程 fork 出一个 bgrewriteaof 子进程,这里的 fork() 使用写时拷贝(copy-on-write)的技术,拷贝出一份数据副本,并建立一个 AOF 重写缓冲区。
  1. 子线程重写期间,如果服务器收到了写命令,主线程会先把写命令写到 AOF 缓冲区(这部分数据会写到旧的 AOF 文件),然后再写到 AOF 重写缓冲区。这样保证即使重写失败也能保证数据的安全。
  1. 子进程重写完成后,通知主线程,主线程收到信号后会把 AOF 重写缓冲区的内容写到新的 AOF 文件(不会直接写到新的 AOF 文件里面,而是先写 AOF 缓冲区,再写回新的 AOF 文件),最后把新的 AOF 文件进行改名,覆盖原有的 AOF 文件。
整个过程只有第3步是阻塞主线程的,这将 AOF 重写对性能造成的影响降到了最低。
notion image

2.1.3 写时复制(Copy-On-Write)技术

Redis在执行 BGSAVE 命令或者 BGREWRITEAOF 命令时,需要 fork 出子进程,调用了 linux 的 fork() 命令,使用了 COW 技术。
  1. 使用 COW 技术 fork 出子线程的时候,kernel 把父进程中所有的内存页的权限都设为 read-only,然后子进程的地址空间指向父进程,子线程和父线程共享物理内存空间。
  1. 当父进程写数据的时候,在写入数据的内存页上触发页中断异常,CPU 硬件检测到内存页是 read-only 的,于是触发页异常中断(page-fault),陷入 kernel 的一个中断例程。中断例程中,kernel 就会把触发的异常的页复制一份。这时候主进程再把要修改的数据写入到新的物理页,子进程仍然指向旧的物理页数据(说明子线程备份的是旧数据)。
notion image
说明:
  • 刚 fork 完的时候,父子进程只是分配的虚拟内存空间不同,对应的物理内存空间是相同的。
  • Redis 服务中,子进程只会读取共享内存中的数据,它并不会执行任何写操作,只有父进程会在写入时才会触发 COW 机制。没有触发异常的其他内存页依然是父子进程共享同一个物理页。
  • 子进程写到备份文件的数据是执行了 fork() 函数那一时刻的数据,后面的更新都不会备份。
  • 总体来看,Redis还是读操作比较多。如果子进程存在期间,发生了大量的写操作,那可能就会出现很多的分页错误(页异常中断page-fault),这样就得耗费不少性能在复制上。
  • 而在 rehash 阶段上,写操作是无法避免的。所以 Redis 在 fork 出子进程之后,将负载因子阈值提高,尽量减少写操作,避免不必要的内存写入操作,最大限度地节约内存。
COW 的优点:
  • 可以快速完成线程 fork 工作,不会阻塞主线程太长时间。
  • 可以减少不必要的内存资源分配。
COW 的缺点:
  • 在 fork() 之后,父线程如果产生大量写操作,会产生大量的页中断异常,也会造成性能损失。

3、RDB

AOF 文件记录的是命令操作的日志,而不是实际的数据;而 RDB 快照就是记录某一个瞬间的内存数据,记录的是实际数据。
Redis 提供了两个命令来生成 RDB 文件,分别是 save 和 bgsave,他们的区别就在于是否在「主线程」里执行:
  • 执行了 save 命令,就会在主线程生成 RDB 文件,由于和执行操作命令在同一个线程,所以如果写入 RDB 文件的时间太长,会阻塞主线程
  • 执行了 bgsave 命令,会创建一个子进程来生成 RDB 文件,这样可以避免主线程的阻塞

3.1 RDB文件创建过程

RDB 触发机制分为使用指令手动触发自动触发,最终都会调用 rdbSave 函数来创建 RDB 文件。
  • 手动触发:Redis 提供了两个命令来生成 RDB 文件,分别是 save 和 bgsave,他们的区别就在于是否在「主线程」里执行:
    • 执行了 save 命令,就会在主线程生成 RDB 文件,由于和执行操作命令在同一个线程,所以如果写入 RDB 文件的时间太长,会阻塞主线程
    • 执行了 bgsave 命令,会创建一个子进程来生成 RDB 文件,这样可以避免主线程的阻塞。
  • 自动触发:在redis.conf 进行配置,然后通过服务器周期函数serverCron来触发,该函数每100毫秒执行一次。默认配置如下所示。执行快照是一个比较重的操作,如果频率太频繁,可能会对 Redis 性能产生影响。如果频率太低,服务器故障时,丢失的数据会更多。通常可能设置至少 5 分钟才保存一次快照,这时如果 Redis 出现宕机等情况,则意味着最多可能丢失 5 分钟数据。
    notion image
     
    以最常用的 bgsave 命令为例,执行过程如下:
    1. 主线程 fork 出一个bgsave子进程,这里的 fork() 同样使用写时拷贝(copy-on-write)的技术,拷贝出一份数据副本。
    1. 子进程把数据写到一个临时的 RDB 文件。
    1. 当子进程写完新的 RDB 文件后,把旧的 RDB 文件替换掉。
    注意:
    • 为了避免产生竞争条件, BGSAVE 执行时, SAVE 命令不能执行。
    • 为了避免性能问题, BGSAVEBGREWRITEAOF 不能同时执行。
    • RDB 文件的加载工作是在服务器启动时自动执行的,Redis 并没有提供专门用于加载 RDB 文件的命令。
    • 调用 rdbLoad 函数载入 RDB 文件时,不能进行任何和数据库相关的操作,不过订阅与发布方面的命令可以正常执行,因为它们和数据库不相关联。
    • 如果服务器在启动时, 打开了 AOF 功能, 那么程序优先使用 AOF 文件来还原数据。 只有在 AOF 功能未打开的情况下, Redis 才会使用 RDB 文件来还原数据。这是因为在服务器发生故障时,RDB 丢失的数据会比 AOF 持久化的方式更多,因为 RDB 快照是全量快照的方式,因此执行的频率不能太频繁,否则会影响 Redis 性能,而 AOF 日志可以以秒级的方式记录操作命令,所以丢失的数据就相对更少。
    Redis事件和线程Redis数据库功能
    mcbilla
    mcbilla
    一个普通的干饭人🍚
    Announcement
    type
    status
    date
    slug
    summary
    tags
    category
    icon
    password
    🎉欢迎来到飙戈的博客🎉
    -- 感谢您的支持 ---
    👏欢迎学习交流👏