深入理解Redis的持久化机制

2021-03-09 15:24:36 浏览数 (1)

前言

由于redis所有数据一般都在内存中,如果不进行配置持久化,redis一旦发生重启操作,数据全部丢失掉,所以就需要开启redis持久化机制,将数据保存到硬盘中,当redis重启后,底层会读取磁盘文件来进行恢复数据,合理使用持久化机制是成为架构师或运维重要的一步,接下来就来为各位小伙伴介绍redis持久化机制的几种方式

RDB持久化机制

Rdb持久化就是把当前进程数据生成快照保存到磁盘的过程,且默认情况下生成快照保存在名称为dump.rdb的二进制文件中

RDB的持久化配置

代码语言:javascript复制
#持久化策略save 900 1save 300 10save 60 10000
# 如果持久化出错,是否停止主进程写入stop-writes-on-bgsave-error yes
# 导入时是否检查 rdbcompression yes
# 是否压缩rdbchecksum yes
# rdb文件名dbfilename dump.rdb
# 保存rdb文件目录位置dir ./

配置很简单,这里说一下持久化机制策略具体什么意思

  • save 900 1 表示900s(15分钟)内,如果有一条命令是写入,就触发产生一次快照
  • save 300 10 表示300s(5分钟)内,如果有10条命令是写入,就触发产生一次快照
  • save 60 1000 表示60s(1分钟)内,如果有10000条命令是写入,就触发产生一次快照

可能会有小伙伴会有疑问,为什么需要这么多配置?

因为redis每个时段的读写请求肯定不是均衡的,为了平衡性和数据安全,当然我们也可以根据业务,定制自定义规则什么时候情况下触发备份。

stop-writes-on-bgsave-error

这个配置也是相当重要的一项,默认是开启状态,这是当redis进行持久化的过程中如果发生错误,主进程就停止新的写入操作,也是为了保证数据的一致性吧。

简单理解:redis正在创建快照,而创建快照大概需要10s左右,然后主进程这期间还是会继续进行接受写入操作,如果我的异步线程创建快照发生了错误,那么redis主线程就会自动停止主进程的写入操作。

如果想要禁用掉rdb配置,也是非常soeasy的,直接将持久化策略全部注释掉即可

RDB工作原理

触发RDB持久化机制分为两种,第一种:手动触发,第二种:自动触发

RDB重写机制,手动触发

如果有特殊情况,我们也可以手动执行命令生快rbd快照,我们进入客户端执行save或bgsave命令,就可以生成dump.rdb文件,每次执行命令都会将所有redis快照到一个新的ddb文件里,并覆盖掉原来的rdb快照文件。

  • save: 会阻塞主线程写入数据,直到数据同步完成。
  • bgsave: 会产生一个fork子进程,由子进程进行数据同步,就算发生阻塞也是子线程,不会影响主线程的数据写入
代码语言:javascript复制
127.0.0.1:6379> saveOK127.0.0.1:6379> bgsaveBackground saving started

RDB重写机制,自动触发

  • 使用save相关配置,“save m n” 会自动触发bgsave操作
  • 从节点执行全量复制,主节点会同步数据到从节点,会自动触发bgsave操作
  • 执行debug rload命令重新加载redis,会自动触发save操作
  • 执行shutdown操作,如果没开启AOF,也会自动触发bgsave操作

bgsave操作流程

1. 执行bgsave命令,Redis父进程判断当前是否存在正在执行的子进程,如RDB/AOF子进程,如果存在bgsave命令直接返回。

2. 父进程执行fork操作创建子进程,fork操作过程中父进程被阻塞。

3. 父进程fork完成后,bgsave命令返回“* Background saving started by pid xxx”信息,并不再阻塞父进程,可以继续响应其他命令。

4. 父进程创建RDB文件,根据父进程内存生成临时快照文件,完成后对原有文件进行原子替换。根据lastsave命令可以获取最近一次生成RDB的时间,对应info Persistence中的rdb_last_save_time。

5. 进程发送信号给父进程表示完胜,父进程更新统计信息。

save与bgsave对比

命令

save

bgsave

IO类型

同步

异步

是否阻塞redis其它命令

