Redis源码走读及编程实践——数据安全篇(二)

2021-10-13 11:14:38 浏览数 (1)

导语

《Redis源码走读及编程实践——数据安全篇(一)》介绍了RDB的落地原理并走读了redis的RDB落地流程,本文继续介绍redis的另外一种数据落地机制AOF,从配置、原理三个角度介绍redis的AOF机制。因笔者能力有限,行文观点若有疏漏,恳请指正。更多后台开发文章移步:作者个人博客

AOF

如前所述,redis采取RDB的方式进行全内存镜像备份;但是这种方式开销大,不能频繁进行;为了降低数据丢失,redis采取记录操作流水的形式记录客户端每次对redis-server数据的修改,也就是AOF(Append Only File)。关于AOF,主要需要关注以下几个问题:

  1. AOF具体的实现流程是怎样的?
  2. AOF文件如何描述redis-server的DB操作?
  3. 由于AOF是记录操作流水,AOF是如何解决操作流水中数据冗余的问题?

配置篇

Redis-server配置中和AOF相关的配置如下图:

Redis官方对于各个配置项的解释很清楚,这里简要介绍一下,具体可以参考redis发布包配置文件中的说明文档:

  1. appendonly:redis默认采取RDB模式进行数据冷备;但是由于RDB模式开销比较大,频率不能太高,所以间隔之间挂机的话会有分钟级的数据丢失。而AOF则是可以保证秒级或者严格的无数据丢失,更加适用于数据比较重要的场景。
  2. appendfilename:AOF落地写文件的文件名。
  3. appendfsync:程序写磁盘实际是一个异步过程,实际只是将数据写到一个输出缓存;需要依次经过flush和fsync才能落到磁盘上;而appendfsync提供了写文件刷磁盘的机制,可以分别配置为:
    1. no:表示不强制刷盘,根据os调度自动执行,性能最高,但是落地延迟不确定
    2. everysec:每秒强制调用fsync落到磁盘,可以达到秒级的数据持久性;
    3. always:每写一条log,就调用一次fsync同步到磁盘,严格保证数据落地,但是性能较差
  4. no-appendfsync-on-rewrite:当AOF配置为always或者everysec时,会强制fsync数据刷盘,但是当此时正在有线程执行bgsave或者AOF重写过程时,容易导致fsync阻塞时间过长,影响redis-server的性能,为此通过此配置项控制,若是后端有重写或者落地过程时,不执行刷盘机制。
  5. auto-aof-rewrite-percentage和auto-aof-rewrite-min-size:针对前文所述,AOF记录的操作流水,则必然存在大量的数据冗余,因此AOF提供了AOF重写机制规避这种问题,这两个配置就是触发AOF重写的条件,一个是文件大小至少得达到一定大小(auto-aof-rewrite-min-size)才会触发AOF重写;其次,达到一定的变化量才会触发AOF重写(auto-aof-rewrite-percentage)。
  6. aof-load-truncated:redis在运行过程中若是系统崩溃了,容易出现AOF文件被截断的情况,若是出现这种情况,通过此配置决定是否正常启动;若是配置yes,则表示接受AOF截断,然后尽可能多的从文件恢复数据;若是配置no,则发现出现截断则启动失败。

操作落地

代码语言:txt复制
所谓AOF(Append Only File)是redis在RDB之外的一种数据落地机制,区别于RDB全量备份内存镜像,AOF机制采取的是记录操作流水的方式实现数据落地,因此可以实践秒级甚至可以完全的无数据丢失。
代码语言:txt复制
Redis中,AOF落地其实分为三步:首先是写数据到AOF数据缓存区,然后是将数据从用户缓存区通过系统调用复制到内核缓存区,此时进程挂掉数据不会丢失,但是机器掉电或者系统崩溃会导致数据丢失;最后是写文件的数据从内核缓存区真正写入到磁盘,此时真正意义数据落地;可以通过手动调用flush和fsync强制数据刷到内核缓存区和强制数据落磁盘。
代码语言:txt复制
在redis中feedAppendOnlyFile完成上述流程的第一步,而flushAppendOnlyFile完成数据落内核缓存区和写磁盘两步:依次来看,首先是feedAppendOnlyFile接口:
  1. feedAppendOnlyFile的调用时机:在redis-server实例接收到客户端的指令请求之后,会对比server的dirty值,若大于0,则表示最近一次执行的redis命令触发了DB的数据发生变化,那么与此同时aof_state指令打开,则意味着开启AOF机制,则此时调用feedAppendOnlyFile走记录日志流水的流程;
  2. AOF机制中,类expireCommand命令都被转为pexpireCommandAt命令,类setexCommand的命令都被转为setCommand和pexpireatCommand命令;
  3. AOF重写过程中,若是出现写DB操作出现,由于当前文件会被丢弃掉,所以在写文件的同时需要写复制积压缓存区,同步给子进程落地;

