公众号搜寻“码路印记”

写在后面

Redis是一个内存数据库,也就是说所有的数据将保留在内存中,这与传统的MySQL、Oracle、SqlServer等关系型数据库间接把数据保留到硬盘相比,Redis的读写效率十分高。然而保留在内存中也有一个很大的缺点,一旦断电或者宕机,内存数据库中的内容将会全副失落。为了补救这一缺点,Redis提供了把内存数据长久化到硬盘文件,以及通过备份文件来复原数据的性能。

Redis反对两种形式的长久化:RDB快照文件和AOF。本文先介绍RDB快照形式的工作原理、优缺点等方面进行论述,写完AOF形式后再比照两者的优缺点,联合前辈总结给出生产实践,心愿可能对你了解Redis的长久化机制带来帮忙。通过本文你将理解到以下内容:~~

RDB快照用官网的话来说:RDB长久化计划是依照指定工夫距离对你的数据集生成的工夫点快照。它是Redis数据库中数据的内存快照,它是一个二进制文件(默认名称为:dump.rdb,可批改),存储了文件生成时Redis数据库中所有的数据内容。可用于Redis的数据备份、转移与复原。

配置参数

RDB快照的触发形式及运行行为受配置参数的影响,关上配置文件redis.conf查看“SNAPSHOTTING”章节,理解RDB快照的参数及作用。对于各个参数的含意进行了翻译,英语好的同学能够间接看英文。

  • save <seconds> <changes>
################################ SNAPSHOTTING  ################################## Save the DB on disk:##   save <seconds> <changes>##   Will save the DB if both the given number of seconds and the given#   number of write operations against the DB occurred.##   In the example below the behavior will be to save:#   after 900 sec (15 min) if at least 1 key changed#   after 300 sec (5 min) if at least 10 keys changed#   after 60 sec if at least 10000 keys changed##   Note: you can disable saving completely by commenting out all "save" lines.##   It is also possible to remove all the previously configured save#   points by adding a save directive with a single empty string argument#   like in the following example:##   save ""save 900 1save 300 10save 60 10000

save参数是Redis触发主动备份的触发策略,seconds为统计工夫(单位:秒), changes为在统计工夫内产生写入的次数。save m n的意思是:m秒内有n条写入就触发一次快照,即备份一次。save参数能够配置多组,满足在不同条件的备份要求。如果须要敞开RDB的主动备份策略,能够应用save ""。以下为几种配置的阐明:

save 900 1:示意900秒(15分钟)内至多有1个key的值发生变化,则执行save 300 10:示意300秒(5分钟)内至多有1个key的值发生变化,则执行save 60 10000:示意60秒(1分钟)内至多有10000个key的值发生变化,则执行save "": 该配置将会敞开RDB形式的长久化
  • dbfilename:快照文件的名称,默认为dump.rdb。
  • dir:快照文件保留目录,默认与以后配置文件在同一目录。
  • stop-writes-on-bgsave-error:默认值为yes。当启用了RDB且最初一次后盾保留数据失败,Redis是否进行接收数据。这会让用户意识到数据没有正确长久化到磁盘上,否则没有人会留神到劫难(disaster)产生了。如果Redis重启了,那么又能够从新开始接收数据了。
# By default Redis will stop accepting writes if RDB snapshots are enabled# (at least one save point) and the latest background save failed.# This will make the user aware (in a hard way) that data is not persisting# on disk properly, otherwise chances are that no one will notice and some# disaster will happen.## If the background saving process will start working again Redis will# automatically allow writes again.## However if you have setup your proper monitoring of the Redis server# and persistence, you may want to disable this feature so that Redis will# continue to work as usual even if there are problems with disk,# permissions, and so forth.stop-writes-on-bgsave-error yes
  • rdbcompression:默认值yes。备份数据时是否应用LZF算法压缩字符串对象。默认是开启的,这样能够节约存储空间,然而在生成备份文件时耗费局部CPU。
# Compress string objects using LZF when dump .rdb databases?# By default compression is enabled as it's almost always a win.# If you want to save some CPU in the saving child set it to 'no' but# the dataset will likely be bigger if you have compressible values or keys.rdbcompression yes
  • rdbchecksum:保留和加载rdb文件时是否应用CRC64校验,默认开启。启用此参数能够使rdb文件更加平安,进步稳定性,然而会有肯定的性能(大概10%)损失。如果rdb文件创建时未应用校验和,那么校验和将被设置为0,以此告知Redis跳过校验。
