1 介绍

从上一篇的 《深刻理解高性能Redis的本质》 中可以知道, 我们经常在数据库层上加一层缓存(如Redis),来保证数据的访问效率。

这样性能确实也有了大幅度的提升,但是本身Redis也是一层服务,也存在宕机、故障的可能性。

一旦服务挂起,可能生产的后果包括如下几方面:

1、Redis的数据是存在内存中的,所以一旦挂起,内存中的数据会全部丢失。

2、I/O从内存层级迁移到磁盘层级,性能极速下降。

3、原本访问缓存的请求会透过缓存层直接投向数据库,给数据库带来极大的压力,甚至导致雪崩。

所以,缓存层崩溃产生的后果是灾难的。为了避免宕机和宕机后的数据丢失, 为了保证数据的快速恢复,Redis提供了两个持久化数据的能力, AOF(Append Only FIle)日志 和 RDB 快照。

2 关于RDB 内存快照

大规模高并发的分布式场景,经常会遇到问题就是Redis挂起,导致访问失败,而所有的请求透过缓存层投向数据库,给数据库造成极大的压力。

而Redis的数据是存储在高速缓存中,即使我们重启并且恢复使用,缓存池依旧是空的,因为内存被释放了。

重新建立缓存的过程,对数据库也是一个暴击的过程,很可能会导致整个系统调用链的雪崩。参考我的这篇《架构与思维:一次缓存雪崩的灾难复盘

我们知道,Redis 数据都是保存在内存中,能不能将内存中的数据进一步写到磁盘上,Redis 重启的时候就可以把磁盘上的数据快速恢复到内存中。这样,即使Redis宕机重启之后,依然能够正常的提供服务。

但是不能忽略一个问题,Redis和MySQL最大的区别之一就是一个存储在内存,一个持久化在磁盘。但是如果每次数据的变化(新增、修改、删除缓存)都要写内存并同时写磁盘,这样成本太高,内存+磁盘,会让 Redis 性能大大降低。而且还要保证原子性操作,避免内存和磁盘的数据不一致。

2.1 使用内存快照

为了避免实时写入高频操作磁盘带来的负面效应。Redis提供了内存快照策略。

我们知道,Redis 在 执行写(增、删、改)指令过程中,内存中数据会持续的在变化。而内存快照,指的是 Redis 内存中的数据在某一刻的状态。就好比如是拍照一样,你把那一刻的数据都定格下来,持久化到磁盘上。打游戏的同学可以想象存盘。

快照文件我们称之为 RDB 文件,即 Redis DataBase 的缩写。

Redis 通过定时执行 RDB 内存快照,这样就不必每次执行写指令都存盘,只需要在执行内存快照的时候写磁盘。这样既保证Redis的高效读写,还实现了定时持久化,宕机后可快速恢复数据。



如上图,在做数据恢复时,直接将 RDB 文件读入内存完成恢复。

2.2 生成RDB策略

Redis 提供了两种模式来生成 RDB 文件:

  • save: 由主线程来执行,同步阻塞,只有等save完成后,才能进行新操作;
  • bgsave:执行后,会立刻返回OK,同时调用 glibc 的函数fork产生一个子进程用于写入 RDB 文件,快照持久化完全交给子进程来处理。主进程继续执行他自己的工作,非阻塞。

2.2.1 save模式

save模式是主进程执行,非常不建议使用主进程执行的方式,在 《深刻理解高性能Redis的本质》 中,

我们知道他的主操作都是在单线程模型上完成的。所以尽量避免 RDB 文件生成影响主线程的网络I/O和键值对读写。

2.2.2 bgsave模式

上面提到的另外一种方式,fork一个子进程来写RDB文件。

Redis 使用操作系统的多进程写时复制技术 COW(Copy On Write) 来实现快照持久化,这个很重要,具体可以了解下这篇《Copy On Write机制》,写的不错。

Redis 在持久化时会调用 glibc 的函数fork产生一个子进程,由这个子进程来处理快照持久化的动作,子进程可以共享主进程的所有内存数据,所以它读取到主进程的数据之后写入到 RDB 文件。而父进程继续处理客户端的写操作,不受影响。

在创建 RDB 文件时,程序会对数据库中的键进行检查,仅仅将未过期的键保存到新创建的 RDB 文件中。

当主进程执行写指令修改数据的时候,这个数据就会复制一份副本, bgsave 子进程读取这个副本数据写到 RDB 文件,所以主进程就可以直接修改原来的数据。



