type
status
date
slug
summary
tags
category
icon
password
1、概述
Redis是一个内存数据库,所有的数据将保存在内存中,这与传统的MySQL、Oracle、SqlServer等关系型数据库直接把数据保存到硬盘相比,Redis的读写效率非常高。但是保存在内存中也有一个很大的缺陷,一旦断电或者宕机,内存数据库中的内容将会全部丢失。为了弥补这一缺陷,Redis提供了把内存数据持久化到硬盘文件,以及通过备份文件来恢复数据的功能,即Redis持久化机制。有两种方式:
持久化方式 | 描述 | 优点 | 缺点 | 适合场景 |
AOF | 每当Redis接受到会修改数据集的命令时,就会把命令以追加的形式添加到AOF文件。 | 数据安全,详细记录到每一条写指令 | 文件体积大,数据恢复速度慢 | 文件小,恢复速度快,适合用于定时快照备份和主从全量数据同步的场景。 |
RDB | 按照指定时间间隔以快照的形式保存了某个时间点的Redis数据。 | 文件体积小,便于快速备份和恢复,启动速度快 | 数据安全性低,隔一段时间才持久化一次,宕机容易造成数据的丢失 | 文件大,但是大部分情况保留了原始的命令操作,适合用于对数据完整性和安全性更高的场景中。 |
另外还有一种混合模式,同时开启 AOF 和 RDB 功能:
- RDB 以一定的频率执行。
- 在两次 RDB 期间,使用 AOF 日志记录这期间的所有命令操作。
- 下一次 RDB 的时候,清空 AOF 日志。
在恢复数据的时候,Redis会优先使用AOF日志来恢复数据,因为AOF保存的文件比RDB文件更完整。
2、AOF
Redis 每执行一条写操作命令,就把该命令以追加的方式写入到一个文件里,然后重启 Redis 的时候,先去读取这个文件里的命令,并且执行它,执行完文件的所有命令后数据库就被还原了,这就是 AOF 功能。注意只会记录写操作命令,读操作命令是不会被记录的。
2.1 AOF文件创建过程
AOF 的操作步骤为:
- 命令追加(append):当AOF持久化功能打开时,服务器在执行完一个写命令之后,会以协议格式将被执行的写命令追加到服务器状态的 aof_buf 缓冲区的末尾。(先写内存,再写日志。这样可以保证只有正确执行的命令才会记录到日志上。)
- 文件写入(write):根据条件将 aof_buf 中的缓存写入到 AOF 文件(仍然在内存中)。
- 文件同步(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 文件。
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重写过程
- 主线程 fork 出一个 bgrewriteaof 子进程,这里的 fork() 使用写时拷贝(copy-on-write)的技术,拷贝出一份数据副本,并建立一个 AOF 重写缓冲区。
- 子线程重写期间,如果服务器收到了写命令,主线程会先把写命令写到 AOF 缓冲区(这部分数据会写到旧的 AOF 文件),然后再写到 AOF 重写缓冲区。这样保证即使重写失败也能保证数据的安全。
- 子进程重写完成后,通知主线程,主线程收到信号后会把 AOF 重写缓冲区的内容写到新的 AOF 文件(不会直接写到新的 AOF 文件里面,而是先写 AOF 缓冲区,再写回新的 AOF 文件),最后把新的 AOF 文件进行改名,覆盖原有的 AOF 文件。
整个过程只有第3步是阻塞主线程的,这将 AOF 重写对性能造成的影响降到了最低。
2.1.3 写时复制(Copy-On-Write)技术
Redis在执行 BGSAVE 命令或者 BGREWRITEAOF 命令时,需要 fork 出子进程,调用了 linux 的 fork() 命令,使用了
COW
技术。- 使用 COW 技术 fork 出子线程的时候,kernel 把父进程中所有的内存页的权限都设为 read-only,然后子进程的地址空间指向父进程,子线程和父线程共享物理内存空间。
- 当父进程写数据的时候,在写入数据的内存页上触发页中断异常,CPU 硬件检测到内存页是 read-only 的,于是触发页异常中断(page-fault),陷入 kernel 的一个中断例程。中断例程中,kernel 就会把触发的异常的页复制一份。这时候主进程再把要修改的数据写入到新的物理页,子进程仍然指向旧的物理页数据(说明子线程备份的是旧数据)。
说明:
- 刚 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 分钟数据。
以最常用的
bgsave
命令为例,执行过程如下:- 主线程 fork 出一个bgsave子进程,这里的 fork() 同样使用写时拷贝(copy-on-write)的技术,拷贝出一份数据副本。
- 子进程把数据写到一个临时的 RDB 文件。
- 当子进程写完新的 RDB 文件后,把旧的 RDB 文件替换掉。
注意:
- 为了避免产生竞争条件,
BGSAVE
执行时,SAVE
命令不能执行。
- 为了避免性能问题,
BGSAVE
和BGREWRITEAOF
不能同时执行。
- RDB 文件的加载工作是在服务器启动时自动执行的,Redis 并没有提供专门用于加载 RDB 文件的命令。
- 调用 rdbLoad 函数载入 RDB 文件时,不能进行任何和数据库相关的操作,不过订阅与发布方面的命令可以正常执行,因为它们和数据库不相关联。
- 如果服务器在启动时, 打开了 AOF 功能, 那么程序优先使用 AOF 文件来还原数据。 只有在 AOF 功能未打开的情况下, Redis 才会使用 RDB 文件来还原数据。这是因为在服务器发生故障时,RDB 丢失的数据会比 AOF 持久化的方式更多,因为 RDB 快照是全量快照的方式,因此执行的频率不能太频繁,否则会影响 Redis 性能,而 AOF 日志可以以秒级的方式记录操作命令,所以丢失的数据就相对更少。
- Author:mcbilla
- URL:http://mcbilla.com/article/ddfadf35-f56a-4957-9c5b-3531c46682f1
- Copyright:All articles in this blog, except for special statements, adopt BY-NC-SA agreement. Please indicate the source!