# Since version 5 of RDB a CRC64 checksum is placed at the end of the file.# This makes the format more resistant to corruption but there is a performance# hit to pay (around 10%) when saving and loading RDB files, so you can disable it# for maximum performances.## RDB files created with checksum disabled have a checksum of zero that will# tell the loading code to skip the check.rdbchecksum yes

长久化流程

在Redis内实现RDB长久化的办法有rdbSave和rdbSaveBackground两个函数办法(源码文件rdb.c中),先简略说下两者差异:

  • rdbSave:是同步执行的,办法调用后就会立即启动长久化流程。因为Redis是单线程模型,长久化过程中会阻塞,Redis无奈对外提供服务;
  • rdbSaveBackground:是后盾(异步)执行的,该办法会fork出子过程,真正的长久化过程是在子过程中执行的,主过程会持续提供服务;

RDB长久化的触发必然离不开以上两个办法,触发的形式分为手动和主动。手动触发容易了解,是指咱们通过Redis客户端人为的对Redis服务端发动长久化备份指令,而后Redis服务端开始执行长久化流程,这里的指令有save和bgsave。主动触发是Redis依据本身运行要求,在满足预设条件时主动触发的长久化流程,主动触发的场景有如下几个(摘自这篇文章):

  • serverCron中save m n配置规定主动触发;
  • 从节点全量复制时,主节点发送rdb文件给从节点实现复制操作,主节点会登程bgsave;
  • 执行debug reload命令从新加载redis时;
  • 默认状况下(未开启AOF)执行shutdown命令时,主动执行bgsave;

联合源码及参考文章,我整顿了RDB长久化流程来帮忙大家有个整体的理解,而后再从一些细节进行阐明。从下图能够晓得,主动触发流程是一个残缺的链路,涵盖了rdbSaveBackground、rdbSave等,接下来我以serverCron为例剖析一下整个流程。

serverCron

serverCron是Redis内的一个周期性函数,每隔100毫秒执行一次,它的其中一项工作就是:依据配置文件中save规定来判断以后须要进行主动长久化流程,如果满足条件则尝试开始长久化。简略理解一下这部分的运行原理。

第一次遇到这个函数,通过代码看下这个函数的代码正文。咱们能够发现它会实现过期key解决、软件监控、更新一些统计数据、触发RDB长久化或AOF重写、客户端超时解决等,本节咱们只关注RDB长久化局部。

/* This is our timer interrupt, called server.hz times per second. * Here is where we do a number of things that need to be done asynchronously. * For instance: * * - Active expired keys collection (it is also performed in a lazy way on *   lookup). * - Software watchdog. * - Update some statistic. * - Incremental rehashing of the DBs hash tables. * - Triggering BGSAVE / AOF rewrite, and handling of terminated children. * - Clients timeout of different kinds. * - Replication reconnection. * - Many more... * * Everything directly called here will be called server.hz times per second, * so in order to throttle execution of things we want to do less frequently * a macro is used: run_with_period(milliseconds) { .... } */int serverCron(struct aeEventLoop *eventLoop, long long id, void *clientData) {}

在redisServer中有几个与RDB长久化无关的字段(如下代码)。saveparams为配置文件中的save配置,lastsave为上次长久化的工夫戳,dirty为上次长久化后产生批改的次数。

struct redisServer {    /* 省略其余字段 */     /* RDB persistence */    long long dirty;                /* Changes to DB from the last save,上次长久化后批改key的次数 */    struct saveparam *saveparams;   /* Save points array for RDB,对应配置文件多个save参数 */    int saveparamslen;              /* Number of saving points,save参数的数量 */    time_t lastsave;                /* Unix time of last successful save 上次长久化工夫*/    /* 省略其余字段 */}/* 对应redis.conf中的save参数 */struct saveparam {    time_t seconds;                    /* 统计工夫范畴 */       int changes;                    /* 数据批改次数 */};

这部分的源码比较简单,感兴趣的同学能够下载查看,为了节俭篇幅我就不贴代码了。如果没有后盾的RDB长久化或AOF重写过程,serverCron会依据以上配置及状态判断是否须要执行长久化操作,判断根据就是看lastsave、dirty是否满足saveparams数组中的其中一个条件。如果有一个条件匹配,则调用rdbSaveBackground办法,执行异步长久化流程。

rdbSaveBackground

rdbSaveBackground是RDB长久化的辅助性办法,依据调用方(父过程或者子过程)不同,有两种不同的执行逻辑。如果调用方是父过程,则fork出子过程,保留子过程信息后间接返回。如果调用方是子过程则调用rdbSave执行RDB长久化逻辑,长久化实现后退出子过程。