这既保证了快照的完整性,也允许主进程同时对数据进行修改,避免了对正常业务的影响。

2.2.3 避免过频全量照片

虽然说Redis 使用 bgsave 函数 fork 子进程在后台完成 内存中的数据做快照,没有影响父进程继续处理客户端的各种操作。

但是需注意一点,过于频繁的执行全量的数据快照,必然会导致严重的性能开销:

  • 频繁生成 RDB 文件写入磁盘,磁盘压力过大,效率降低。
  • fork 出来的 bgsave 子进程因为共享主线程的数据,一定程度上会阻塞主线程的运行,主线程的内存越大,阻塞时间越长。

2.3 总结

  • 快照的恢复速度快,但是生成 RDB 文件的频率需要把握一个度,频率过低快照间隔数据较大,丢失的数据就会比较多;频率太快,又会消耗额外开销,降低Redis性能。
  • RDB 建议采用二进制 + 数据压缩的方式写磁盘,文件体积小,数据恢复速度快。

3 AOF 日志

AOF 日志存储了 Redis 服务器的顺序指令序列,AOF 日志只记录对内存进行修改的指令记录。

假设 AOF 日志记录了自 Redis 实例创建以来所有的修改性指令序列,那么就可以通过对一个空的 Redis 实例顺序执行所有的指令。

也就是说,可以通过重放(replay),来建立 Redis 当前实例的内存数据结构。这种模式有没有很熟悉,有没有想到MySQL主从同步时候的relay log。

3.1 日志变更前后对比

AOF记录日志有两种模式,一种是预写式日志,也称写前日志(Write Ahead Log, WAL): 在实际写数据之前,将修改的数据写到日志文件中。

另外一种是写后日志: 先执行写操作,当数据存入内存后,再记录日志。

预写式日志类似 MySQL Innodb 引擎 中的 redo log,修改数据前先记录日志,再修改。

3.2 日志格式

Redis 接收到 set keyName someValue 命令的时候,会先将数据写到内存,Redis 会按照如下格式写入 AOF 文件。

*3:表示当前指令分为三个部分,每个部分都是 $ + 数字开头,后面是3部分的具体内容:指令、键、值。

数字:表示这部分的命令、键、值多占用的字节大小。比如 $3表示这部分包含 3 个字符,也就是 set 的长度。

推荐使用写后日志的模式,避免了额外的检查开销,不需要对执行的命令进行语法检查。如果使用写前日志的话,就需要先检查语法是否有误,否则日志记录了错误的命令,在使用日志恢复的时候就会出错。另外,写后才记录日志,不会阻塞当前的 指令执行。

# set keyName someValue
*3
$3
set
$7 #长度为7
keyName
$9 #长度为9
someValue # 执行 mset key1 1 ,key2 2 ,key33 3
# aof日志如下:
*7 # 本批命令需要往下读7行非 $ 开始的命令
$4 #接着读取4个字节宽度,‘mset’长度为4,记为 $4
mset
$4 #接着读取4个字节宽度,‘key1’长度为4,记为 $4
key1
$1 #接着读取1个字节宽度,‘1’长度为1,记为 $1
1
$4
key2
$1
2
$5 #接着读取的字节宽度,‘$key33’长度为5,记为 $5
key33
$1
3

3.3 可能存在的问题

  • 可能存在丢失:比如Redis 刚执行完指令,还没记录日志宕机了,命令数据就丢了。
  • AOF 避免了当前命令的阻塞,但是AOF 日志是主线程执行,将日志写入磁盘过程中,如果磁盘压力大就会导致执行变慢,降低后续的操作。

3.4 写回策略

上面的问题,在Redis高频读写的时候是必然存在的,想要解决,在写入的时候做一层缓冲就可以了,避免直塞。这时候Redis提供了一种执行策略叫写回策略。

3.4.1 写回策略说明

