持久化

发布时间 2023-12-19 10:22:42作者: 梅丹隆

一、RDB & AOF 简介

Redis 提供了两种持久化策略

  • RDB 持久化机制,会在一段时间内生成指定时间点的数据集快照(snapshot)
  • AOF 持久化机制,记录 server 端收到的每一条写命令,当 server 重启时会进行重放以此来重建之前的数据集。AOF 文件中的命令全部以 Redis 协议的格式来保存,新命令会被追加(append)到文件的末尾。 Redis 还可以在后台对 AOF 文件进行重写(rewrite) ,使得 AOF 文件的体积不会超出保存数据集状态所需的实际大小。
  • 如果你仅使用 Redis 作为缓存加速访问,你可以关闭这两个持久化设置
  • 你也可以同时开启这两个持久化设置,但是在这种情况下,Redis 重启时会使用 AOF 文件来重建数据集,因为 AOF 文件保存的数据往往更加完整

二、详解RDB

1、RDB 创建与载入

  • Redis 提供了 _**SAVE**__**BGSAVE**_ 两个命令来生成 RDB 文件,区别是前者是阻塞的,后者是后台 _**fork**_ 子进程进行不会阻塞主进程处理命令请求
  • 载入 RDB 文件不需要手工运行,而是 _**server**_ 端自动进行,只要启动时检测到 RDB 文件存在 _**server**_ 端便会载入 RDB 文件重建数据集
  • 当然上面简介中已经提到,如果 同时存在 AOF 的话会优先使用 AOF 重建数据集因为其保存的数据更完整。

2、RDB 相关配置

2.1、SAVE POINT save

你可以配置保存点(save point),Redis 如果每 N 秒数据发生了 M 次改变就保存快照文件,例如下面:

# 这个保存点配置表示每60秒,如果数据发生了1000次以上的变动,Redis就会自动保存快照文件
save 60 1000
# 保存点可以设置多个,Redis的配置文件就默认设置了3个保存点
# 格式为:save <seconds> <changes>
# 可以设置多个。
save 900 1 #900秒后至少1个key有变动
save 300 10 #300秒后至少10个key有变动
save 60 10000 #60秒后至少10000个key有变动

2.2、stop-writes-on-bgsave-error yes | no

如果 Redis 执行 RDB 持久化失败(常见于操作系统内存不足),那么 Redis 将不再接受 client 写入数据的请求。当然在实践中,我们通常会将 stop-writes-on-bgsave-error _设置为 _false,同时让监控系统在 Redis 执行 RDB 持久化失败时发送告警,以便介入解决,而不是粗暴地拒绝 client 的写入请求。

2.3、rdbcompression yes | no

当生成 RDB 文件时,Redis 会判断字符串长度 >=20字节则压缩,否则不压缩存储,默认 Redis 会采用 LZF 算法进行数据压缩。

2.4、rdbchecksum yes | no

从版本5的 RDB 的开始,一个 CRC64的校验码会放在文件的末尾。这样更能保证文件的完整性,但是在保存或者加载文件时会损失一定的性能(大概10%)。如果想追求更高的性能,可以把它禁用掉,这样文件在写入校验码时会用 0替代,加载的时候看到0就会直接跳过校验。

3、RDB的优点

  • RDB文件是一个很简洁的单文件,它保存了某个时间点的 Redis 数据集,很适合用于做备份。你可以设定一个时间点对 RDB 文件进行归档,这样就能在需要的时候很轻易的把数据恢复到不同的版本。
  • 基于上面所描述的特性,RDB 文件很适合用于灾备,因为单文件可以很方便地传输到另外的数据中心。
  • RDB的性能很好,需要进行持久化时,主进程会 _**fork**__** **_一个子进程出来,然后把持久化的工作交给子进程,自己不会有相关的 I/O 操作。
  • 比起 AOF,在数据量比较大的情况下,RDB的启动速度更快。