否(在生成子进程执行调用fork函数时会有短暂阻塞)

复杂度

O(n)

O(n)

优点

不会消耗额外内存

不阻塞客户端命令

缺点

阻塞客户端命令

需要fork子进程,消耗内存

RDB优点

  • RDB是一个非常紧凑的文件,它保存了某个时间点得数据集,非常适用于数据集的备份,比如你可以在每个小时报保存一下过去24小时内的数据,同时每天保存过去30天的数据,这样即使出了问题你也可以根据需求恢复到不同版本的数据集.
  • 对于灾难恢复而言,RDB是非常不错的选择。因为我们可以非常轻松的将一个单独的文件压缩后再转移到其它存储介质上。
  • RDB在保存RDB文件时父进程唯一需要做的就是fork出一个子进程,接下来的工作全部由子进程来做,父进程不需要再做其他IO操作,所以RDB持久化方式可以最大化redis的性能.
  • 与AOF相比,在恢复大的数据集的时候,RDB方式会更快一些.(为什么?因为rdb是二进制文件)

RDB缺点

  • 如果你希望在redis意外停止工作(例如电源中断)的情况下丢失的数据最少的话,那么RDB不适合你.虽然你可以配置不同的save时间点(例如每隔5分钟并且对数据集有100个写的操作),但是Redis要完整的保存整个数据集是一个比较繁重的工作,你通常会每隔5分钟或者更久做一次完整的保存,万一在Redis意外宕机,你可能会丢失几分钟的数据
  • RDB 需要经常fork子进程来保存数据集到硬盘上,当数据集比较大的时候,fork的过程是非常耗时的,可能会导致Redis在一些毫秒级内不能响应客户端的请求.如果数据集巨大并且CPU性能不是很好的情况下,这种情况会持续1秒,AOF也需要fork,但是你可以调节重写日志文件的频率来提高数据集的耐久度

AOF持久化机制

快照功能并不是非常耐久(durable):如果 Redis 因为某些原因而造成故障停机, 那么服务器将丢失最近写入、且仍未保存到快照中的那些数据。从 1.1 版本开始, Redis 增加了一种完全耐久的持久化方式:AOF 持久化,将修改的每一条指令记录进文件appendonly.aof中(先写入os cache,每隔一段时间fsync到磁盘)

AOF的持久化配置

代码语言:javascript复制
#是否开启aof,默认未开启appendonly no
#aof文件名称appendfilename "appendonly.aof"
#aof持久化数据同步方式appendfsync everysec
#aof重写期间是否同步no-appendfsync-on-rewrite no
#aof重写触发配置auto-aof-rewrite-percentage 100auto-aof-rewrite-min-size 64mb
#加载aof时如果出错如何处理aof-load-truncated yes
#aof文件重写策略aof-rewrite-incremental-fsync yes

老套路,分析一下重点配置

appendfsync everysec 同步数据方式分为三种

  • always 表示每次有新命令追加到 AOF 文件时就执行一次fsync,非常慢,也非常安全。
  • everysec 表示每秒fsync一次,足够快,并且在故障时只会丢失 1 秒钟的数据。
  • no 表示从不fsync,将数据交给操作系统来处理。更快,也更不安全的选择。

当然,一般情况下都会采用appendfsync everysec配置,这样同步数据速度会很快,也保证了数据安全性,就算丢失数据最多就1秒的数据

no-appendfsync-on-rewrite

当aof在重写阶段,会进行磁盘同步,磁盘同步的是根据同步方式来进行的,如果同步方式设置为everysec的话,则就是每秒磁盘同步一次。

auto-aof-rewrite-min-size

当aof磁盘文件大小达到设定的阈值,就会进行一次aof重写操作,防止文件过大

auto-aof-rewrite-percentage

当aof文件自上一次重写后文件大小增长了100%则再次触发重写(如果百分比设置为0,则不会发生重写)

aof-load-truncated

启动aof之后,redis会默认使用aof文件加载数据,如果发现aof文件损坏,如何设定的值为yes,则会忽略掉改行错误,尽可能加载更多的数据,如果为no,则会直接报错退出,需要注意的是,该参数是适用于redis启动阶段。

