看懂Redis持久化原理

网友投稿 728 2022-09-27

看懂Redis持久化原理

看懂Redis持久化原理

Redis为持久化提供了两种方式

RDB:在指定的时间间隔能对你的数据进行快照存储(它是备份当前瞬间 Redis 在内存中的数据记录,通过快照(snapshotting)实现的)。

AOF:记录每次对服务器写的操作,当服务器重启的时候会重新执行这些命令来恢复原始的数据(只追加文件(Append-Only File,AOF),其作用就是当 Redis 执行写命令后,在一定的条件下将执行过的写命令依次保存在 Redis 的文件中,将来就可以依次执行那些保存的命令恢复 Redis 的数据了)。

持久化功能有效地避免因进程退出造成的数据丢失问题, 当下次重启时利用之前持久化的文件即可实现数据恢复。本文将通过下面内容的介绍,希望能够让大家更全面、清晰的认识这两种持久化方式,同时理解这种保存数据的思路,应用于自己的系统设计中。

持久化的配置

为了使用持久化的功能,我们需要先知道该如何开启持久化的功能。

半持久化RDB模式

开启rdb持久化

Redis进行RDB快照的条件由用户在配置文件中自定义,由两个参数构成:时间和改动的键的个数。当在指定的时间内被更改的键的个数大于指定的数值时就会进行快照。在配置文件中已经预置了3个条件:

save 900 1 #900秒内有至少1个键被更改则进行快照;save 300 10 #300秒内有至少10个键被更改则进行快照;save 60 10000 #60秒内有至少10000个键被更改则进行快照默认可以存在多个条件,条件之间是“或”的关系,只要满足其中一个条件,就会进行快照。# 文件名称dbfilename dump.rdb# 文件保存路径dir /home/work/app/redis/data/# 如果持久化出错,主进程是否停止写入stop-writes-on-bgsave-error yes# 是否压缩rdbcompression yes# 启动redis时,是否检查rdb数据库的完整性rdbchecksum yes

配置其实非常简单,这里说一下持久化的时间策略具体是什么意思。

save 900 1 表示900s内如果有1条是写入命令,就触发产生一次快照,可以理解为就进行一次备份

save 300 10 表示300s内有10条写入,就产生快照

下面的类似,那么为什么需要配置这么多条规则呢?因为Redis每个时段的读写请求肯定不是均衡的,为了平衡性能与数据安全,我们可以自由定制什么情况下触发备份。所以这里就是根据自身Redis写入情况来进行合理配置。

#可以看到触发条件时候开始产生快照2268:M 08 May 2020 20:53:00.030 * 1 changes in 900 seconds. Saving...2268:M 08 May 2020 20:53:00.032 * Background saving started by pid 27692769:C 08 May 2020 20:53:00.091 * DB saved on disk2769:C 08 May 2020 20:53:00.092 * RDB: 4 MB of memory used by copy-on-write2268:M 08 May 2020 20:53:00.133 * Background saving terminated with success

stop-writes-on-bgsave-error yes 这个配置也是非常重要的一项配置,这是当备份进程出错时,主进程就停止接受新的写入操作,是为了保护持久化的数据一致性问题。如果自己的业务有完善的监控系统,可以禁止此项配置, 否则请开启。

关于压缩的配置 rdbcompression yes

运维提示: 当遇到坏盘或磁盘写满等情况时, 可以通过config set dir{newDir}在线修改文件路径到可用的磁盘路径, 之后执行bgsave进行磁盘切换, 同样适用于AOF持久化文件。

# mkdir -p /data127.0.0.1:6379> config set dir '/data'OK127.0.0.1:6379> bgsaveBackground saving started# ls /data/dump.rdb

压缩: Redis默认采用LZF算法对生成的RDB文件做压缩处理, 压缩后的文件远远小于内存大小, 默认开启, 可以通过参数config set rdbcompression{yes|no}动态修改。虽然压缩RDB会消耗CPU, 但可大幅降低文件的体积, 方便保存到硬盘或通过网络发送给从节点, 因此线上建议开启。