4、RDB的缺点

  • RDB容易造成数据的丢失,当你希望在 Redis 停止工作时尽量减少数据丢失的话,那 RDB 不适用。
    • 假设每5分钟保存一次快照,如果Redis因为某些原因不能正常工作,那么从上次产生快照到 Redis 出现问题这段时间的数据就会丢失了。你可以通过配置不同的 _**save point**_ 来减轻数据丢失的程度,但是越紧凑的_** **__**save point**_ 会越频繁地触发 RDB 生成操作,从而对 Redis 性能产生影响
  • RDB 使用 _**fork**_ 子进程进行数据的持久化,如果数据比较大的话可能就会花费点时间,造成 Redis 停止服务几毫秒,如果数据量很大且 CPU 性能不是很好的时候,停止服务的时间甚至会到一秒。
    • AOF 也需要 _**fork**_ 但是你可以自己调整 rewrite 的频率,它不会造成数据丢失。
    • Linux 系统中,_**fork**_ 会拷贝进程的 _**page table**_
    • 随着进程占用的内存越大,进程的 _**page table**_ 也会越大,那么 _**fork**_ 也会占用更多的时间。
    • 如果 Redis 占用的内存很大 (例如 20 GB),那么在 _**fork**_ 子进程时,会出现明显的停顿现象(无法处理 client 的请求)。
    • 另外,在不同机器上,fork 的性能是不同的,可以参见 Fork time in different systems
  • Linux _**fork**_ 子进程采用的是 _**copy-on-write**_ 的方式。在 Redis 执行 RDB 持久化期间,如果 client 写入数据很频繁,那么将增加 Redis 占用的内存,最坏情况下,内存的占用将达到原先的两倍。

三、详解AOF

1、AOF的实现

  • RDB 持久化数据库键值对来记录数据库状态不同,AOF 是通过保存对数据库的写命令集来记录数据库状态的。
  • AOF 持久化实现可以分为**命令追加(append)****文件写入(write)****文件同步(fsync)**** **三个步骤。
  • _**Append**_ 追加命令到 AOF 缓冲区,_**Write**_ 将缓冲区的内容写入到程序缓冲区,_**Fsync**_ 将程序缓冲区的内容写入到文件。

1.1、命令追加

AOF 持久化功能打开时,_**server**_ 端每执行完一个写命令,会以协议格式将被执行的写命令追加到 _**server**__**redisServer**_ 结构体中的 _**aof_buf**_ 缓冲区末尾。

1.2、文件写入与同步

Redis server 进程是一个**事件循环(event loop)**_**server**_ 每结束一个事件循环之前都会调用 _**flushAppendOnlyFile**_ 函数,考虑是否将 _**aof_buf**_ 缓冲区中的内容吸入和保存到 AOF 文件,而 _**flushAppendOnlyFile**_ 函数的行为由 _**appendfsync**_ 选项来控制

appendfsync 值 flushAppendOnlyFile 行为
always 每个事件循环都将 aof_buf 缓冲区中的内容写入 AOF 文件,并且调用 fsync() 将其同步到磁盘。这可以保证最好的数据持久性,但却会给系统带来极大的开销,其效率是三者中最慢的,但同时安全性也是最高的,即使宕机也只丢失一个事件循环中的数据。
no 每个事件循环都将 aof_buf 缓冲区中的内容写入 AOF 文件,但不对其进行同步,何时同步至磁盘会让操作系统决定。这种模式下 AOF 的写入速度最快,不过因其会在系统缓存中积累一段时间的数据,所以同步时间为三者最长。一旦宕机将会丢失自上一次同步 AOF 文件起所有的数据。
everysec 每个事件循环都将 aof_buf 缓冲区中的内容写入 AOF 文件,Redis 还会每秒在子线程中执行一次 fsync()。在实践中,推荐使用这种设置,一定程度上可以保证数据持久性,又不会明显降低 Redis 性能。

