redis:持久化之AOF增量持久化

AOF日志

AOF(append-only file)持久化也叫做在增量持久化,它仅对数据的变化进行存储,类似于日志文件。

AOF(Append-only file)针对RDB的缺点做了优化:

  • 在使用AOF持久化方式时,Redis会将每一个收到的写操作命令都通过Write函数追加到文件最后,类似于MySQL的binlog。
  • 当Redis重启时会通过重新执行文件中保存的写命令来在内存中重建整个数据库的内容。

AOF日志文件存储的是redis服务器的顺序指令序列,AOF日志只记录内存改变的指令

  • 假设AOF日志记录了自Redis实例创建依赖所有的修改性指令序列,那么就可以对一个空的Redis实例顺序执行所有的指令----也就是“重放”,来恢复Redis当前实例的内存数据结构的状态
  • Redis会在收到客户端修改指令后,进行参数校验、逻辑处理,如果没有问题,就立即将该指令文本存储到AOF日志中,也就是说,先执行指令然后才将日志存盘。这点不同于leveldb、hbase等存储引擎,它们都是先存储日志再做立即处理。

AOF的主要作用是解决了数据持久化的实时性,目前已经是Redis持久化的主流方式

使用AOF

Redis 默认并没有开启 AOF 持久化方式,需要我们自行开启

  • 在 redis.conf 配置文件中将 appendonly no 调整为 appendonly yes,这样就开启了 AOF 持久化
# By default Redis asynchronously dumps the dataset on disk. This mode is
# good enough in many applications, but an issue with the Redis process or
# a power outage may result into a few minutes of writes lost (depending on
# the configured save points).
#
# The Append Only File is an alternative persistence mode that provides
# much better durability. For instance using the default data fsync policy
# (see later in the config file) Redis can lose just one second of writes in a
# dramatic event like a server power outage, or a single write if something
# wrong with the Redis process itself happens, but the operating system is
# still running correctly.
#
# AOF and RDB persistence can be enabled at the same time without problems.
# If the AOF is enabled on startup Redis will load the AOF, that is the file
# with the better durability guarantees.
#
# Please check http://redis.io/topics/persistence for more information.

appendonly no

  • 文件名,默认是appendonly.aof
# The name of the append only file (default: "appendonly.aof")

appendfilename "appendonly.aof"

与 RDB 不同的是 AOF 是以记录操作命令的形式来持久化数据的,我们可以查看以下 AOF 的持久化文件 appendonly.aof

*2
$6
SELECT
$1
0
*3
$3
set
$6
mykey1
$6
你好
*3
$3
set
$4
key2
$5
hello
*1
$8

工作流程

AOF的工作流程:命令写入(append)、文件同步(sync)、文件重写(rewrite)、重启加载(load)
在这里插入图片描述
流程如下:

  • 所有写入命令都会追加到aof_buf(缓冲区)中。
  • AOF缓冲区根据对应的策略向硬盘做同步操作
  • 随着AOF文件越来越大,需要定期对AOF文件进行重写,达到压缩的目的
  • 当redis服务器重启时,可以加载AOF文件进行数据恢复

具体一点:

  • redis的增量持久化,存在于每次处理完命令之后,通过propagate函数触发。

在这里插入图片描述

  • 客户端发出 bgrewriteaof命令。
  • redis主进程fork子进程
  • 父进程继续处理client请求,除了把写命令写入到原来的AOF文件中,同时把收到的写命令缓存到AOF重写缓冲区。这样就能保证如果子进程重写失败的话并不会出问题
  • 子进程根据内存快照,按照命令合并规则写入到新AOF文件中。
  • 当子进程把内存快照写入临时文件中后,子进程发信号通知父进程。然后父进程把缓存的写命令也写入到临时文件。
  • 现在父进程可以使用临时文件替换老的aof文件,并重命名,后面收到的写命令也开始往新的aof文件中追加。

命令写入

AOF命令写入的内容直接是文本协议格式。例如set hello world这条命令,在AOF缓冲区会追加如下文本:

*3\r\n$3\r\nset\r\n$5\r\nhello\r\n$5\r\nworld\r\n
#如果看的不太清除,可以分隔以下
* 3\r\n  $3\r\nset  \r\n $5\r\nhello \r\n $5\r\nworld\r\n

问题:为什么AOF直接采用文本协议模式

  • 文件具有很好的兼容性
  • 开启AOF后,所有写入命令都包含追加操作,直接采用协议格式,避免了二次处理开销
  • 文本协议具有可读性,方便直接修改和处理。