aof-rewrite-incremental-fsync

每次批量写入磁盘的数据量由aof-rewrite-incremental-fsync参数控制,默认为32M,避免单次刷盘数据过多造成硬盘阻塞。

AOF工作原理

AOF存储格式

aof文件是以一种resp协议格式数据进行存储的,比如:新增一条命令为“set sunny 666” aof文件记录如下格式

代码语言:javascript复制
*3$3set$5sunny$3666

星号后面的数字代表命令有多少个参数,$号后面的数字代表这个参数有几个字符

如上分析:

*3:代表命令由三个部分组成

$3:代表命令的长度,如上set命令长度就是为3

$5:代表key的长度,如上sunny的长度就是为5

$3:代表value的长度,如上666的长度就是为3

注意,如果执行带过期时间的set命令,aof文件里记录的是并不是执行的原始命令,而是记录key过期的时间戳

比如执行“set lm 123 ex 1000”,对应aof文件里记录如下

代码语言:javascript复制
*3$3set$2lm$3123*3$9PEXPIREAT$2lm$131608730911027

AOF重写机制,手动触发

AOF文件里可能有太多没用指令,所以AOF会定期根据内存的最新数据生成aof文件

  • bgrewriteaof AOF重写redis会fork出一个子进程去做(与bgsave命令类似),不会对redis正常命令处理有太多影响

例如,执行了如下几条命令:

代码语言:javascript复制
127.0.0.1:6379> incr readcount(integer) 1127.0.0.1:6379> incr readcount(integer) 2127.0.0.1:6379> incr readcount(integer) 3127.0.0.1:6379> incr readcount(integer) 4127.0.0.1:6379>

重写前后的区别

代码语言:javascript复制
重写前*2$4incr$9readcount*2$4incr$9readcount*2$4incr$9readcount*2$4incr$9readcount
重写后,是否发现很精简了*3$3SET$9readcount$14

AOF重写机制,自动触发

与auto-aof-rewrite-percentage,auto-aof-rewrite-min-size两个参数有关。

bgrewriteaof 操作流程

1. 执行AOF重写请求。

如果当前进程正在执行bgsave操作,重写命令会等待bgsave执行完后再执行。

2. 父进程执行fork创建子进程。

3. fork操作完成后,主进程会继续响应其它命令。所有修改命令依然会写入到aof_buf中,并根据appendfsync策略持久化到AOF文件中。

4. 因fork操作运用的是写时复制技术,所以子进程只能共享fork操作时的内存数据,对于fork操作后,生成的数据,主进程会单独开辟一块aof_rewrite_buf保存。

5. 子进程根据内存快照,按照命令合并规则写入到新的AOF文件中。每次批量写入磁盘的数据量由aof-rewrite-incremental-fsync参数控制,默认为32M,避免单次刷盘数据过多造成硬盘阻塞。

6. 新AOF文件写入完成后,子进程发送信号给父进程,父进程更新统计信息。

7. 父进程将aof_rewrite_buf(AOF重写缓冲区)的数据写入到新的AOF文件中。

8. 使用新AOF文件替换老文件,完成AOF重写。

实际上,当Redis节点执行完一个命令后,它会同时将这个写命令发送到AOF缓冲区和AOF重写缓冲区。

RDB 和 AOF ,我应该用哪一个?

命令

RDB

AOF

启动优先级

体积

恢复速度

数据安全性

容易丢数据

根据策略决定

注意:如果开启的RBD 和 AOF,两个持久化机制都是执行,重启后,默认会执行AOF恢复数据,因为数据安全性,所以AOF会优先RDB