int rdbSaveBackground(char *filename, rdbSaveInfo *rsi) {    pid_t childpid;    if (hasActiveChildProcess()) return C_ERR;    server.dirty_before_bgsave = server.dirty;    server.lastbgsave_try = time(NULL);    // fork子过程    if ((childpid = redisFork(CHILD_TYPE_RDB)) == 0) {        int retval;        /* Child */        redisSetProcTitle("redis-rdb-bgsave");        redisSetCpuAffinity(server.bgsave_cpulist);        // 执行rdb长久化        retval = rdbSave(filename,rsi);        if (retval == C_OK) {            sendChildCOWInfo(CHILD_TYPE_RDB, 1, "RDB");        }        // 长久化实现后,退出子过程        exitFromChild((retval == C_OK) ? 0 : 1);    } else {        /* Parent 父过程:记录fork子过程的工夫等信息*/        if (childpid == -1) {            server.lastbgsave_status = C_ERR;            serverLog(LL_WARNING,"Can't save in background: fork: %s",                strerror(errno));            return C_ERR;        }        serverLog(LL_NOTICE,"Background saving started by pid %ld",(long) childpid);        server.rdb_save_time_start = time(NULL);        server.rdb_child_type = RDB_CHILD_TYPE_DISK;        return C_OK;    }    return C_OK; /* unreached */}

rdbSave是真正执行长久化的办法,它在执行时存在大量的I/O、计算操作,耗时、CPU占用较大,在Redis的单线程模型中长久化过程会继续占用线程资源,进而导致Redis无奈提供其余服务。为了解决这一问题Redis在rdbSaveBackground中fork出子过程,由子过程实现长久化工作,防止了占用父过程过多的资源。

须要留神的是,如果父过程内存占用过大,fork过程会比拟耗时,在这个过程中父过程无奈对外提供服务;另外,须要综合思考计算机内存使用量,fork子过程后会占用双倍的内存资源,须要确保内存够用。通过info stats命令查看latest_fork_usec选项,能够获取最近一个fork以操作的耗时。

rdbSave

Redis的rdbSave函数是真正进行RDB长久化的函数,流程、细节贼多,整体流程能够总结为:创立并关上临时文件、Redis内存数据写入临时文件、临时文件写入磁盘、临时文件重命名为正式RDB文件、更新长久化状态信息(dirty、lastsave)。其中“Redis内存数据写入临时文件”最为外围和简单,写入过程间接体现了RDB文件的文件格式,本着一图胜千言的理念,我依照源码流程绘制了下图。

补充阐明一下,上图右下角“遍历以后数据库的键值对并写入”这个环节会依据不同类型的Redis数据类型及底层数据结构采纳不同的格局写入到RDB文件中,不再开展了。我感觉大家对整个过程有个直观的了解就好,这对于咱们了解Redis外部的运作机制大有裨益。

数据恢复

数据恢复是主动执行的,咱们将备份文件 (例如:dump.rdb) 挪动到Redis备份文件目录并启动服务即可,Redis就会主动加载文件数据至内存。Redis 服务器在载入RDB文件期间,会始终处于阻塞状态,直到载入工作实现为止。

这里备份文件名称及目录须要于redis.conf中的配置信息保持一致。

RDB优缺点

理解了RDB的工作原理后,对于RDB的优缺点就比拟容易总结了。先来看下RDB的长处:

  • RDB是一个紧凑压缩的二进制文件,代表Redis在某一个工夫点上的数据快照,非常适合用于备份、全量复制等场景。
  • RDB对劫难复原、数据迁徙十分敌对,RDB文件能够转移至任何须要的中央并从新加载。
  • RDB是Redis数据的内存快照,数据恢复速度较快,相比于AOF的命令重放有着更高的性能。

事物总是有两面性的,RDB长处显著,同样也存在毛病:

  • RDB形式无奈做到实时或秒级长久化。因为长久化过程是通过fork子过程后由子过程实现的,子过程的内存只是在fork操作那一时刻父过程的数据快照。而fork操作是一个耗时操作,无奈做到实时性。
  • RDB长久化过程中的fork操作,会导致内存占用加倍,而且父过程数据越多,fork过程越长。
  • RDB文件有文件格式要求,不同版本的Redis会对文件格式进行调整,存在老版本无奈兼容新版本的问题。

参考文献

  • Redis官网文档:《Redis Persistence》https://redis.io/topics/persi...
  • 《Redis RDB Persistence Details》https://programming.vip/docs/...
  • 一文看懂Redis的长久化原理:https://programming.vip/docs/...
  • Redis详解(六)------ RDB 长久化:https://www.cnblogs.com/ysoce...

[
](https://juejin.im/post/5d8587...