禁用RDB配置

非常容易,只需要在save的最后一行写上:save ""

save "" # save 900 1# save 300 10# save 60 10000

RDB的原理

半持久化RDB模式是Redis备份默认方式,是通过快照(snapshotting)完成的,当符合在Redis.conf 配置文件中设置的条件时Redis会自动将内存中的所有数据进行快照并存储在硬盘上,完成数据备份。Redis启动后会读取RDB快照文件,将数据从硬盘载入到内存,根据数据量大小与结构和服务器性能不同,通常将一个记录一千万个字符串类型键、大小为1GB的快照文件载入到内存中需花费20~30秒钟。

rdb持久化实现过程 Redis实现快照的过程,Redis使用fork函数复制(写时复制)一份当前进程(父进程)的副本(子进程),父进程继续接收并处理客户端发来的命令,而子进程开始将内存中的数据写入硬盘中的临时文件,当子进程写入完所有数据后会用该临时文件替换旧的RDB文件,至此一次快照操作完成。

除了自动触发快照,还可以手动发送SAVE和BGSAVE命令让Redis执行快照,两个命令的区别在于:save是由主进程进行快照操作,会阻塞住其他请求。bgsave是通过fork子进程进行快照操作。

针对RDB方式的持久化,手动触发可以使用:

save:会阻塞当前 Redis 服务器响应其他命令,直到 RDB 快照生成完成为止,对于内存比较大的实例会造成长时间阻塞,所以线上环境不建议使用。

bgsave:Redis 主进程会 fork 一个子进程,RDB 快照生成有子进程来负责,完成之后,子进程自动结束,bgsave 只会在 fork 子进程的时候短暂的阻塞,这个过程是非常短的,所以推荐使用该命令来手动触发。

127.0.0.1:6379> bgsaveBackground saving started[root@localhost ~]# tail -10f /usr/local/redis/redis_6379.log 1320:M 29 Apr 2020 21:25:26.556 * Background saving started by pid 2109--bgsave命令返回“Background saving started”信息并不再阻塞父进程, 可以继续响应其他命令。2109:C 29 Apr 2020 21:25:26.580 * DB saved on disk2109:C 29 Apr 2020 21:25:26.581 * RDB: 4 MB of memory used by copy-on-write1320:M 29 Apr 2020 21:25:26.581 * Background saving terminated with success

而自动触发的场景主要是有以下几点:

1.根据我们的 save m n 配置规则自动触发

2.从节点全量复制时,主节点发送rdb文件给从节点完成复制操作,主节点会触发 bgsave

3.执行 debug reload 时

4.执行 shutdown时,如果没有开启aof,也会触发

由于 save 基本不会被使用到,我们重点看看 bgsave 这个命令是如何完成RDB的持久化的,bgsave 命令大概有以下几个步骤:

1) 执行bgsave命令, Redis父进程判断当前是否存在正在执行的子进程, 如RDB/AOF子进程, 如果存在bgsave命令直接返回。 2) 父进程执行fork操作创建子进程, fork操作过程中父进程会阻塞, 通过info stats命令查看latest_fork_usec选项, 可以获取最近一个fork操作的耗时, 单位为微秒。

[root@localhost ~]# /usr/local/redis//bin/redis-cli bgsaveBackground saving started[root@localhost ~]# /usr/local/redis/bin/redis-cli info stats | grep latest_fork_useclatest_fork_usec:34474

3) 父进程fork完成后, bgsave命令返回“Background saving started”信息并不再阻塞父进程, 可以继续响应其他命令。 4) 子进程创建RDB文件, 根据父进程内存生成临时快照文件, 完成后对原有文件进行原子替换。 执行lastsave命令可以获取最后一次生成RDB的时间, 对应info统计的rdb_last_save_time选项。