为了提高日志文件的写入效率,写回策略会做如下变化:

  • 当你调用 write 函数将数据写入到文件时,这时候不是真正的落盘,而是将写入数据暂存在操作系统的内存缓冲区里。
  • 待到缓冲区的空间被填满、或者超过了指定的阈值时候,才真正地将缓冲区中的数据写入到磁盘里面。

    这种做法显然提高了效率,但也为写入数据带来了安全性问题,如果服务器发生了单机,那么保存在内存缓冲区里面的写入数据就会丢失。

    为此,系统提供了fsyncfdatasync两个同步函数,它们可以强制让操作系统立即将缓冲区中的数据写入到硬盘里面,从而确保写入数据的安全性。

    Redis 提供的 AOF 配置项 appendfsync 写回策略直接决定 AOF 持久化功能的效率和安全性,以下是 appendfsync 的3个枚举:
  • always:同步写回,写指令执行完 即将缓冲区内容回写到 AOF 文件。
  • everysec:每秒写回,写指令执行完,日志写到 AOF 文件缓冲区,缓冲区每隔一秒再把内容同步到磁盘。
  • no: 操作系统控制,写执行执行完毕,把日志写到 AOF 文件内存缓冲区,由操作系统决定何时回写到磁盘。

写磁盘会带来性能上的损耗,所以写回的策略要根据实际情况做一个取舍,比如你是偏向性能还是可靠性。

always 同步写回可以做到数据不丢失,但是每次执行写指令都需要写入磁盘,性能最差。

everysec 每秒写回,避免了同步写回的性能开销,但是如果服务发生宕机,会有大约1s时间周期的数据丢失,这种模式是在性能和可靠性之间做了妥协。

no 操作系统控制,执行写指令后就写入 AOF 文件缓冲,再执行后续的写磁盘指令,性能最好,但有可能丢失更多的数据。

3.4.2 写回策略的选择

我们可以根据服务的实际情况来抉择策略,看是偏向高性能还是高可靠。

  • 高性能需求,选择 No 策略
  • 高可靠性保证,就选择 Always 策略
  • 如果能够接受数据存在少量丢失,又希望性能较好的话,就选择 Everysec 策略

4 混合RDF/AOF 方式模式

现实情况下,无论使用RDB或者AOF都差点意思。使用 rdb 来恢复内存状态,势必会丢失一部分数据。 使用 AOF 日志重放,重放对性能有一定的影响,而且在 Redis 实例很大的情况下,需要花费很长的时间。

Redis 4.0 解决了这个问题,才用了一个新的持久化模式——混合持久化,该 混合模式 默认是关闭状态的。

将 RDB 文件的内容和 rdb快照时间点之后的增量的 AOF 日志文件存在一起。这时候 AOF 日志不需要再是全量的日志,而是最近一次快照时间点之后到当下发生的增量 AOF 日志,通常这部分 AOF 日志很小。

所以执行有如下顺序:

  • 查找rdb内容,如果存在先加载 rdb内容再 重放剩余的 aof。
  • 没有rdb内容,直接以aof格式重放整个文件。

    这样快照就不用频繁的执行,同时由于 AOF 只需要记录最近一次快照之后的数据,不需要记录所有的操作,避免了出现单次重放文件过大的问题。

5 总结

  • RDB提供了快照模式,记录某个时间的Redis内存状态。RDB设计了 bgsave 和写时复制,尽可能避免执行快照期间对读写指令的影响,但是频繁快照会给磁盘带来压力以及 fork 阻塞主线程。需把握频率。
  • AOF 日志存储了 Redis 服务的顺序指令序列,通过重放(replay)指令来写入日志文件,并通过写回策略来避免高频读写给Redis带来压力。
  • RDB快照的照片时间间隔,必然会带来数据缺失,如果允许分钟级别的数据丢失,可以只使用 RDB。
  • 如果只用 AOF,写回策略优先使用 everysec 的配置选项,因为它在可靠性和性能之间取了一个平衡。
  • 数据不能丢失时,内存快照和 AOF 的混合使用是一个很好的选择。

