Redis---- Redis的持久化机制RDB和AOF原理
Posted TheWhc
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Redis---- Redis的持久化机制RDB和AOF原理相关的知识,希望对你有一定的参考价值。
Redis的持久化机制
Redis是内存数据库,为了保证效率所有的操作都是在内存中完成。数据都是缓存在内存中,当你重启系统或者关闭系统,之前缓存在内存中的数据都会丢失再也不能找回。因此为了避免这种情况,Redis需要实现
持久化
将内存中的数据存储起来。
1、Redis如何实现持久化?
-
RDB持久化:能够在指定的时间间隔对数据进行快照存储
-
AOF持久化:记录每次对服务器写的操作,当服务器重启的时候会重新执行这些命令来恢复原始的数据,AOF命令以
redis协议
追加保存每次写的操作到文件末尾。Redis还能对AOF文件进行
后台重写
,使得AOF文件的体积不至于过大。 -
不使用持久化:如果只想让数据在服务器运行的时候存在,可以选择不使用任何持久化方式
-
同时开启RDB和AOF:同时开启两种持久化方式,在这种情况下Redis重启的时候会优先加载AOF文件来恢复原始的数据,因为在通常情况下AOF文件保存的数据集要比RDB文件保存的数据集要完整。
2、RDB持久化
RDB(Redis Database)持久化是把当前内存数据生成快照保存到硬盘的过程,触发RDB持久化过程分为
手动触发
和自动触发
2.1 RBD文件的创建
-
手动触发
对应
save
命令,会阻塞当前Redis服务器,直到RDB过程完成为止,在服务器进程阻塞期间,服务器不能处理任何命令请求。对于内存比较大的实例会造成长时间阻塞,线上环境不建议使用
-
自动触发
对应
bgsave
命令,Redis进程执行fork操作创建子进程,RDB持久化过程由子进程负责,完成后自动结束。在这期间,服务器仍然可以继续处理客户端的命令请求。阻塞只发生在fork阶段,一般时间很短。- 其它触发
bgsave
操作- 如果从节点执行全量复制操作,主节点自动执行
bgsave
生成RDB文件并发送给从节点 - 默认情况下执行shutdown命令时,如果没有开启AOF持久化功能则自动执行
bgsave
- 如果从节点执行全量复制操作,主节点自动执行
- 其它触发
2.2 自动间隔性保存
因为BGSAVE
命令可以在不阻塞服务器进程的情况下执行,所以Redis允许用户通过设置服务器配置的save选项
,让服务每隔一段时间自动执行一次BGSAVE命令。
# save选项的默认条件
# save <seconds> <changes> : 表示xxx秒内数据修改xx次自动触发bgsave
save 900 1
save 300 10
save 60 10000
- save 900 1 : 服务器在900秒之内,对数据库进行了至少1次修改
2.3 RDB文件结构
比如Redis内存只有一条数据,是通过set dingbingfa niubi
写入的。
RDB文件保存的是二进制数据。
2.4 原理
bgsave工作机制
- 执行bgsave命令,Redis父进程判断当前是否存在正在执行的子进 程,如RDB/AOF子进程,如果存在,bgsave命令直接返回。
- 父进程执行fork操作创建子进程,fork操作过程中父进程会阻塞
- 父进程fork完成后,bgsave命令返回“Background saving started”信息并不再阻塞父进程,可以继续响应其他命令
- 子进程创建RDB文件,根据父进程内存生成临时快照文件,完成后对原有文件进行原子替换。
- 进程发送信号给父进程表示完成,父进程更新统计信息
fork原理
fork一个子进程完成持久化工作,由操作系统的进程内存隔离的特征保证时点性,写时复制保证效率,减少客户端阻塞时间。
- 时点性:保证在拷贝的时候,没有新来的命令修改内存。
- 写时复制:fork出子进程并没有立刻将内存进行拷贝,仅仅拷贝了一份映射关系,让父子进程暂时指向同一个内存空间,当父子进程对内存空间进行写操作时,才会真正复制内存,而且是页为单位。
主进程通过 fork
系统调用生成子进程时,操作系统会把主进程的「页表」复制一份给子进程,这个页表记录着虚拟地址和物理地址映射关系,而不会复制物理内存,也就是说,两者的虚拟空间不同,但其对应的物理空间是同一个。
子进程就共享了父进程的物理内存数据了,这样能够节约物理内存资源,页表对应的页表项的属性会标记该物理内存的权限为只读。
当父进程或者子进程在向这个内存发起写操作时,CPU 就会触发缺页中断,这个缺页中断是由于违反权限导致的,然后操作系统会在「缺页异常处理函数」里进行物理内存的复制,并重新设置其内存映射关系,将父子进程的内存读写权限设置为可读写,最后才会对内存进行写操作,这个过程被称为「写时复制(Copy On Write)」。
写时复制顾名思义,在发生写操作的时候,操作系统才会去复制物理内存,这样是为了防止 fork 创建子进程时,由于物理内存数据的复制时间过长而导致父进程长时间阻塞的问题。
当然,操作系统复制父进程页表的时候,父进程也是阻塞中的,不过页表的大小相比实际的物理内存小很多,所以通常复制页表的过程是比较快的。
不过,如果父进程的内存数据非常大,那自然页表也会很大,这时父进程在通过 fork 创建子进程的时候,阻塞的时间也越久。
所以,有两个阶段会导致阻塞父进程:
- 创建子进程的途中,由于要复制父进程的页表等数据结构,阻塞的时间跟页表的大小有关,页表越大,阻塞的时间也越长;
- 创建完子进程后,如果子进程或者父进程修改了共享数据,就会发生
写时复制
,这期间会拷贝物理内存,如果内存越大,自然阻塞的时间也越长;
触发bgsave
机制后,主进程就会创建子进程,此时父子进程共享物理内存,子进程只会对这个内存进行只读,子进程对数据进行快照存储,生成RDB文件
。
但是子进程生成RDB文件过程中,主进程依然可以正常处理命令。
如果主进程修改了已经存在key-value,就会发生写时复制,这里只会复制主进程修改的物理内存数据,没修改物理内存还是与子进程共享的。
所以如果这个阶段修改的是一个 bigkey,也就是数据量比较大的 key-value 的时候,这时复制的物理内存数据的过程就会比较耗时,有阻塞主进程的风险。
2.5 小结
- RDB文件用于保存和还原Redis服务器所有数据库中的所有键值对数据
- SAVE命令由服务器进程直接执行保存操作,所以该命令会阻塞服务器
- BGSAVE命令由子进程执行保存操作,所以该命令不会阻塞服务器
- 服务器状态中会保存所有用save选项设置的保存条件,当任意一个保存条件满足时,服务器会自动执行BGSAVE命令
- RDB文件是一个经过压缩的二进制文件,由多个部分组成
- 对于不同类型的键值对,RDB文件会使用不同的方式来保存它们
3、AOF持久化
AOF(append only file)持久化:以独立日志的方式记录每次写命令,重启时再重新执行AOF文件中的命令达到恢复数据的目的。AOF的主要作用是解决了数据持久化的
实时性
,目前已经是Redis持久化的主流方式。
3.1 AOF持久化工作机制
开启AOF功能需要配置:appendonly yes,默认不开启
3.1.1 命令追加、文件写入、文件同步
AOF持久化功能的实现可以分为命令追加
(append)、文件写入
、文件同步
(sync)三个步骤。
-
命令追加
Redis 执行完写操作命令后,会将命令追加到
server.aof_buf
缓冲区; -
AOF文件的写入与同步
然后通过 write() 系统调用,将 aof_buf 缓冲区的数据写入到 AOF 文件,此时数据并没有写入到硬盘,而是拷贝到了内核缓冲区 page cache,等待内核将数据写入硬盘;
文件的写入与同步: 为了提高文件的写入效率,在现代操作系统中,当用户调用write函数,将一些数据写入到文件的时候,操作系统通常会将写入数据暂时保存在一个内存缓冲区里面,等到缓冲区的空间被填满、或者超过了指定的时限后,才真正将缓冲区中的数据写入到磁盘中。 这种做法提高了效率,但也为写入数据带来了安全问题,因为如果计算机发生停机,那么保存在内存缓冲区里面的写入数据都会丢失, 所以,系统提供了fsync和fdatasync两个同步函数,可以强制让操作系统立即将缓冲区的数据写入到硬盘里面,从而确保写入数据的安全性。
-
具体内核缓冲区的数据什么时候写入到硬盘,由内核决定
(Redis服务器配置的appendfsync选项的值可以决定写回磁盘策略)
Redis写入AOF日志的过程,如图所示:
3.1.2 写回磁盘策略
在
redis.conf
配置文件中的appendfsync
配置项可以有以下3种参数可填:
- Always:总是,每次写操作命令执行完后,同步将 AOF 日志数据写回硬盘;
- Everysec:每秒,每次写操作命令执行完后,先将命令写入到 AOF 文件的内核缓冲区,然后每隔一秒将缓冲区里的内容写回到硬盘;
- No,不由 Redis 控制写回硬盘的时机,转交给操作系统控制写回的时机,也就是每次写操作命令执行完后,先将命令写入到 AOF 文件的内核缓冲区,再由操作系统决定何时将缓冲区内容写回硬盘。
以上三种写回策略都无法完美解决 主进程阻塞(效率) 和 **减少数据丢失(安全性)**问题,因为两个问题是对立的。原因如下:
- Always 策略的话,可以最大程度保证数据不丢失,但是由于它每执行一条写操作命令就同步将 AOF 内容写回硬盘,所以是不可避免会影响主进程的性能;
- Everysec 策略的话,是折中的一种方式,避免了 Always 策略的性能开销,也比 No 策略更能避免数据丢失,当然如果上一秒的写操作命令日志没有写回到硬盘,发生了宕机,这一秒内的数据自然也会丢失。
- No 策略的话,是交由操作系统来决定何时将 AOF 日志内容写回硬盘,相比于 Always 策略性能较好,但是操作系统写回硬盘的时机是不可预知的,如果 AOF 日志内容没有写回硬盘,一旦服务器宕机,就会丢失不定数量的数据。
所以根据自己的业务场景进行选择:
- 如果要高性能,就选择 No 策略;
- 如果要高可靠,就选择 Always 策略;
- 如果允许数据丢失一点,但又想性能高,就选择 Everysec 策略。
写回策略 | 写回时机 | 优点 | 缺点 |
---|---|---|---|
Always | 同步写回 | 可靠性靠、最大程序程度数据不丢失 | 每个写命令都要写回硬盘,性能性能大 |
Everysec | 每秒写回 | 性能适中 | 宕机时会丢失1秒内的数据 |
No | 由操作系统控制写回 | 性能好 | 宕机时丢失的数据可能会更多 |
原理:
- Always 策略就是每次写入 AOF 文件数据后,就执行 fsync() 函数;
- Everysec 策略就会创建一个异步任务来执行 fsync() 函数;
- No 策略就是永不执行 fsync() 函数;
3.2 AOF重写机制
AOF 重写机制是在重写时,读取当前数据库中的所有键值对,然后将每一个键值对用一条命令记录到
新的 AOF 文件
,等到全部记录完后,就将新的 AOF 文件替换掉现有的 AOF 文件。
3.2.1 AOF重写的目的
-
减少AOF文件占用空间
1. 旧的AOF文件含有无效的命令,如:del key1, hdel key2等。重写只保留最终数据的写入命令。 2. 多条命令可以合并,如lpush list a,lpush list b,lpush list c可以直接转化为lpush list a b c。
-
更小的AOF文件可以更快的被Redis加载恢复
3.2.2 AOF后台重写方式
Redis 的重写 AOF 分为手动触发和自动触发
- 手动触发:直接调用
bgrewriteaof
命令,是由后台子进程完成的- 自动触发:根据auto-aof-rewrite-min-size和auto-aof-rewrite-percentage参数确定自动触发时机。
后台子进程的好处:
- 子进程进行 AOF 重写期间,主进程可以继续处理命令请求,从而避免阻塞主进程;
- 子进程带有主进程的数据副本(主进程fork出子进程,跟RDB中的bgsave中的fork子进程一样),这里使用子进程而不是线程,因为如果是使用线程,多线程之间会共享内存,那么在修改共享内存数据的时候,需要通过加锁来保证数据的安全,而这样就会降低性能。而使用子进程,创建子进程时,父子进程是共享内存数据的,不过这个共享的内存只能以只读的方式,而当父子进程任意一方修改了该共享内存,就会发生「写时复制」,于是父子进程就有了独立的数据副本,就不用加锁来保证数据安全。
3.2.3 AOF重写缓冲区
重写 AOF 日志过程中,如果主进程修改了已经存在 key-value,此时这个 key-value 数据在子进程的内存数据就跟主进程的内存数据不一致了,这时要怎么办呢?
为了解决这种数据不一致问题,Redis 设置了一个 AOF 重写缓冲区,这个缓冲区在创建bgrewriteaof
子进程之后开始使用。
在重写 AOF 期间,当 Redis 执行完一个写命令之后,它会同时将这个写命令写入到 AOF 缓冲区和 AOF 重写缓冲区。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-UVJGlmvM-1623683457481)(Redis的持久化机制.assets/20210614230910.png)]
3.3.4 AOF重写流程
-
手动执行
bgrewriteaof
命令 -
父进程
fork
子进程去完成AOF后台重写,然后父进程继续处理客户端发送过来的命令请求 -
当父进程接收到客户端的写命令时
3.1 将执行后的写命令追加到 AOF缓冲区
3.2 将执行后的写命令追加到 AOF重写缓冲区
-
子进程执行AOF重写工作
-
子进程完成AOF重写工作后
5.1 向主进程发送一条信号,信号是进程间通讯的一种方式,且是异步的。
5.2 主进程收到该信号后,会调用一个信号处理函数,将 AOF 重写缓冲区中的所有内容追加到新的 AOF 的文件中,使得新旧两个 AOF 文件所保存的数据库状态一致;
5.3 新的 AOF 的文件进行改名,覆盖现有的 AOF 文件。
在整个 AOF 后台重写过程中,除了发生写时复制会对主进程造成阻塞,还有信号处理函数执行时也会对主进程造成阻塞,在其他时候,AOF 后台重写都不会阻塞主进程。
3.3 小结
- AOF文件通过保存所有修改数据库的写命令请求来记录服务器的数据库状态
- AOF文件中的所有命令都是以
Redis命令请求协议的格式
保存 - 命令请求会先保存到AOF缓冲区里面,之后再定期写入并同步到AOF文件
- appendfsync选项的不同值对AOF持久化功能的安全性以及Redis服务器的性能有很大的影响
- 服务器只要载入并重新执行保存在AOF文件中的命令,就可以还原数据库本来的状态
- AOF重写可以产生一个新的AOF文件,这个新的AOF文件和原有的AOF文件保存的数据库状态一样,体积更小
- 在执行
berewritedaof
命令时,Redis服务器会维护一个AOF重写缓冲区,该缓冲区会在子进程创建AOF文件期间,记录服务器执行的所有写命令。当子进程完成创建新AOF文件的工作之后,服务器会将重写缓冲区中的所有内容追加到新的AOF文件的末尾,使得新旧两个文件所保存的数据库状态一致。最后,服务器用新的AOF文件替换旧的AOF文件,以此来完成AOF文件重写操作
4、RDB 对比 AOF
4.1 RDB优缺点
- 优点
- RDB 是一个非常紧凑的文件,它保存了某个时间点的数据集,非常适用于数据集的备份,比如你可以在每个小时保存一下过去24小时内的数据,同时每天保存过去30天的数据,这样即使出了问题你也可以根据需求恢复到不同版本的数据集。
- RDB 是一个紧凑的单一文件,很方便传送到另一个远端数据中心,非常适用于灾难恢复。
- RDB 在保存 RDB 文件时父进程唯一需要做的就是 fork 出一个子进程,接下来的工作全部由子进程来做,父进程不需要再做其他 IO 操作,所以 RDB 持久化方式可以最大化 Redis 的性能。
- 与AOF相比,在恢复大的数据集的时候,RDB 方式会更快一些
- 缺点
- Redis 要完整的保存整个数据集是一个比较繁重的工作,你通常会每隔5分钟或者更久做一次完整的保存,万一在 Redis 意外宕机,你可能会丢失几分钟的数据。
- RDB 需要经常 fork 子进程来保存数据集到硬盘上,当数据集比较大的时候, fork 的过程是非常耗时的,可能会导致 Redis 在一些毫秒级内不能响应客户端的请求。
4.2 AOF优缺点
- 优点
- 你可以使用不同的 fsync 策略:无 fsync、每秒 fsync 、每次写的时候 fsync .使用默认的每秒 fsync 策略, Redis 的性能依然很好( fsync 是由异步任务进行处理的,主线程会尽力处理客户端请求),一旦出现故障,你最多丢失1秒的数据。
- AOF文件是一个只进行追加的日志文件,所以不需要写入seek,即使由于某些原因(磁盘空间已满,写的过程中宕机等等)未执行完整的写入命令,你也可使用redis-check-aof工具修复这些问题。
- AOF 文件有序地保存了对数据库执行的所有写入操作, 这些写入操作以 Redis 协议的格式保存, 因此 AOF 文件的内容非常容易被人读懂, 对文件进行分析(parse)也很轻松。导出(export) AOF 文件也非常简单:举个例子, 如果你不小心执行了 FLUSHALL 命令, 但只要 AOF 文件未被重写, 那么只要停止服务器, 移除 AOF 文件末尾的 FLUSHALL 命令, 并重启 Redis , 就可以将数据集恢复到 FLUSHALL 执行之前的状态。
- 缺点
- 对于相同的数据集来说,AOF 文件的体积通常要大于 RDB 文件的体积。
- 数据恢复(load)时AOF比RDB慢,通常RDB 可以提供更有保证的最大延迟时间。
4.3 简单对比总结
RDB优点:
- RDB 是紧凑的二进制文件,比较适合备份,全量复制等场景
- RDB 恢复数据远快于 AOF
RDB缺点:
- RDB 无法实现实时或者秒级持久化;
- 新老版本无法兼容 RDB 格式。
AOF优点:
- 可以更好地保护数据不丢失;
- appen-only 模式写入性能比较高;
- 适合做灾难性的误删除紧急恢复。
AOF缺点:
- 对于同一份文件,AOF 文件要比 RDB 快照大;
- AOF 开启后,会对写的 QPS 有所影响,相对于 RDB 来说 写 QPS 要下降;
- 数据库恢复比较慢, 不合适做冷备。
5、参考
低并发编程 :你管这破玩意叫 RDB
小林coding : 宕机了,缓存数据没了。。。
爱笑的架构师 : 一次性将Redis RDB持久化和AOF持久化讲透
以上是关于Redis---- Redis的持久化机制RDB和AOF原理的主要内容,如果未能解决你的问题,请参考以下文章
Redis---- Redis的持久化机制RDB和AOF原理
Redis---- Redis的持久化机制RDB和AOF原理