[root@localhost ~]# /usr/local/redis/bin/redis-cli info | grep rdb_last_saverdb_last_save_time:1588166726[root@localhost ~]# /usr/local/redis/bin/redis-cli lastsave(integer) 1588166726

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

不管是RDB还是AOF都是先写入一个临时文件,然后通过 rename 完成文件的替换工作。

RDB优点:

RDB 快照是某一时刻 Redis 节点内存数据,非常适合做备份,上传到远程服务器或者文件系统中,用于容灾备份。数据恢复时 RDB 要远远快于 AOF。

RDB是一个紧凑压缩的二进制文件, 代表Redis在某个时间点上的数据快照。 非常适用于备份, 全量复制等场景。 比如每6小时执行bgsave备份,并把RDB文件拷贝到远程机器或者文件系统中(如hdfs) , 用于灾难恢复。

有优点同样存在缺点,RDB 的缺点有:

RDB 持久化方式数据没办法做到实时持久化/秒级持久化。我们已经知道了 bgsave 命令每次运行都要执行 fork 操作创建子进程,属于重量级操作,频繁执行成本过高。

RDB 文件使用特定二进制格式保存,Redis 版本演进过程中有多个格式 的 RDB 版本,存在老版本 Redis 服务无法兼容新版 RDB 格式的问题。

如果我们对数据要求比较高,每一秒的数据都不能丢,RDB 持久化方式肯定是不能够满足要求的,那 Redis 有没有办法满足呢,答案是有的,那就是接下来的 AOF 持久化方式。

AOF的配置

# 是否开启aofappendonly yes# 文件名称appendfilename "appendonly.aof"# 同步方式appendfsync everysec# 重写触发配置auto-aof-rewrite-percentage 100 --aof文件大小相比上次文件大小,增长100%时,重写auto-aof-rewrite-min-size 64mb --aof文件至少超过64M时,重写# 加载aof时如果有错如何处理aof-load-truncated yes# 子进程根据内存快照, 按照命令合并规则写入到新的AOF文件。 每次批量写入硬盘数据量由配置aof-rewrite-incremental-fsync控制, 默认为32MB, 防止单次刷盘数据过多造成硬盘阻塞aof-rewrite-incremental-fsync yes#AOF重写时会消耗大量硬盘IO, 可以开启配置no-appendfsync-onrewrite, 默认关闭。 表示在AOF重写期间不做fsync操作。no-appendfsync-on-rewrite no

还是重点解释一些关键的配置:

​​appendfsync everysec​​ 它其实有三种模式:

always:把每个写命令都立即同步到aof,很慢,但是很安全everysec:每秒同步一次,是折中方案no:redis不处理交给OS来处理,非常快,但是也最不安全

一般情况下都采用 everysec 配置,这样可以兼顾速度与安全,最多损失1s的数据。

​​aof-load-truncated yes​​​ 如果该配置启用,在加载时发现aof尾部不正确是,会向客户端写入一个log,但是会继续执行,如果设置为 ​​no​​ ,发现错误就会停止,必须修复后才能重新加载。

AOF的原理

AOF:以独立日志的方式记录每次写命令,重启时再重新执行AOF文件中的命令达到恢复数据的目的 AOF的整个流程大体来看可以分为两步:一步是命令的实时写入(如果是 ​​​appendfsync everysec​​ 配置,会有1s损耗),第二步是对aof文件的重写。

在 AOF 持久化过程中有两个非常重要的操作:一个是将操作命令追加到 AOF_BUF 缓存区,另一个是 AOF_buf 缓存区数据同步到 AOF 文件,接下来我们详细聊一聊这两个操作:

1、为什么要将命令写入到 aof_buf 缓存区而不是直接写入到 aof 文件?