Redis系列2:数据持久化提高可用性的更多相关文章

  1. Redis进阶:数据持久化,安全,在PHP中使用

    一.redis数据持久化 由于redis是一个内存数据库,如果系统遇到致命问题需要关机或重启,内存中的数据就会丢失,这是生产环境所不能允许的.所以redis提供了数据持久化的能力. redis提供了两 ...

  2. Docker深入浅出系列 | 容器数据持久化

    Docker深入浅出系列 | 容器数据持久化 Docker已经上市很多年,不是什么新鲜事物了,很多企业或者开发同学以前也不多不少有所接触,但是有实操经验的人不多,本系列教程主要偏重实战,尽量讲干货,会 ...

  3. Redis系列文章-数据结构篇

    Redis系列文章 前言: 工作原因,在学习mybatis知识后,2个月没有补充新的知识了,最近拿起书本开始学习.打算写下这个Redis系列的文章. 目录结构如下: Redis内置数据结构 Redis ...

  4. Redis系列三之持久化

    一.Redis持久化 Redis是一个支持持久化的内存数据库,redis需要经常将内存中的数据同步到磁盘来保证持久化. redis提供了不同级别的持久化方法: Snapshotting(快照,默认方式 ...

  5. Redis 中的数据持久化策略(RDB)

    Redis 是一个内存数据库,所有的数据都直接保存在内存中,那么,一旦 Redis 进程异常退出,或服务器本身异常宕机,我们存储在 Redis 中的数据就凭空消失,再也找不到了. Redis 作为一个 ...

  6. Redis 中的数据持久化策略(AOF)

    上一篇文章,我们讲的是 Redis 的一种基于内存快照的持久化存储策略 RDB,本质上他就是让 redis fork 出一个子进程遍历我们所有数据库中的字典,进行磁盘文件的写入. 但其实这种方式是有缺 ...

  7. docker 系列 - 容器数据持久化和数据共享

    docker 主要有两种数据存储形式, 一种是storage driver(也叫做 Graph driver), 另一种是 volume driver. stroage driver主要是存储那些无状 ...

  8. IdentityServer4系列 | 支持数据持久化

    一.前言 在前面的篇章介绍中,一些基础配置如API资源.客户端资源等数据以及使用过程中发放的令牌等操作数据,我们都是通过将操作数据和配置数据存储在内存中进行实现的,而在实际开发生产中,我们需要考虑如何 ...

  9. redis系列:RDB持久化与AOF持久化

    前言 什么是持久化? 持久化(Persistence),即把数据(如内存中的对象)保存到可永久保存的存储设备中(如磁盘).持久化的主要应用是将内存中的对象存储在数据库中,或者存储在磁盘文件中.XML数 ...

随机推荐

  1. C++五子棋(六&七)——游戏结束

    规则原理 如图 判断游戏结束 chessData.h //row,col 表示当前落子 bool checkWin(ChessData* game, int row, int col); 横.竖.斜( ...

  2. GopherCon SG 2019 "Understanding Allocations" 学习笔记

    本篇是根据 GopherCon SG 2019 "Understanding Allocations" 演讲的学习笔记. Understanding Allocations: th ...

  3. 2021.08.01 P3377 左偏树模板

    2021.08.01 P3377 左偏树模板 P3377 [模板]左偏树(可并堆) - 洛谷 | 计算机科学教育新生态 (luogu.com.cn) #include<iostream> ...

  4. 论文解读(DCRN)《Deep Graph Clustering via Dual Correlation Reduction》

    论文信息 论文标题:Deep Graph Clustering via Dual Correlation Reduction论文作者:Yue Liu, Wenxuan Tu, Sihang Zhou, ...

  5. ps、top命令查找不到进程的解决方案

    netstat -anpt发现一个奇怪的连接,但是ps和top命令确查不到此进程,这很可能是因为因为ps和top命令被替换了导致这些进程被过滤掉了.因此我这里有个脚本专门查找出来隐藏的进程 #!/us ...

  6. 9.1 Linux存储结构和文件系统

    1. 存储结构 Linux系统中的一切文件都是从"根"目录(/)开始的,并按照文件系统层次标准(FHS)采用倒树状结构来存放文件,以及定义了常见目录的用途. 目录名称 应放置文件的 ...

  7. OpenHarmony 3.1 Beta版本关键特性解析——分布式DeviceProfile

    (以下内容来自开发者分享,不代表 OpenHarmony 项目群工作委员会观点) 成翔 OpenAtom OpenHarmony(以下简称"OpenHarmony")作为分布式操作 ...

  8. 前端 pickerview 的效果 实现 省市区 三级联动

    效果图 需要引入 大佬写的js 以及 css 源文件里面有大佬的地址 这是我存在gitee上的文件 https://gitee.com/depressiom/address-pickview-effe ...

  9. 团队Beta2

    队名:观光队 链接 组长博客 作业博客 组员实践情况 王耀鑫 **过去两天完成了哪些任务 ** 文字/口头描述 学习 展示GitHub当日代码/文档签入记录 接下来的计划 完成短租车,页面美化 **还 ...

  10. 10个最危险的Linux命令,希望你牢记在心

    点击上方"开源Linux",选择"设为星标" 回复"学习"获取独家整理的学习资料! 来源:Linux迷链接:https://www.linu ...