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

2021-06-19 18:55:49 浏览数 (1)

导语

redis作为内存数据库,为了防止因为程序bug或者机器故障导致的数据丢失,redis采取了多维度的措施来保障redis写入数据的安全。从单机层面:采取备份磁盘镜像 数据流水的形式,将内存状态落地到本地磁盘,防止因程序bug或者系统故障导致的数据丢失;从多机层面:通过主备机制,进行远程热备,保障数据安全。

本文分为拆分为多篇,分别从RDB、AOF、主备等角度介绍redis的数据落磁盘机制,介绍落地原理及相关源码流程、主备机制,包括原理,相关协议,以及具体实现。因笔者能力有限,行文观点若有疏漏,恳请指正。更多内容,移步作者个人博客

磁盘备份

redis通过磁盘实现数据落地,主要通过内存镜像定时备份和记录操作流水两种方式:

  1. 所谓内存镜像定时备份,即RDB(Redis DataBase):redis定时对内存进行全量备份,落地到本地磁盘文件;
  2. 所谓记录操作流水,即AOF(Append Only File):由于内存全量备份资源开销大,因此RDB只能做到定时更新&备份的时间粒度比较大,所以redis又通过记录操作流水的方式(即AOF),实现存储数据操作的回放。

RDB

所谓RDB,即内存镜像备份机制我们主要关注以下几个问题:

  1. 什么情况下,会触发redis进程执行RDB操作?
  2. redis如何进行RDB,期间发生数据的修改怎么办?
  3. RDB文件的格式是怎样,redis如何解析rdb文件?

我们结合代码,逐个问题进行分析。

RDB触发时机

代码语言:txt复制
在redis的配置文档中,我们可以看到rdb触发的时机分为两个维度:时间维度和修改维度;当修改次数超过指定配置,或者修改后超过指定时间,则触发RDB流程。
RDB落地频率的配置项RDB落地频率的配置项

以下为RDB主调流程的框架,其中:

RDB主流程为rdbSaveBackground接口

触发RDB流程分为两种情形:

客户端执行SAVE/BGSAVE指令,主动触发RDB流程;

二者区别在于,save会阻塞主进程直到RDB完成,bgsave会fork子进程执行rdb流程

定时任务触发(serverCron),当指定时间内修改次数超过阈值时触发RDB流程;

RDB流程是互斥的,即同一时刻只能一个RDB流程在跑;在这期间若是出现客户端执行BGSAVE指令,服务器也是不响应的,会推迟到本地RDB结束之后再执行;

redis的主循环redis的主循环

RDB落地流程

代码语言:txt复制
redis的RDB是通过fork一个进程实现的,由于进程之间是独立地址空间的,借助Linux的COW机制,当RDB过程中有数据发生修改,被修改的数据只会体现在redis主进程中,对rdb进程没有影响。
代码语言:txt复制
另一方面,当redis写请求大的时候,会导致redis内存耗用量变大,为了避免redis在RDB过程中被OOM,需要配置redis的内存占用,避免因为占用内存多大导致进程挂掉。
最大内存占用配置最大内存占用配置
RDB落地的入口方法RDB落地的入口方法
RDB落地的主控流程RDB落地的主控流程
RDB键值对落地方法RDB键值对落地方法

以上为redis的RDB主流程,主要包括以下几点:

  1. redis通过fork子进程的形式进行RDB,父进程主要负责一些数据管理和状态记录,实际数据落地操作交给子进程来做;父子进程通过管道进行通信
  2. redis的落地和数据分为三层:
    1. 文件级:主要写入文件头、DB和系统相关的管理信息;在接口rdbSave和rdbSaveRio中进行
    2. DB级:逐个DB进行处理,包括每个DB的大小、DB ID等信息;主要在接口rdbSaveRio中进行
    3. 数据级:即DB里面每个KV对,主要在接口rdbSaveKeyValuePair中进行
  3. 需要落地的数据包括:
    1. Redis-server的元数据,包括redis版本号、系统信息、redis占用内存的大小等
    2. Database元数据信息:redis版本、键值对数目等
    3. DB中的字段
    4. 每个DB的lua脚本
  4. 由于字典rehash会触发系统的COW机制,所以为了降低内存开销,在RDB过程中,关闭rehash
  5. RDB文件的格式为:
RDB文件格式RDB文件格式
RDB文件中每个DB的存储格式RDB文件中每个DB的存储格式
DB中每个键值对的格式DB中每个键值对的格式

RDB文件加载

Redis-server在启动过程中,会根据指令加载指定位置的rdb文件,核心流程的代码如下:

主要需要注意的是:

  1. Redis-server实例启动之后,在完成初始化工作后,通过调用栈:loadDataFromDisk-> rdbLoad->rdbLoadRio完成rdb文件的载入;其中loadDataFromDisk是根据标志位走入rdb分支还是AOF分支,而rdbLoad主要为RDB流程进行相关的准备工作;而真正载入rdb数据的,是rdbLoadRio接口
  2. server.loading是用于标识当前是否处于加载文件的阶段,这个标志位会对很多模块的逻辑产生影响,亦即,若是在加载rdb文件过程中,很多操作是不允许被执行的
  3. 超时的KV,在载入RDB数据的时候会删除掉

参考资料

  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 人点赞