数据flush并没有完全实际意义上的数据落地,flushAppendOnlyFile才是实际完成数据落磁盘:

关于AOF数据落地,需要关注以下几点:

  1. 对于always的落地策略,redis在flushAppendOnlyFile接口中是通过调用接口aof_fsync直接实现的;
  2. 而对于everysec的落地机制,redis采取的是提交任务,然后交给后台线程执行落地指令;也就是说其实现在的redis-server实际上是多进程 多线程的框架;
  3. 对于部分写入的场景,redis的机制是,发现只能部分写入,则认为写操作失败,写的数据全部回滚,通过truncate文件实现,并且在下次写入的时候重新尝试写入(若是always的写入机制则此时redis-server无法从错误中恢复,只能选择结束进程);
  4. 由于多进程同时写多个文件,带来IO性能的损耗,因此通知配置aof_no_fsync_on_rewrite开关来关闭bgsave或者aof-rewrite过程中的刷盘信息;这种做法,虽然提升了性能,但也带来一定的风险,即刷盘未完成机器宕机或系统崩溃会有数据丢失;

AOF重写

代码语言:txt复制
由于AOF记录的是redis的操作流,则必然存在很多冗余信息;时间长了,会导致AOF文件过大,既占用了存储空间又导致了重启进程的时候重建数据时间过长,为此redis采取AOF重写的方式来消除冗余数据;如前所述,触发AOF重写有两个维度,一个是文件大小;另外一个是文件的增长比例;在此,我们着重看AOF重写流程:

AOF重写的核心接口与流程已经在上面截图中有所论述,我们针对其中的一些需要注意的点进行说明:

  1. AOF重写的流程框架为:通过接口rewriteAppendOnlyFileBackground来fork出子进程负责执行AOF重写的核心主控接口rewriteAppendOnlyFile,主控接口通过rewriteAppendOnlyFileRio/rdbSaveRio完成内存镜像落地(以字节串形式或者指令流的格式),通过aofReadDiffFromParent完成复制积压缓存区的写入;
  2. AOF重写机制中,父子进程通过管道进行通信,包括:父进程写AOF重写期间发生写操作的指令流;子进程通知父进程停止写复制积压缓存区等;
  3. 相比AOF指令流回放,RDB重建DB的速度更快,因此redis4之后,通过开关aof_use_rdb_preamble控制是否打开RDB AOF混合持久化机制,对于RDB AOF混合持久化的AOF重写文件,实际就是RDB文件

加载流程

在redis server启动的时候,会加载磁盘数据,根据配置项中的AOF开关,判断是加载RDB文件还是AOF文件,分别通过不同的接口rdbLoad和loadAppendOnlyFile实现;关于AOF文件的加载,核心代码的流程如下部分代码截图所示;这里归纳一下需要注意的地方:

  1. 由于AOF存在混合持久化的机制,因为在加载AOF文件之处,会先加载头文件五个字节,判断是否是AOF混合持久化的落地文件,若是的话,采取RDB的加载机制;否则通过构建伪终端的形式,通过重放redis命令重建DB;
  2. 因为redis在执行指令的时候需要涉及很多client的成员变量,用于缓存参数等,所以在重放AOF文件的时候需要构建一个伪终端,即对端连接的client;
  3. 因为AOF存的是指令流,因此存在在指令落地的时候,进程crash了,导致指令不全,此时通过开关aof_load_truncated控制是否接收残缺的AOF文件,若是接受残缺的AOF文件,则在读到最后的时候会丢弃最后一次指令结果;
  4. 此外,在类似list这种含有N个元素的数据结构中,对于RDB的重建机制,就是读取N个参数读入进来,但是对于AOF重建来说,每个参数实际对应一个RPUSH key val指令,需要解析三个参数并执行一个RPUSH指令,因此来说RDB的重建效率要高于AOF所以redis4.0之后开启了RDB AOF混合持久化机制。

参考资料

  1. 《Redis设计与实现》黄建宏著
  2. https://www.cnblogs.com/winterfells/p/9161540.html
  3. https://cloud.tencent.com/developer/article/1427626
  4. https://unix.stackexchange.com/questions/33381/getting-information-about-a-process-memory-usage-from-proc-pid-smaps
  5. https://blog.csdn.net/u010902721/article/details/46446031
  6. https://blog.csdn.net/petib_wangwei/article/details/38225929
  7. http://www.web-lovers.com/redis-source-rdb.html
  8. http://sunny90.com/a/server/2014/0905/103.html

0 人点赞