我们知道 Redis 是单线程响应,如果每次写入 AOF 命令都直接追加到磁盘上的 AOF 文件中,这样频繁的 IO 开销,Redis 的性能就完成取决于你的机器硬件了,为了提升 Redis 的响应效率就添加了一层 aof_buf 缓存层, 利用的是操作系统的 cache 技术,这样就提升了 Redis 的性能,虽然这样性能是解决了,但是同时也引入了一个问题,aof_buf 缓存区数据如何同步到 AOF 文件呢?由谁同步呢?这就是我们接下来要聊的一个操作:fsync 操作。

2、aof_buf 缓存区数据如何同步到 aof 文件中?

aof_buf 缓存区数据写入到 aof 文件是有 linux 系统去完成的,由于 Linux 系统调度机制周期比较长,如果系统故障宕机了,意味着一个周期内的数据将全部丢失,这不是我们想要的,所以 Linux 提供了一个 fsync 命令,fsync 是针对单个文件操作(比如这里的 AOF 文件),做强制硬盘同步,fsync 将阻塞直到写入硬盘完成后返回,保证了数据持久化,正是由于有这个命令,所以 redis 提供了配置项让我们自行决定何时进行磁盘同步,redis 在 redis.conf 中提供了appendfsync 配置项,有如下三个选项:

Redis提供了多种AOF缓冲区同步文件策略, 由参数appendfsync控制,# appendfsync always appendfsync everysec # appendfsync no

always:每次有写入命令都进行缓存区与磁盘数据同步,这样保证不会有数据丢失,但是这样会导致 redis 的吞吐量大大下降,下降到每秒只能支持几百的 TPS ,这违背了 redis 的设计,所以不推荐使用这种方式

everysec:这是 redis 默认的同步机制,虽然每秒同步一次数据,看上去时间也很快的,但是它对 redis 的吞吐量没有任何影响,每秒同步一次的话意味着最坏的情况下我们只会丢失 1 秒的数据, 推荐使用这种同步机制,兼顾性能和数据安全

no:不做任何处理,缓存区与 aof 文件同步交给系统去调度,操作系统同步调度的周期不固定,最长会有 30 秒的间隔,这样出故障了就会丢失比较多的数据。 这就是三种磁盘同步策略,但是你有没有注意到一个问题,AOF 文件都是追加的,随着服务器的运行 AOF 文件会越来越大,体积过大的 AOF 文件对 redis 服务器甚至是主机都会有影响,而且在 Redis 重启时加载过大的 AOF 文件需要过多的时间,这些都是不友好的,那 Redis 是如何解决这个问题的呢?Redis 引入了重写机制来解决 AOF 文件过大的问题。

3、Redis 是如何进行 AOF 文件重写的?(随着命令不断写入AOF,文件会越来越大,为了解决这个问题,redis引入了AOF重写机制压缩文件)

因为AOF持久化是通过保存被执行的写命令来记录数据库状态的,所以随着服务器运行时间的流逝,AOF文件中的内容越来越多,文件体积越来越大,如果不加以控制,会对redis服务器甚至宿主计算器造成影响。AOF文件重写是把Redis进程内的数据转 化为写命令同步到新AOF文件的过程。

Redis AOF 文件重写是把 Redis 进程内的数据转化为写命令同步到新 AOF 文件的过程,重写之后的 AOF 文件会比旧的 AOF 文件占更小的体积,这是由以下几个原因导致的:

(1)进程内已经超时的数据不再写入文件

(2)旧的 AOF 文件含有无效命令,如 del key1、hdel key2、srem keys、set a111、set a222等。重写使用进程内数据直接生成,这样新的AOF文件只保 留最终数据的写入命令

(3)多条写命令可以合并为一个,如:lpush list a、lpush list b、lpush list c可以转化为:lpush list a b c。为了防止单条命令过大造成客户端缓冲区溢 出,对于 list、set、hash、zset 等类型操作,以 64 个元素为界拆分为多条。

重写之后的 AOF 文件体积更小了,不但能够节约磁盘空间,更重要的是在 Redis 数据恢复时,更小体积的 AOF 文件加载时间更短。AOF 文件重写跟 RDB 持久化一样分为手动触发和自动触发。

