redis系列四-爱代码爱编程
Redis数据持久化
Redis是一个内存数据库,所以其运行效率非常高。但也存在一个问题:内存中的数据是不持久的,若主机宕机或Redis关机重启,则内存中的数据全部丢失。当然,这是不允许的,为了保证Redis数据不丢失,那就要把数据从内存存储到硬盘上,以便在服务重启时还能够从磁盘中恢复原有数据,这就是Redis的数据持久化。
Redis数据持久化有三种方式:
- RDB快照(Redis DataBase):将某一时刻的内存数据以二进制的方式写入磁盘(早期默认方式)
- AOF日志(Append Only File):文件追加方式,记录所有的操作命令,并以文本的形式追加到文件中
- 混合持久化方式:Redis4.0新增了混合持久化的方式,集成了RDB和AOF的优点
1.1、持久化基本原理
Redis 持久化也称为钝化,是指将内存中数据库的状态描述信息保存到磁盘中。只不过是不同的持久化技术对数据的状态描述信息是不同的,生成的持久化文件也是不同的。但它们的作用都是相同的:避免数据意外丢失。
通过手动方式或自动定时方式,或自动条件触发方式,将内存中数据库的状态描述信息写入到指定的持久化文件中。当系统重新启动时,自动加载持久化文件,并根据文件中数据库状态描述信息将数据恢复到内存中,这个数据恢复过程也称为激活。这个钝化与激活的过程就是Redis持久化的基本原理。
不过从以上分析可知,对于Redis单机状态下,无论是手动方式还是定时方式或条件触发方式,都存在数据丢失问题:在尚未手动或自动保存时发生了Redis宕机状况,那么从上次保存到宕机期间产生的数据就会丢失。不同的持久化方式,其数据的丢失率也是不同的。
1.2、RDB机制
RDB其实就是把数据以快照的形式保存在磁盘上。
RDB持久化是指在指定的时间间隔内将内存中的数据集快照写入磁盘。也是早期默认的持久化方式,这种方式就是将内存中的数据以快照的方式写入二进制文件中,默认的文件名为dump.rdb
RDB采用内存快照方式,它记录的是某一刻的数据,而不是操作,所以采用RDB的方式做数据恢复时只需把RDB文件读入内存即可,实现快速恢复。
既然RDB机制是通过把某个时刻的所有数据生成一个快照保存下来,那么就应该有一个触发机制来实现这个过程,对于RDB来说提供来三种机制:save、BGSAVE和自动化。
1.2.1、save触发方式
save命令会阻塞当前Redis服务,执行save命令期间,Redis不能处理其他命令,直到RDB过程完成为止。具体流程如下:
执行完成时如果存在老的dump.rdb文件,就用新的替换掉旧的。整个过程Redis不能处理客户端的读写请求,这种方式显然不太友好。
1.2.2、BGSAVE触发方式
执行BGSAVE命令时,Redis会在后台异步进程快照操作,同时还可以响应客户端的请求,具体流程如下:
具体操作是Redis进程执行fork操作创建子进程,RDB持久化过程由子进程负责,完成后自动结束,阻塞只发生在fork阶段,一般时间很短。基本上Redis内部所有的RDB操作都是采用BGSAVE命令的这个过程。
在执行快照的同时,Redis会借助操作系统提供的写时复制技术(Copy-On-Write,COW),正常处理写操作。BGSAVE子进程由主进程fork生成,可以共享主进程的所有内存数据,BGSAVE子进程运行后,开始读取主进程的内存数据,并把它们写入RDB文件。
Redis是怎么解决在BGSAVE做快照时允许数据修改的呢?
这里主要是利用BGSAVE的子进程实现的,具体操作如下:
如果主进程执行读操作,则主进程和子进程互相不影响;
如果主进程执行写操作,则被修改的数据会复制一份副本,然后,主进程在这个数据副本上进行修改,同时BGSAVE子进程可以继续把原来的数据写入RDB文件。
虽然BGSAVE执行时不阻塞主进程,但是,如果频繁地执行全量快照也会带来两方面的开销:
- 一方面,频繁将全量数据写入磁盘,会给磁盘带来很大压力,多个写操作竞争有限的磁盘IO,前一个快照还没写完,后一个又开始写,容易造成恶性循环,所以在Redis中如果有一个BGSAVE在执行,就不会启动第二个BGSAVE子进程。
- 另一方面,BGSAVE子进程需要通过fork主进程创建,fork操作本身会阻塞主进程,而且主进程的内存越大,阻塞时间就越长。
1.2.3、自动触发
自动触发是由配置文件来控制的。在redis.conf配置文件中,里面有如下配置,我们可以去设置:
- save:这里是用来配置触发 Redis的 RDB 持久化条件,也就是什么时候将内存中的数据保存到硬盘。比如save m n。表示m秒内数据集存在n次修改时,自动触发bgsave。
默认如下配置:
表示900 秒内如果至少有 1个key的值变化,则保存save 900 1
表示300 秒内如果至少有 10个key 的值变化,则保存save 300 10
表示60 秒内如果至少有 10000个key 的值变化,则保存save 60 10000
不需要持久化,那么你可以注释掉所有的 save行来停用保存功能。 - stop-writes-on-bgsave-error :默认值为yes。当启用了RDB且最后一次后台保存数据失败,Redis是否停止接收数据。这会让用户意识到数据没有正确持久化到磁盘上,否则没有人会注意到灾难(disaster)发生了。如果Redis重启了,那么又可以重新开始接收数据了
- rdbcompression :默认值是yes。对于存储到磁盘中的快照,可以设置是否进行压缩存储。
- rdbchecksum :默认值是yes。在存储快照后,我们还可以让redis使用CRC64算法来进行数据校验,但是这样做会增加大约10%的性能消耗,如果希望获取到最大的性能提升,可以关闭此功能。
- dbfilename :设置快照的文件名,默认是 dump.rdb
- dir:设置快照文件的存放路径,这个配置项一定是个目录,而不能是文件名
RDB快照是一次全量备份,存储的是内存数据的二进制序列化形式,存储上非常紧凑。当进行快照持久化时,会开启一个子进程专门负责快照持久化,子进程会拥有父进程的内存数据,父进程修改内存子进程不会反应出来,所以在快照持久化期间修改的数据不会被保存,可能丢失数据。
1.3、AOF机制
全量备份总是耗时的,有时候我们提供一种更加高效的方式AOF,工作机制很简单,Redis会将每一个收到的写命令都通过write函数追加到文件中。通俗的理解就是日志记录。
AOF采用的是写后日志的方式,Redis先执行命令把数据写入内存,然后再记录日志到文件中。AOF日志记录的是操作命令,不是实际的数据,如果采用AOF方法做故障恢复时需要将全量日志都执行一遍
1.3.1、AOF三种触发机制
- 每修改同步always:每个写命令执行完,立马同步地将日志写回磁盘,同步持久化,每次发生数据变更会被立即记录到磁盘,性能较差但数据完整性比较好
- 每秒同步everysec:每个写命令执行完,只是先把日志写到 AOF 文件的内存缓冲区,每隔一秒把缓冲区中的内容写入磁盘,异步操作,每秒记录,如果一秒内宕机,有数据丢失,
- 不同步no:每个写命令执行完,只是先把日志写到 AOF 文件的内存缓冲区,由操作系统决定何时将缓冲区内容写回磁盘
配置项 | 写回时机 | 优点 | 缺点 |
Always | 同步写回 | 可靠性高,数据基本不丢失 | 每个写命令都有落盘,性能低 |
Everysec | 每秒写回 | 性能适中 | 宕机时丢失1秒内的数据 |
No | 操作系统控制写回 | 性能好 | 宕机时丢失数据较多 |
需要根据业务场景选择,想要高性能,数据完整性不很重要就选No,数据完整性很重要就选Always,允许数据有一点丢失,还希望性能不受太大影响,就选Everysec。
1.3.2、AOF的文件重写
AOF 是以文件的形式在记录接收到的所有写命令。随着接收的写命令越来越多,AOF 文件会越来越大。这也就意味着,我们一定要小心 AOF 文件过大带来的性能问题,主要在于以下三个方面:
- 文件系统本身对文件大小有限制,无法保存过大的文件;
- 如果文件太大,之后再往里面追加命令记录的话,效率也会变低;
- 如果发生宕机,AOF 中记录的命令要一个个被重新执行,用于故障恢复,如果日志文件太大,整个恢复过程就会非常缓慢,这就会影响到 Redis 的正常使用。
为了解决AOF文件体积膨胀的问题,Redis提供了AOF文件重写机制,AOF 重写机制的工作原理为Redis 根据数据库的现状创建一个新的 AOF 文件,新旧两个文件所保存的数据库状态是相同的,但是新的AOF文件不会包含任何浪费空间的冗余命令,通常体积会较旧AOF文件小很多。重写机制具有多变一功能。所谓的多变一,也就是说,旧日志文件中的多条命令有可能修改了同一个键的值,在重写后的新日志中变成了一条命令(重写后只记录最后键值对的修改命令)。
重写的过程:
重写过程是由后台子进程 bgrewriteaof 来完成的,这也是为了避免阻塞主进程 ,导致数据库性能下降。
可以把重写的过程总结为一个拷贝,两处日志 :
- 一个拷贝:就是指每次执行重写时,主进程 fork 出后台的 bgrewriteaof 子进程。此时,fork 会把主进程的内存拷贝一份给 bgrewriteaof 子进程,这里面就包含了数据库的最新数据。然后,bgrewriteaof 子进程就可以在不影响主进程的情况下,逐一把拷贝的数据写成操作,记入重写日志。
- 第一处日志:因为主进程未阻塞,仍然可以处理新来的操作,Redis会把这个操作写到它的AOF缓冲区,即使宕机了,这个 AOF 日志的操作仍然是齐全的,可以用于恢复。
- 第二处日志:从bgrewriteaof 子进程创建时到AOF重写完成期间的操作也会被写到重写日志的缓冲区,这样重写日志也不会丢失新的操作,等到拷贝数据的所有操作记录重写完成后,重写日志缓冲区记录的这些新操作也会写入新的 AOF 文件,以保证数据库最新状态的记录。
此时,我们就可以用新的 AOF 文件替代旧文件了,完成AOF文件瘦身到目的。
设置重写触发策略,例如配置auto-aof-rewrite-percentage和auto-aof-rewrite-min-size,让Redis自动在文件增长到一定比例或超过最小尺寸时触发AOF重写。
AOF增量同步:配置aof-rewrite-incremental-fsync选项,可以控制在AOF重写过程中增量地进行fsync操作,从而减少AOF文件过大时的同步操作开销。
1.4、RDB和AOF对比
RDB快照是一次全量备份,AOF基本上实时备份数据库的每一步操作,所以两种备份的方式决定了这两种备份的特性和使用场景,如下表格列举两种工作机制的优缺点。
备份方式 | 优点 | 缺点 | 使用场景 |
RDB | RDB文件紧凑,全量备份 | 在进程快照持久化时主进程修改的数据容易丢失 | 定时全量备份 |
AOF | 可以更好的保护数据不丢失,一般配置最多丢失1秒的数据; AOF日志文件通过可读的方式记录操作命令,容易通过AOF日志文件恢复无操作; AOF日志支持重写,对日志文件瘦身 | 对同一份数据来说,AOF日志比RDB文件更大; 通过AOF恢复数据比RDB更慢; | 实时记录数据操作命令,对误操作的紧急恢复; 结合RDB对全量备份实时备份操作记录 |
1.5、RDB和AOF混合使用
RDB和AOF应该怎么选择,需要看自己的需求,最好的方式是这两种备份结合使用,如下图是两种备份的具体对比。
项目 | RDB | AOF |
加载优先级 | 低 | 高 |
日志文件体积 | 小 | 大 |
数据恢复速度 | 快 | 慢 |
数据安全性 | 容易丢数据 | 根据策略决定 |
轻重 | 重 | 轻 |
Redis执行RDB备份的执行频率非常重要,因为RDB太频繁会频繁写硬盘影响磁盘性能,极端情况下可能造成硬盘夯死导致系统宕机,RDB时间间隔太长就容易丢失数据,在Redis4.0版本后支持了RDB和AOF结合使用,就是RDB全量备份按一定频率执行,在两次RDB之间使用AOF日志记录期间的所有命令操作,这样一来RDB快照不用执行太频繁,也就避免了频繁fork对主进程对影响,而AOF只记录两次RDB备份之间的操作,不需记录所有的操作,因此AOF日志文件也不会出现很大的情况,也可以避免了AOF日志重写的开销。
特别提醒:
混合使用RDB和AOF综合了两者的优势,避免了两者的劣势,但配置结合使用时需要注意,因为结合使用加载日志文件时AOF的优先级比RDB的优先级高,所以在默认配置开启RDB的情况下开启AOF时,不能直接修改配置文件redis.conf然后直接重启redis服务,这样重启时很有可能AOF日志文件还没生成,重启Redis服务后自动加载AOF文件(空文件),就清空了Redis的数据,这时再执行一次RDB备份,就彻底清空了Redis数据,在生产中这种情况是灾难性的。所以开启AOF时需要先通过命令redis-cli config set appendonly yes 临时开启AOF,让Redis先执行一次AOF日志生成,保证AOF日志文件记录了数据库的最新状态后,才能修改配置文件redis.conf重启Redis服务使其永久有效。
欢迎关注作者的公众号,公众号每天分享运维干货文章