问题:AOF为什么要先执行命令再记日志呢
说到日志,我们比较熟悉的是数据库的写前日志(Write Ahead Log, WAL),也就是说,在实际写数据前,先把修改的数据记到日志文件中,以便故障时恢复。不过,AOF日志正好相反,它是写后日志,“写后”的意思是redis是先执行命令,把数据写入内存,然后才记录日志
在这里插入图片描述
那AOF为什么要先执行命令再记日志呢?要回答这个问题,我们要先知道AOF里记录了什么内容。

传统的数据库日志,比如redo log(重做日志),记录的是修改后的数据,而AOF里记录的是redis收到的每一条命令,这些命令是以文本形式保存的。

在这里插入图片描述
但为了避免额外的检测开销,redis在向AOF里面记录日志的时候,并不会先去对这些命令进行语法检测。是以,如果先记日志再执行命令的话,日志中就有可能记录了错误的命令,redis在使用日志恢复数据时,就有可能出错

而后写日志这种方式,就是先让系统执行命令,只有命令能执行成功,才会被记录到日志中,否则,系统就会直接向客户端报错。所以,redis使用写后日志这一方式的一大好处是,可以避免出现记录错误命令的情况

除此之外,AOF还有一个好处:它是在命令执行后才记录日志,所以不会阻塞当前的写操作

不过,AOF也有两个潜在的风险:

  • 首先,如果刚执行完一个命令,还没有来得及记日志就宕机了,那么这个命令和响应的数据就有丢失的风险。如果此时redis是用作缓存,还可以从后端数据库重新丢数据进行恢复,但是,如果redis是直接用作数据库的话,此时,因为命令没有计入日志,所以就无法用日志进行恢复了。
  • 其次,AOF虽然避免了对当前命令的阻塞,但是可能会给下一个操作带来阻塞风险。这是因为AOF日志也是在主线程中执行的,所以在把日志文件写入磁盘时,磁盘写压力大,就会导致写盘很慢,进而导致后续的操作也无法执行了

这两个风险都是和 AOF 写回磁盘的时机相关的。这也就意味着,如果我们能够控制一个写命令执行完后 AOF 日志写回磁盘的时机,这两个风险就解除了。

问题:为什么要将命令写入到aof_buf缓冲区而不是直接写入到aof文件

  • 我们知道redis是单线程响应,如果每次写入AOF命令都直接追击到磁盘上的AOF文件中,这样频繁的IO开销,redis的性能完全取决于当前硬盘负载。

  • 先写入缓冲区aof_buf中,还有一个好处,Redis可以提供多种缓冲区同步硬盘的策略,在性能和安全性方面做出平衡

  • 虽然这样性能是解决了,但是同时也引入了一个问题,aof_buf 缓存区数据如何同步到 AOF 文件呢?由谁同步呢?这就是我们接下来要聊的一个操作:fsync 操作

文件同步

问题:aof_buf缓冲区数据如何同步到aof文件中

  • aof_buf缓冲区数据写入到aof文件是由linux系统去完成的,由于linux系统调度机制周期比较长,如果系统故障宕机了,意味着一个周期内的数据将全部丢失,这不是我们想要的
    • AOF日志是以文件的形式存在的,当程序对AOF日志文件进行写操作时,实际上是将内容写到了内核为文件描述符分配的一个内存缓存中,然后内核会异步将脏数据刷回到磁盘里的
    • 这就意味着如果机器突然宕机,AOF日志内容可能还没有来得及完全刷到磁盘中,这个时候就会出现日志丢失。那怎么办?
  • 所以 Linux 提供了一个 fsync 命令
    • fsync(fd)函数可以将指定文件(针对单个文件操作)的内容强制从内核缓冲刷到磁盘(强制硬盘同步,fsync 将阻塞直到写入硬盘完成后返回)。
    • 只要redis进程实时调用fsync函数就可以保证AOF日志不丢失。
    • 但是fsync是一个磁盘IO操作,它很慢,如果redis执行一条指令就fsync一次,那么redis的性能会下降
    • 所以在生产环境中,redis通常是每隔1s左右fsync操作,这个1s周期是可以配置的。
    • redis同样提供了另外两种策略,一个是永不调用fsync—让操作系统来决定何时同步磁盘,这样做很不安全;另一个是来一个指令就fsync一次----结果导致非常慢。这两种策略在生产环境中基本不会使用,了解即可。