(1)手动触发直接调用 bgrewriteaof 命令就好了,我们后面会详细聊一聊这个命令

(2)自动触发就需要我们在 redis.conf 中修改以下几个配置:

auto-aof-rewrite-percentage 100 auto-aof-rewrite-min-size 64mb auto-aof-rewrite-percentage:代表当前 AOF文件空间 (aof_current_size)和上一次重写后 AOF 文件空间(aof_base_size)的比值,默认是 100%,也就是一样大的时候

auto-aof-rewrite-min-size:表示运行 AOF 重写时 AOF 文件最小体积,默认为 64MB,也就是说 AOF 文件最小为 64MB 才有可能触发重写。 满足了这两个条件,Redis 就会自动触发 AOF 文件重写,AOF 文件重写的细节跟 RDB 持久化生成快照有点类似,下面是 AOF 文件重写流程图:

对于上图有四个关键点补充一下:

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

无论是 RDB 还是 AOF 都是先写入一个临时文件,然后通过 ​​rename​​ 完成文件的替换工作。

RDB 将数据库的快照(snapshot)以二进制的方式保存到磁盘中。AOF 则以协议文本的方式,将所有对数据库进行过写入的命令(及其参数)记录到 AOF 文件,以此达到记录数据库状态的目的。

从持久化中恢复数据

数据的备份、持久化做完了,我们如何从这些持久化文件中恢复数据呢?如果一台服务器上有既有RDB文件,又有AOF文件,该加载谁呢?

其实想要从这些文件中恢复数据,只需要重新启动Redis即可。我们还是通过图来了解这个流程:

启动时会先检查AOF文件是否存在,如果不存在就尝试加载RDB。那么为什么会优先加载AOF呢?因为AOF保存的数据更完整,通过上面的分析我们知道AOF基本上最多损失1s的数据。流程说明:

1) AOF持久化开启且存在AOF文件时, 优先加载AOF文件, 打印日志:* DB loaded from append only file: 5.841 seconds 2) AOF关闭或者AOF文件不存在时, 加载RDB文件, 打印日志:* DB loaded from disk: 5.586 seconds 3) 加载AOF/RDB文件成功后, Redis启动成功。 4) AOF/RDB文件存在错误时, Redis启动失败并打印错误信息。

性能与实践

通过上面的分析,我们都知道RDB的快照、AOF的重写都需要fork,这是一个重量级操作,会对Redis造成阻塞。因此为了不影响Redis主进程响应,我们需要尽可能降低阻塞。

降低fork的频率,比如可以手动来触发RDB生成快照、与AOF重写;控制Redis最大使用内存,防止fork耗时过长;使用更牛逼的硬件;合理配置Linux的内存分配策略,避免因为物理内存不足导致fork失败。

在线上我们到底该怎么做?我提供一些自己的实践经验。

如果Redis中的数据并不是特别敏感或者可以通过其它方式重写生成数据,可以关闭持久化,如果丢失数据可以通过其它途径补回自己制定策略定期检查Redis的情况,然后可以手动触发备份、重写数据单机如果部署多个实例,要防止多个机器同时运行持久化、重写操作,防止出现内存、CPU、IO资源竞争,让持久化变为串行可以加入主从机器,利用一台从机器进行备份处理,其它机器正常响应客户端的命令RDB持久化与AOF持久化可以同时存在,配合使用。

版权声明:本文内容由网络用户投稿,版权归原作者所有,本站不拥有其著作权,亦不承担相应法律责任。如果您发现本站中有涉嫌抄袭或描述失实的内容,请联系我们jiasou666@gmail.com 处理,核实后本网站将在24小时内删除侵权内容。

上一篇:一文详解Spring如何控制Bean注入的顺序
下一篇:Shell if else语句实战案例
相关文章

 发表评论

暂时没有评论,来抢沙发吧~