Redis之所以快,一个重要的原因是基于内存,然而因此会出现个问题:万一服务器宕机了,那么内存中的数据就会全部丢失。
这种情况下,如果通过读取后端数据库进行恢复,会给数据库带来巨大压力不说,其速度也是非常慢的。
所以Redis提供RDB和AOF两种持久化机制,持久化功能有效地避免因进程退出造成的数据丢失问题,当下次重启时利用之前持久化的文件即可实现数据恢复。
AOF
AOF(append only file)持久化:以独立日志的方式记录每次写命令,重启时再重新执行AOF文件中的命令达到恢复数据的目的。AOF的主要作用是解决了数据持久化的实时性。
AOF功能开启需要设置配置:appendonly yes,默认不开启。AOF文件名通过appendfilename配置设置,默认文件名是appendonly.aof。
执行流程
执行中的问题
先知
在MySQL日志了提到了数据库的写前日志(Write Ahead Log, WAL),在实际写数据前,先把修改的数据记到日志文件中,以便故障时进行恢复。不过,AOF 日志正好相反,它是写后日志,“写后”的意思是 Redis 是先执行命令,把数据写入内存,然后才记录日志。
传统数据库的日志,例如 redo log(重做日志),记录的是修改后的数据,而 AOF 里记录的是 Redis 收到的每一条命令,这些命令是以文本形式保存的。这个做的好处有两点:
1、为了避免额外的检查开销,Redis 在向 AOF 里面记录日志的时候,并不会先去对这些命令进行语法检查。所以,如果先记日志再执行命令的话,日志中就有可能记录了错误的命令,Redis 在使用日志恢复数据时,就可能会出错。而写后日志这种方式,就是先让系统执行命令,只有命令能执行成功,才会被记录到日志中,否则,系统就会直接向客户端报错。所以,Redis 使用写后日志这一方式的一大好处是,可以避免出现记录错误命令的情况。
2、在命令执行后才记录日志,所以不会阻塞当前的写操作。
AOF缓冲中的刷盘策略
AOF后写日志有以上的优点,但也会带来两个缺点:
1、日志后写,要是日志还没写入就宕机了,那么数据就丢失了,如果是用作缓存的还好,
如果是要写入数据库的,那就无法恢复了。
2、虽然写后日志不会阻塞当前操作,但会阻塞下一个操作呀,毕竟总要执行的
所以,什么时候进行磁盘写回就很重要,为此AOF 机制给我们提供了三个选择,也就是 AOF 配置项appendfsync 的三个可选值。
可选值 | 说明 |
---|---|
Always | 同步写回:每个写命令执行完,立马同步地将日志写回磁盘 |
Everysec | 每秒写回:每个写命令执行完,只是先把日志写到 AOF 文件的内存缓冲区,每隔一秒把缓冲区中的内容写入磁盘 |
No | 操作系统控制的写回:每个写命令执行完,只是先把日志写到 AOF 文件的内存缓冲区,由操作系统决定何时将缓冲区内容写回磁盘 |
其实总的来看,数据都是会有丢失的可能,只是或多或少的问题。
优缺点对比:
可选值 | 执行时机 | 优点 | 缺点 |
---|---|---|---|
Always | 同步写回 | 可靠性高,数据基本不丢失 | 每条写命令都写回,性能影响较大 |
Everysec | 每秒写回 | 性能适中 | 宕机丢失一秒钟内的数据,最多两秒(原因在最后) |
No | 操作系统控制的写回 | 性能好 | 宕机时丢失的数据会比较多 |
总结:
想要获得高性能,就选择 No 策略;
如果想要得到高可靠性保证,就选择Always 策略;
如果允许数据有一点丢失,又希望性能别受太大影响的话,那么就选择Everysec 策略。
AOF重写机制
当AOF文件越来越大的时候,Redis引入AOF重写机制压缩文件体积。AOF文件重写是把Redis进程内的数据转化为写命令同步到新AOF文件的过程
比如说,当读取了键值对“hello”: “world”之后,重写机制会记录 set hello world 这条命令
需要恢复时,可以重新执行该命令,就能实现数据的恢复。
重写后的AOF文件为什么可以变小:
1、进程内已经超时的数据不再写入文件。
2、旧的AOF文件含有无效命令,重写使用进程内数据直接生成,这样新的AOF文件只保留最终数据的写入命令。
3、多条写命令可以合并为一个,如:lpush list a、lpush list b、lpush list c可以转化为:lpush list a b c。
AOF重写除了降低了文件占用空间外,新的更小的AOF文件可以更快地被Redis加载。
触发机制
手动触发
直接调用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重写流程
重写的过程 ---- 一个拷贝,两处日志
“一个拷贝”就是指,每次执行重写时,主线程 fork 出后台的 bgrewriteaof 子进程。此
时,fork 会把主线程的内存拷贝一份给 bgrewriteaof 子进程,这里面就包含了数据库的
最新数据。然后,bgrewriteaof 子进程就可以在不影响主线程的情况下,逐一把拷贝的数
据写成操作,记入重写日志。
“两处日志”是指:
因为主线程未阻塞,仍然可以处理新来的操作。此时,如果有写操作,**第一处日志就是指
正在使用的 AOF 日志,Redis 会把这个操作写到它的缓冲区**。这样一来,即使宕机了,这
个 AOF 日志的操作仍然是齐全的,可以用于恢复。
而**第二处日志,就是指新的 AOF 重写日志**。这个操作也会被写到重写日志的缓冲区。这
样,**重写日志也不会丢失最新的操作**。等到拷贝数据的所有操作记录重写完成后,重写日
志记录的这些最新操作也会写入新的 AOF 文件,以保证数据库最新状态的记录。此时,我
们就可以用新的 AOF 文件替代旧文件了。
日志恢复和修复
同时开启两种持久化方式
在这种情况下,当redis重启的时候会优先载入AOF文件来恢复原始的数据, 因为在通常情况下AOF文件保存的数据集要比RDB文件保存的数据集要完整.
如果加载的AOF损坏了:采用redis-check-aof–fix命令进行修复,修复后使用diff-u对比数据的差异,找出丢失的数据,有些可以人工修改补全。
AOF优缺点
AOF优点
备份机制更稳健,丢失数据概率更低
可读的日志文本
AOF缺点
比起RDB占用更多的磁盘空间。
恢复备份速度要慢。
每次读写都同步的话,有一定的性能压力。
RDB
RDB , Redis DataBase 的缩写。RDB持久化是把当前进程数据生成快照(把某一时刻的状态以文件的形式写到磁盘上) 保存到硬盘的过程,这样一来,即使宕机,快照文件也不会丢失,数据的可靠性也就得到了保证。
RDB 记录的是某一时刻的数据
RDB的触发机制
触发RDB持久化过程分为手动触发和自动触发。
手动触发
save:在主线程中执行,会导致阻塞,直到RDB过程完成为止;
bgsave:主线程执行fork操作创建一个子进程,专门用于写入 RDB 文件,避免了主线程的阻塞,阻塞只发生在fork阶段
因为save操作会导致线程阻塞,bgsave则是对这个问题做的优化,所以现在默认都是使用bgsave执行RDB操作
自动触发
1)使用save相关配置,如“save m n”。表示m秒内数据集存在n次修改时,自动触发bgsave。
2)如果从节点执行全量复制操作,主节点自动执行bgsave生成RDB文件并发送给从节点。
3)执行debug reload命令重新加载Redis时,也会自动触发save操作。
4)默认情况下执行shutdown命令时,如果没有开启AOF持久化功能则自动执行bgsave。
执行流程
执行中的问题
先知
Fork的作用是复制一个与当前进程一样的进程。新进程的所有数据(变量、环境变量、程序计数器等) 数值都和原进程一致,但是是一个全新的进程,并作为原进程的子进程。
在Linux程序中,fork()会产生一个和父进程完全相同的子进程,但子进程在此后多会exec系统调用,
出于效率考虑,Linux中引入了“写时复制技术”
一般情况父进程和子进程会共用同一段物理内存,只有进程空间的各段的内容要发生变化时,
才会将父进程的内容复制一份给子进程。
执行RDB时还是要执行写入/修改操作怎么办
bgsave的操作是为了避免阻塞的,但避免阻塞和正常处理写操作并不是一回事。主线程的确没有阻塞,可以正常接收请求,但是,为了保证快照完整性,它只能处理读操作,因为不能修改正在执行快照的数据。
暂停写操作,这是不可能的,所以Redis 会借助操作系统提供的写时复制技术(Copy-On-Write, COW),在执行快照的同时,正常处理写操作。
总结就是
bgsave 子进程运行后,开始读取主线程的内存数据,并把它们写入 RDB 文件。
此时,如果主线程对这些数据的操作也都是读操作,那么,主线程和bgsave 子进程相互不影响。
但是,如果主线程要修改一块数据,那么,这块数据就会被复制一份,生成该数据的副本。
然后,bgsave 子进程会把这个副本数据写入 RDB 文件,而在这个过程中,主线程仍然可以直接修改原来的数据。
执行的频率
对于AOF和RDB操作的理解,可以直接理解为打游戏的存档。AOF可以理解为是你每一步操作都记录下来,而RDB就是一次存档。有时候可能会有这么个感觉,如果我RDB执行频率和AOF的机制一样,那不就完美替换了AOF了吗…………
这里有个误区:RDB是对所有数据执行全量快照。而对所有数据操作会有两个问题:
1、频繁将全量数据写入磁盘,会给磁盘带来很大压力。
2、bgsave 子进程需要通过 fork 操作从主线程创建出来。虽然,子进程在创建后
不会再阻塞主线程,但是,fork 这个创建过程本身会阻塞主线程,而且主线程的内存越
大,阻塞时间越长。如果频繁 fork 出 bgsave 子进程,这就会频繁阻塞主线程了。
为了避免执行全量快照,可以想办法变成增量快照——只要将修改过的数据记录下来,然后之后执行快照时只要将修改过的数据写入RDB即可,但这样则必须要额外的内存空间记录,如果数据量大,那么需要的额外空间也大。
对此,Redis 4.0 中提出了一个混合使用 AOF 日志和内存快照的方法。简单来说,内存快照以一定的频率执行,在两次快照之间,使用 AOF 日志记录这期间的所有命令操作。
AOF日志也只用记录两次快照间的操作,也就是说,不需要记录所有操作了,因此,就不会出现文件过大的情况了,也可以避免重写开销。在第二次全量快照的时候,将修改的记录写入快照中后AOF文件就能清空。
RDB的优缺点
RDB的优点
1、RDB是一个紧凑压缩的二进制文件(节省磁盘空间),代表Redis在某个时间点上的数据快照。
非常适用于备份,全量复制等场景(大规模数据恢复)。
比如每6小时执行bgsave备份,并把RDB文件拷贝到远程机器或者文件系统中,用于灾难恢复。
2、Redis加载RDB恢复数据远远快于AOF的方式。
RDB的缺点
1、RDB方式数据没办法做到实时持久化/秒级持久化。因为bgsave每次运行都要执行fork操作创建子进程,
属于重量级操作,频繁执行成本过高。Fork的时候,内存中的数据被克隆了一份,大致2倍的膨胀性。
另外虽然Redis在fork时使用了写时拷贝技术,但是如果数据庞大时还是比较消耗性能。
2、RDB文件使用特定二进制格式保存,Redis版本演进过程中有多个格式的RDB版本,
存在老版本Redis服务无法兼容新版RDB格式的问题。
3、在备份周期在一定间隔时间做一次备份,所以如果Redis意外down掉的话,就会丢失最后一次快照后的所有修改
(打游戏最后一次忘记存档)。
fork操作
前面说到:fork操作是会阻塞父进程的,但fork采用操作系统提供的写实复制(Copy On Write)机制又避免阻塞。
阻塞
虽然fork创建的子进程不需要拷贝父进程的物理内存空间,但是会复制父进程的空间内存页表。
这个拷贝过程会消耗大量CPU资源,拷贝完成之前整个进程是会阻塞的,阻塞时间取决于整个实例
的内存大小,实例越大,内存页表越大,fork阻塞时间越久。
避免阻塞
运用写时复制——在有新的写入/修改操作时,数据会被复制一份并生成副本,生成副本要申请新的内存空
间,内存分配是以页为单位进行分配的,默认4k。这一过程虽然避免了写入的阻塞问题,但会带来申请内存
空间可能遇到的阻塞问题,特别是Linux kernel在2.6.38内核增加了Transparent Huge Pages(THP),支持
huge page(2MB)的页分配,默认开启。当开启时可以降低fork创建子进程的速度,但执行fork之后,
如果开启THP,复制页单位从原来4KB变为2MB,会大幅增加重写期间父进程内存消耗。
所以在Redis机器上需要关闭Huge Page机制
持久化资源消耗
CPU
1、子进程负责把进程内的数据分批写入文件,这个过程属于CPU密集操作,通常子进程对单核CPU利用率接近90%。
2、Redis是CPU密集型服务(CPU要读/写I/O(硬盘/内存),I/O在很短的时间就可以完成),所以不要做绑定单核CPU操作。由于子进程非常消耗CPU,会和父进程产生单核资源竞争。
内存
子进程通过fork操作产生,占用内存大小等同于父进程,理论上需要两倍的内存来完成持久化操作,但Linux有写时复制机制(copy-on-write)。父子进程会共享相同的物理内存页,当父进程处理写请求时会把要修改的页创建副本,而子进程在fork操作过程中共享整个父进程内存快照。
硬盘
子进程主要职责是把AOF或者RDB文件写入硬盘持久化。势必造成硬盘写入压力。
AOF追加阻塞
开启AOF持久化时,常用的同步硬盘的策略是everysec,用于平衡性能和数据安全性。对于这种方式,Redis使用另一条线程每秒执行fsync同步硬盘。当系统硬盘资源繁忙时,会造成Redis主线程阻塞。
原因是:执行fsync时主线程会对比上次AOF同步时间——如果距上次同步成功时间在2秒内,主线程直接返回。如果距上次同步成功时间超过2秒,主线程将会阻塞,直到同步操作完成。
所以everysec配置最多可能丢失2秒数据,不是1秒。且如果系统fsync缓慢,将会导致Redis主线程阻塞影响效率。
补充write和fsync操作
这两个操作是系统层面的,AOF的可选策略最后也是调用这两个操作,不同的策略调用的操作不一定一样。
刷盘操作流程:
内存 ----> 内存缓冲区 ----> 系统缓冲区 ----> 磁盘
write操作:write操作会触发延迟写(delayed write)机制,write操作在写入系统缓冲区后直接返回。
同步硬盘操作依赖于系统调度机制,如果系统宕机了,那么数据也会丢失。
fsync操作:针对单个文件操作(比如AOF文件),做强制硬盘同步,fsync将阻塞直到写入硬盘完成后返回,
保证了数据持久化
优化AOF追加阻塞问题主要方式是优化系统硬盘负载
1、不要和其他要频繁操作硬盘的服务一起部署
2、开启配置no-appendfsync-on-rewrite,默认关闭。表示在AOF重写期间不做fsync操作。
3、配置不同的分盘,分担压力
4、换设备