系统调用write和fsync说明:

  • write:
    • write操作会触发延迟写(delayed write)机制。
    • Linux在内核提供页面缓冲区来提高硬盘IO性能,write操作在写入系统缓冲区之后直接返回。同步硬盘操作依赖于系统调度机制比如:缓冲区页空间写满或者达到特性时间周期
    • 同步系统文件之间,如果此时系统故障宕机,缓冲区数据将丢失
  • fsync:
    • fsync针对单个文件操作,做强制硬盘同步
    • fsync将阻塞直到写入硬盘完成后返回,保证了数据持久化
  • 除了write、fsync、Linux还提供sync、fdatasync操作。

正是由于有fsync这个命令,所以 redis 提供了配置项让我们自行决定何时进行磁盘同步,redis 在 redis.conf 中提供了appendfsync 配置项,有如下三个选项:

# appendfsync always
appendfsync everysec
# appendfsync no
  • always:
    • 每次写入都要同步AOF文件,这样保证不会有数据丢失
    • 但是这样会导致 redis 的吞吐量大大下降,下降到每秒只能支持几百的 TPS ,这违背了 redis 的设计,所以不推荐使用这种方式
  • everysec:
    • 这是 redis 默认的同步机制,每秒同步一次数据
    • 但是它对 redis 的吞吐量没有任何影响,每秒同步一次的话意味着最坏的情况下我们只会丢失 1 秒的数据,
    • 推荐使用这种同步机制,兼顾性能和数据安全
  • no:
    • 不做任何处理,缓存区与 aof 文件同步交给系统去调度
    • 操作系统同步调度的周期不固定,最长会有 30 秒的间隔,这样出故障了就会丢失比较多的数据。

在这里插入图片描述
怎么选择:想要获得高性能,就选择 No 策略;如果想要得到高可靠性保证,就选择Always 策略;如果允许数据有一点丢失,又希望性能别受太大影响的话,那么就选择Everysec 策略

这就是三种磁盘同步策略,但是随着接收的命令越来越多,AOF文件会越来越大,这会带来新的问题:

  • 一是:文件系统本身对文件大小有限制,无法保存过大的问题
  • 二是,如果文件太大,之后再往里面追加命令记录的话,效率也会变低;
  • 三是,AOF中记录的命令要一个个被重新执行,用于故障恢复,如果日志文件太大,整个恢复过程就会非常缓慢,这就会影响到Redis的正常使用

那redis是如何解决这个问题的呢?redis引入了重写机制来解决aof文件过大的问题

文件重写

总结

redis提供了bgrewriteaof指令用于对AOF日志进行瘦身,其原理就是

  • 开辟一个子进程对内存进行遍历,转换成一系列Redis的操作指令,序列化到一个新的AOF日志文件中。
  • 序列号完毕后再将操作期间发生的增量AOF日志追加到这个新的AOF日志文件中,追加完毕后就立即替代旧的AOF日志文件了。
问题提出

AOF机制面临着如下两个问题:

  • AOF文件都是追加的,随着服务器的运行AOF文件会越来越大,体积过大的AOF文件对redis服务器甚至是主机都会有影响
  • 而且在redis重启的时候加载过大的AOF文件需要过多的时间,这都是不友好的

为此,redis引入AOF重写机制压缩文件体积。

  • 通过AOF重写机制来创建一个新的 AOF 文件来代替旧文件。
  • 并且两个文件所保存的数据库状态一样,但新文件不会包含任何冗余命令,所以新文件要比旧文件小得多。
重写的AOF文件为什么可以变小呢

AOF文件重写是把redis进程内的数据转换为写命令同步到新AOF文件的过程。这个重写功能是通过读取服务器当前的数据库状态来实现的。虽然叫做【重写】,但实际上并没有对旧文件进行任何读取修改

那重写的AOF文件为什么可以变小呢?,原因如下:

  • 进程内已经超时的数据不再写入文件
  • 旧的AOF文件含有的无效命令,比如del key1,hdel key2,srem keys,set a 111 等,重写使用进程内数据直接生成,这样新的AOF文件只保留最终的数据写入命令
  • 多条写入命令可以合并为一个
    • 比如lpush list a、lpush list b可以转化为:lpush list a b。
    • 比如旧文件保存了对某个 key 有 4 个 set 命令,经过重写之后,新文件只会记录最后一次对该 key 的 set 命令。因此说新文件不会包含任何冗余命令
    • 为了防止单条命令过大造成客户端缓冲区溢出,对于list、set、hash等类型操作,以64个元素为边界拆分为多条。
触发机制