2、AOF重写

  • AOF 持久化并不是没有缺点的,Redis 会不断将接收到的写命令追加到 AOF 文件中,导致 AOF 文件越来越大。
  • 过大的 AOF 文件会消耗磁盘空间,并且导致 Redis 重启时更加缓慢。为了解决这个问题,在适当情况下,Redis 会对 AOF 文件进行重写,去除文件中冗余的命令,以减小 AOF 文件的体积。
  • AOF的重写会执行大量的写入操作
  • Redis是单线程的,所以如果有服务器直接调用重写,服务器就不能处理其他命令了,因此Redis服务器新起了单独一个进程来执行AOF重写。
  • 当然也可以通过 _**BGREWRITEAOF**_ 命令手动重写 AOF 文件。重写后的新 AOF 文件包含了恢复当前数据集所需的最小命令集合。
  • 在子进程执行AOF重写时,服务端接收到客户端的命令之后,先执行客户端发来的命令,然后将执行后的写命令追加到AOF缓冲区中,同时将执行后的写命令追加到AOF重写缓冲区中。
  • 等到子进程完成了重写工作后,会发一个完成的信号给服务器,服务器就将AOF重写缓冲区中的所有内容追加到AOF文件中,然后原子性地覆盖现有的AOF文件。

image.png

3、AOF相关配置

# 你可以在 redis.conf 中通过以下配置开启 AOF 功能
appendonly yes

# 文件存放目录,与RDB共用。默认为当前工作目录。
dir ./

# 默认文件名为appendonly.aof
appendfilename "appendonly.aof"

# fsync 相关配置
# appendfsync always
appendfsync everysec
# appendfsync no

# Redis会记住自从上一次重写后AOF文件的大小(如果自Redis启动后还没重写过,则记住启动时使用的AOF文件的大小)。
# 如果当前的文件大小比起记住的那个大小超过指定的百分比,则会触发重写。
# 同时需要设置一个文件大小最小值,只有大于这个值文件才会重写,以防文件很小,但是已经达到百分比的情况。
auto-aof-rewrite-percentage 100
auto-aof-rewrite-min-size 64mb
# 上面两个配置的作用:当 AOF 文件的体积大于 64MB,并且 AOF 文件的体积比上一次重写之后的体积大了至少一倍,那么 Redis 就会执行 AOF 重写。

# 要禁用自动的日志重写功能,我们可以把百分比设置为0:
auto-aof-rewrite-percentage 0

4、AOF 的优点

  • RDB可靠。你可以制定不同的 _**fsync**_ 策略:_**no**__**everysec**__**always**_。默认是 _**everysec**_。这意味着你最多丢失一秒钟的数据。
  • AOF日志文件是一个纯追加的文件。就算是遇到突然停电的情况,也不会出现日志的定位或者损坏问题。甚至如果因为某些原因(例如磁盘满了)命令只写了一半到日志文件里,我们也可以用 _**redis-check-aof**_这个工具很简单的进行修复。
  • AOF文件太大时,Redis 会自动在后台进行重写。重写很安全,因为重写是在一个新的文件上进行,同时 Redis 会继续往旧的文件追加数据。新文件上会写入能重建当前数据集的最小操作命令的集合。当新文件重写完,Redis 会把新旧文件进行切换,然后开始把数据写到新文件上。
  • AOF 把操作命令以简单易懂的格式一条接一条的保存在文件里,很容易导出来用于恢复数据。例如我们不小心用 _**FLUSHALL**_命令把所有数据刷掉了,只要文件没有被重写,我们可以把服务停掉,把最后那条命令删掉,然后重启服务,这样就能把被刷掉的数据恢复回来。

5、AOF 的缺点

  • 在相同的数据集下,AOF 文件的大小一般会比 RDB 文件大。
  • 在某些 _**fsync**_ 策略下,AOF 的速度会比 RDB 慢。通常 _**fsync**_ 设置为每秒一次就能获得比较高的性能,而在禁止 _**fsync**_ 的情况下速度可以达到 RDB 的水平。
  • 在过去曾经发现一些很罕见的BUG导致使用AOF重建的数据跟原数据不一致的问题。

四、Redis 4.0 混合持久化

  • Redis 4.0 开始支持 RDBAOF 的混合持久化(默认关闭,可以通过配置项 _**aof-use-rdb-preamble**__** **_开启)。
  • 如果把混合持久化打开,AOF 重写的时候就直接把 RDB 的内容写到 AOF 文件开头。
  • 这样做的好处是可以结合 RDBAOF 的优点, 快速加载同时避免丢失过多的数据。
  • 当然缺点也是有的, AOF 里面的 RDB 部分就是压缩格式不再是 AOF 格式,可读性较差