AOF的优点

  • 使用AOF 会让你的Redis更加耐久: 你可以使用不同的fsync策略:无fsync,每秒fsync,每次写的时候fsync.使用默认的每秒fsync策略,Redis的性能依然很好(fsync是由后台线程进行处理的,主线程会尽力处理客户端请求),一旦出现故障,你最多丢失1秒的数据.
  • 由于该机制对日志文件的写入操作采用的是append模式,因此在写入过程中即使出现宕机现象,也不会破坏日志文件中已经存在的内容。然而如果我们本次操作只是写入了一半数据就出现了系统崩溃问题,不用担心,在Redis下一次启动之前,我们可以通过redis-check-aof工具来帮助我们解决数据一致性的问题。
  • Redis 可以在 AOF 文件体积变得过大时,自动地在后台对 AOF 进行重写:重写后的新 AOF 文件包含了恢复当前数据集所需的最小命令集合。整个重写操作是绝对安全的,因为 Redis 在创建新 AOF 文件的过程中,会继续将命令追加到现有的 AOF 文件里面,即使重写过程中发生停机,现有的 AOF 文件也不会丢失。而一旦新 AOF 文件创建完毕,Redis 就会从旧 AOF 文件切换到新 AOF 文件,并开始对新 AOF 文件进行追加操作。
  • AOF 文件有序地保存了对数据库执行的所有写入操作, 这些写入操作以 Redis 协议的格式保存, 因此 AOF 文件的内容非常容易被人读懂, 对文件进行分析(parse)也很轻松。导出(export) AOF 文件也非常简单:举个例子, 如果你不小心执行了 FLUSHALL 命令, 但只要 AOF 文件未被重写, 那么只要停止服务器, 移除 AOF 文件末尾的 FLUSHALL 命令, 并重启 Redis , 就可以将数据集恢复到 FLUSHALL 执行之前的状态。

AOF的缺点

  • 对于相同的数据集来说,AOF 文件的体积通常要大于 RDB 文件的体积。
  • 根据所使用的 fsync 策略,AOF 的速度可能会慢于 RDB 。在一般情况下, 每秒 fsync 的性能依然非常高, 而关闭 fsync 可以让 AOF 的速度和 RDB 一样快, 即使在高负荷之下也是如此。不过在处理巨大的写入载入时,RDB 可以提供更有保证的最大延迟时间(latency)。

混合持久化机制

重启 Redis 时,我们很少使用 RDB来恢复内存状态,因为会丢失大量数据。我们通常使用 AOF 日志重放,但是重放 AOF 日志性能相对 RDB来说要慢很多,这样在 Redis 实例很大的情况下,启动需要花费很长的时间。Redis 4.0 为了解决这个问题,带来了一个新的持久化选项——混合持久化。

通过如下配置可以开启混合持久化(必须先开启aof):

代码语言:javascript复制
#开启混合模式,默认开启状态aof-use-rdb-preamble yes

如果开启了混合持久化,AOF在重写时,不再是单纯将内存数据转换为RESP命令写入AOF文件,而是将重写这一刻之前的内存做RDB快照处理,并且将RDB快照内容和增量的AOF修改内存数据的命令存在一起,都写入新的AOF文件,新的文件一开始不叫appendonly.aof,等到重写完新的AOF文件才会进行改名,覆盖原有的AOF文件,完成新旧两个AOF文件的替换。

于是在 Redis 重启的时候,可以先加载 RDB 的内容,然后再重放增量 AOF 日志就可以完全替代之前的 AOF 全量文件重放,因此重启效率大幅得到提升。(简单理解:混合模式,当AOF在重写的时候,是以RDB文件内容格式(二进制的形式) 存入到AOF文件中,因为RBD文件恢复速度快,AOF恢复速度慢,这样混合模式就可以大大的提高AOF恢复速度了 注意:如果不是AOF重写的话,新增的内容 还是会以原来的AOF格式追加存储数据 在AOF文件中 ,且默认开始的RBD文件持久化可以关掉了,只要开启了AOF和混合模式的AOF两个配置即可)

混合持久化AOF文件结构如下

Redis数据备份策略:

  1. 写crontab定时调度脚本,每小时都copy一份rdb或aof的备份到一个目录中去,仅仅保留最近48小时的备份
  2. 每天都保留一份当日的数据备份到一个目录中去,可以保留最近1个月的备份
  3. 每次copy备份的时候,都把太旧的备份给删了
  4. 每天晚上将当前机器上的备份复制一份到其他机器上,以防机器损坏

我是黎明大大,我知道我没有惊世的才华,也没有超于凡人的能力,但毕竟我还有一个不屈服,敢于选择向命运冲锋的灵魂,和一个就是伤痕累累也要义无反顾走下去的心。

0 人点赞