AOF重写过程可以手动触发和自动触发:

  • 手动触发:直接调用bgrewriteaof命令。
  • 自动触发:根据auto-aof-rewrite-min-size和auto-aof-rewrite-percentage参数确定自动触发时机。
    • auto-aof-rewrite-min-size:表示运行AOF重写时文件最小体积,默认为64MB。
    • auto-aof-rewrite-percentage:代表当前AOF文件空间(aof_current_size)和上一次重写后AOF文件空间(aof_base_size)的比值。
内部流程

AOF重写过程通过后台线程完成,避免了对主线程的阻塞。

  • 因为重写涉及到大量的IO操作,所以redis是用子进程来实现这个功能的,否则将会阻塞主进程。该子进程拥有父进程的数据副本,可以避免在使用锁的情况下,保证数据的安全

  • 所以这里又会涉及到一个问题,子进程在重新过程中,服务器还在继续处理命令请求,新命令可能会对数据库造成修改,这会导致当前数据库状态和重写后的AOF文件,所保存的数据库状态不一致。

  • 为了解决这个问题,redis设置了一个AOF重写缓冲区。在子进程执行AOF重写期间,主进程需要执行以下三个命令:

    • 执行客户端的请求命令
    • 将执行后的写命令追加到 AOF 缓冲区
    • 将执行后的写命令追加到 AOF 重写缓冲区
  • 当子进程结束重写之后,会向主线程发送一个信号,主进程接收到之后会调用信号处理函数执行以下的步骤。

    • 将 AOF 重写缓冲区内容写入新的 AOF 文件中。此时新文件所保存的数据库状态就和当前数据库状态一致了
    • 对新文件进行改名,原子地覆盖现有 AOF 文件,完成新旧文件的替换。
  • 当函数执行完成后,主进程就继续处理客户端命令。

因此,在整个 AOF 重写过程中,只有在执行信号处理函数时才会阻塞主进程,其他时候都不会阻塞
在这里插入图片描述

具体流程如下:
在这里插入图片描述

  • 执行AOF重写请求,如果当前进程正在执行AOF重写,请求不执行并返回如下响应:
    • 如果当前进程正在执行AOF重写,请求不执行并返回响应:​ ERR Background append only file rewriting already in progress
    • 如果当前进程正在执行bgsave操作,重写命令延迟到bgsave完成之后再执行,返回响应:​ Background append only file rewriting scheduled
  • 父进程执行fork创建子进程,开销等同于bgsave过程
    • 主进程fork操作完成后,继续响应其他命令。所有修改命令依然写入AOF缓冲区并根据appendfsync策略同步到硬盘,保证原有AOF机制正确性
    • 由于fork操作运用写时复制技术,子进程只能共享fork操作时的内存数据。由于父进程依然响应命令,Redis使用“AOF重写缓冲区”保存这部分新数据,防止新AOF文件生成期间丢失这部分数据
  • 子进程根据内存快照,按照命令合并规则写入到新的AOF文件。每次批量写入硬盘数据量由配置aof-rewrite-incremental-fsync控制,默认为32MB,防止单次刷盘数据过多造成硬盘阻塞。
  • 新AOF文件写入完成后,子进程发送信号给父进程,父进程更新统计信息,具体见info persistence下的aof_*相关统计。
  • 新AOF文件写入完成后,子进程发送信号给父进程,父进程更新统计信息,具体见info persistence下的aof_*相关统计
    • 父进程把AOF重写缓冲区的数据写入到新的AOF文件。
    • 使用新AOF文件替换老文件,完成AOF重写

文件恢复

流程

在这里插入图片描述
Redis 的数据恢复流程比较简单,优先恢复的是 AOF 文件,如果 AOF 文件不存在时则尝试加载 RDB 文件

流程说明:

  • AOF持久化开启且存在AOF文件时,优先加载AOF文件
  • AOF关闭或者AOF文件不存在时,加载RDB文件,
  • 加载AOF/RDB文件成功后,Redis启动成功
  • AOF/RDB文件存在错误时,Redis启动失败并打印错误信息。

具体使用哪种持久化方式

下面是来自官方的建议:

(1)一般来说,如果对数据的安全性要求非常高的话,应该同时使用两种持久化功能。

如果可以承受数分钟以内的数据丢失,那么可以只使用 RDB 持久化。

(2)有很多用户都只使用 AOF 持久化, 但并不推荐这种方式: 因为定时生成 RDB 快照(snapshot)非常便于进行数据库备份, 并且 RDB 恢复数据集的速度也要比 AOF 恢复的速度要快

两种持久化策略可以同时使用,也可以使用其中一种。如果同时使用的话, 那么Redis 重启时,会优先使用AOF文件来还原数据

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值