[Redis] Redis的数据持久化机制
Redis作为一种高性能的内存数据库,将全部数据储存在内存当中,因此一旦发生服务器宕机或系统崩溃,存储的数据就会全部丢失。为了解决这一问题,Redis提供RDB和AOF两种持久化机制,将数据同步到磁盘中。当系统或服务器重启时,利用持久化文件即可恢复数据,有效的避免了数据丢失问题。
1 RDB持久化
RDB持久化方式是通过快照(snapshotting)完成的,当符合一定条件时,redis会自动将内存中所有数据以二进制方式生成一份副本并存储在硬盘上。当redis重启时,redis会读取RDB持久化生成的二进制文件进行数据恢复。
1.1 RDB持久化的触发条件
手动触发
save命令触发
客户端执行save命令,该命令强制redis执行快照,这时候redis处于阻塞状态,不会响应任何其他客户端发来的请求,直到RDB快照文件执行完毕,所以请慎用。
bgsave命令触发
bgsave,即后台保存,当执行bgsave命令时,redis会fork出一个子进程来执行快照生成操作,需要注意的redis是在fork子进程这个简短的时间redis是阻塞的,当子进程创建完成以后redis继续响应客户端请求。执行过程如下图所示:
自动触发
自动触发最常见的情况是在配置文件中通过save m n,指定当m秒内发生n次变化时,会触发bgsave。
自动触发实际是在Redis内部一个定时器事件,每隔固定时间去检查当前数据发生的改变次数与时间是否满足配置的持久化触发的条件,如果满足则通过操作系统fork调用来创建出一个子进程,这个子进程默认会与父进程共享相同的地址空间,这时就可以通过子进程来遍历整个内存来进行存储操作,而主进程则仍然可以提供服务,当有写入时由操作系统按照内存页(page)为单位来进行copy-on-write保证父子进程之间不会互相影响。
除了save m n以外,还有一些其他情况会触发bgsave, 在主从复制场景下,如果从节点执行全量复制操作,则主节点会执行bgsave命令,并将rdb文件发送给从节点。执行shutdown命令时,也会自动执行rdb持久化。
1.2 RDB持久化的优缺点
- 优点:
- RDB是一个非常紧凑的文件,体积小,易于传输,适合灾难恢复。
- RDB可以最大化Redis的性能:父进程在保存RDB文件时唯一要做的就是fork出一个子进程,然后这个子进程就会处理接下来的所有保存工作,父进程无须执行任何磁盘I/O操作。
- RDB在恢复大数据集时的速度比AOF的恢复速度要快。
- 缺点:
- RDB是一个快照过程,无法完整的保存所以数据,尤其在数据量比较大时候,一旦出现故障丢失的数据将更多。
- RDB需要fork子进程将内容持久化在磁盘上。如果数据集很大,fork可能很耗时,并且如果数据集很大且CPU性能不佳,则可能导致Redis停止服务几毫秒甚至一秒钟。AOF机制也需要fork,但可以调整重写日志的频率。
2 AOF持久化
除了RDB持久化功能外,Redis还提供了AOF持久化功能。与RDB持久化通过保存数据库中的键值对来记录数据库状态不同,AOF持久化是通过保存Redis服务器所执行的写命令来记录数据库状态的。
2.1 AOF持久化的实现
AOF 持久化功能的实现可以分为命令追加,文件写入,文件同步三个步骤。
命令追加
当AOF持久化功能处于打开状态时,服务器在执行完一个写命令之后,会以协议格式将被执行的写命令追加到服务器状态的aof_buf缓冲区的末尾。其中aof_buf的定义位于redisServer结构体中。
AOF文件的同步和写入
因为服务器在处理文件事件时可能会执行写命令,使得一些内容被追加到aof_buf缓冲区里,所以在服务器每次结束一个事件循环之前,他都会调用flushAppendOnlyFile函数,考虑是否需要将aof_buf缓冲区中的内容写入和同步到AOF文件中。
针对flushAppendOnlyFile函数的行为,redis提供了三种同步策略,由配置参数appendfsync来决定,各个不同值产生的行为如下所示:
- everysec:将aof_buf 缓冲区中的所有内容写入到AOF 文件,如果上次同步AOF 文件的时间距离现在超过一秒钟,那么再次对AOF 文件进行同步,并且这个同步操作是由一个专门负责执行的。
- always:将aof_buf 缓冲区的所有内容写入并同步到AOF 文件中。
- no:将aof_buf 缓冲区的所有内容写入到AOF 文件中,但并不对AOF 文件进行同步,合适同步由操作系统决定。
从效率上来讲,everysec模式足够快,并且就算出现故障停机,数据库也只丢失1秒的命令数据。这是折中的方案,兼顾性能和数据安全,也是redis的默认配置。
always模式在每次写操作后都调用fsync方法强制内核将数据写入到aof文件。这种情况下虽然数据比较安全,但是因为每次写操作都会同步到AOF文件中,所以在性能上会有影响,同时由于频繁的IO操作,硬盘的使用寿命会降低。
no模式下的同步交给操作系统write函数去执行,这种情况下,AOF文件写入速度是最快的,不过因为这种模式会在系统缓存中积累一段时间的写入数据,所以该模式的单次同步时长通常是三种模式中时间最长的。从平摊操作的角度来看,no模式和everysec模式的效率类似,当出现故障停机时,使用no模式的服务器将丢失上次同步AOF文件之后的所有写命令数据。
2.2 AOF文件的载入与数据还原
因为AOF文件里面包含了重建数据库状态所需的所有写命令,所以服务器只要读入并重新执行一遍AOF文件里面保存的写命令,就可以还原服务器关闭之前的数据库状态。Redis读取AOF文件并还原数据库的详细步骤如下图所示。
因为Redis的命令只能在客户端上下文中执行,而载入AOF文件时所使用的命令直接来源于AOF文件而不是网络连接,所以服务器使用了一个没有网络连接的伪客户端来执行AOF文件保存的写命令,伪客户端执行命令的效果和网络连接的客户端执行命令的效果完全一样。
2.3 AOF重写
因为AOF持久化是通过保存被执行的写命令来记录数据库状态的,所以随着服务器运行时间的流逝,AOF文件中的内容会越来越多,文件的体积也会越来越大,体积过大的AOF文件很可能对Redis服务器、甚至整个宿主计算机造成影响,同时使用AOF文件来进行数据还原所需的时间也越多。
为了解决AOF文件体积膨胀的问题,Redis提供了AOF文件重写(rewrite)功能。通过该功能,Redis服务器可以创建一个新的AOF文件来替代现有的AOF文件,新旧两个AOF文件所保存的数据库状态相同,但新AOF文件不会包含任何浪费空间的冗余命令,所以新AOF文件的体积通常会比旧AOF文件的体积要小得多。
AOF文件重写的实现
AOF文件重写并不需要对现有的AOF文件进行任何读取、分析或者写入操作,这个功能是通过读取服务器当前的数据库状态来实现的。
假设服务器对某一个key执行了多个写命令,那么服务器为了保存该key的当前状态,必须将这些写命令全部写入AOF文件中。如果服务器想要用尽量少的命令来记录该key的状态,最简单高效的办法不是去读取和分析现有AOF文件的内容,而是直接从数据库中读取该key对应的value值,然后用一条写命令命令来代替原有的多个写命令即可,这就是AOF重写功能的实现原理。
在实际中,为了避免在执行命令时造成客户端输入缓冲区溢出,重写程序在处理列表、哈希表、集合、有序集合这四种可能会带有多个元素的键时,会先检查键所包含的元素数量,如果元素的数量超过了AOF_REWRITE_ITEMS_PER_CMD常量的值,那么重写程序将使用多条命令来记录键的值,而不单单使用一条命令。在Redis4.0版本中,AOF_REWRITE_ITEMS_PER_CMD常量的值为64。
AOF后台重写
因为Redis服务器使用单个线程来处理命令请求,所以如果由服务器直接调用AOF重写函数的话,那么在重写期间,服务期将无法处理客户端发来的命令请求,所以Redis决定将AOF重写程序放到子进程里执行,这样做可以同时达到两个目的:
- 子进程进行AOF重写期间,服务器进程可以继续处理命令请求。
- 子进程带有服务器进程的数据副本,使用子进程而不是线程,可以在避免使用锁的情况下,保证数据的安全性。
但子进程在进行AOF重写期间,服务器进程还需要继续处理命令请求,而新的命令可能会对现有的数据库状态进行修改,从而使得服务器当前的数据库状态和重写后的AOF文件所保存的数据库状态不一致。为了解决这种数据不一致问题,Redis服务器设置了一个AOF重写缓冲区(aof_rewrite_buf),这个缓冲区在服务器创建子进程之后开始使用,当Redis服务器执行完一个写命令之后,它会同时将这个写命令发送给AOF缓冲区和AOF重写缓冲区。
AOF文件触发条件可分为手动触发和自动触发:
- 手动触发:客户端执行bgrewriteaof命令。
- 自动触发:自动触发通过以下两个配置协作生效:
- uto-aof-rewrite-min-size: AOF文件最小重写大小,只有当AOF文件大小大于该值时候才可能重写,4.0默认配置64MB。
- auto-aof-rewrite-percentage:当前AOF文件大小和最后一次重写后的大小之间的比率等于或者等于指定的增长百分比,如100代表当前AOF文件是上次重写的两倍时候才重写。
每次当serverCron(服务器周期性操作函数)函数执行时,它会检查以下条件是否全部满足,如果全部满足的话,就触发自动的AOF重写操作:
- 没有BGSAVE命令(RDB持久化)/AOF持久化在执行;
- 没有BGREWRITEAOF在进行;
- 当前AOF文件大小要大于server.aof_rewrite_min_size的值;
- 当前AOF文件大小和最后一次重写后的大小之间的比率等于或者大于指定的增长百分比(auto-aof-rewrite-perc)
整个AOF后台重写过程如下所示:
- 开始bgrewriteaof
- 主进程fork出子进程,在这一个短暂的时间内,redis是阻塞的。
- 主进程fork完子进程继续接受客户端请求,此时,客户端的写请求不仅仅写入原来aof_buf缓冲,还写入重写缓冲区aof_rewrite_buf。
- 子进程通过内存快照,按照命令重写策略写入到新的AOF文件。
- 子进程写完新的AOF文件后,向主进程发信号。
- 主进程把aof_rewrite_buf中的数据写入到新的AOF文件。
- 对新的AOF文件进行改名,原子的覆盖现有的AOF文件,完成新旧两个AOF文件的替换。
2.4 AOF持久化的优缺点
- 优点:
- 更高的数据安全性,同时有不同的同步策略
- AOF包含一个格式清晰、易于理解的日志文件记录所有的修改操作。
- AOF机制对日志文件的写入操作采用的是append模式,因此在写入过程中即使出现宕机现象,也不会破坏日志文件中已经存在的内容。同时Redis还提供redis-check-aof工具来解决数据一致性的问题。
- 缺点:
- 数据文件体积较大,即使有重写机制,但是在相同的数据集情况下,AOF文件通常比RDB文件大。
- 相对RDB方式,AOF模式的恢复速度慢于RDB。
- 由于频繁地将命令同步到文件中,AOF持久化对性能的影响相对RDB较大,但仍在一个可以接受的范围内。
3 RDB-AOF混合持久化
从redis4.0开始,添加了新的混合持久化方式,这里介绍的混合持久化就是同时结合RDB持久化以及AOF持久化混合写入AOF文件。这样做的好处是可以结合RDB和AOF的优点,快速加载同时避免丢失过多的数据,缺点是AOF里面的RDB部分就是压缩格式不再是AOF格式,可读性差。
混合持久化同样也是通过bgrewriteAOF完成的,不同的是当开启混合持久化时,fork出的子进程先将共享的内存副本全量的以RDB方式写入AOF文件,然后在将重写缓冲区的增量命令以AOF方式写入到文件,写入完成后通知主进程更新统计信息,并将新的含有RDB格式和AOF格式的AOF文件替换旧的的AOF文件。简单的说,新的AOF文件前半段是RDB格式的全量数据,后半段是AOF格式的增量数据。
当我们开启了混合持久化时,启动redis依然优先加载AOF文件,AOF文件加载可能有两种情况如下:
- AOF文件开头是RDB的格式,先加载RDB内容再加载剩余的AOF。
- AOF文件开头不是RDB的格式,直接以AOF格式加载整个文件。
参考内容:
[1]《redis系列–redis4.0深入持久化》,https://www.cnblogs.com/wdliu/p/9377278.html
[2]《Redis设计与实现》,黄健宏著
本博客所有文章除特别声明外,均采用 CC BY-SA 4.0 协议 ,转载请注明出处!