公众号搜索“码路印记”
Redis是一个内存数据库,也就是说全部的数据将保存在内存中,这与传统的MySQL、Oracle、SqlServer等关系型数据库直接把数据保存到硬盘相比,Redis的读写效率很是高。可是保存在内存中也有一个很大的缺陷,一旦断电或者宕机,内存数据库中的内容将会所有丢失。为了弥补这一缺陷,Redis提供了把内存数据持久化到硬盘文件,以及经过备份文件来恢复数据的功能。html
Redis支持两种方式的持久化:RDB快照文件和AOF。本文先介绍RDB快照方式的工做原理、优缺点等方面进行阐述,写完AOF方式后再对比二者的优缺点,结合前辈总结给出生产实践,但愿可以对你理解Redis的持久化机制带来帮助。经过本文你将了解到如下内容:~~
RDB快照用官方的话来讲:RDB持久化方案是按照指定时间间隔对你的数据集生成的时间点快照。它是Redis数据库中数据的内存快照,它是一个二进制文件(默认名称为:dump.rdb,可修改),存储了文件生成时Redis数据库中全部的数据内容。可用于Redis的数据备份、转移与恢复。redis
RDB快照的触发方式及运行行为受配置参数的影响,打开配置文件redis.conf
查看“SNAPSHOTTING”章节,了解RDB快照的参数及做用。对于各个参数的含义进行了翻译,英语好的同窗能够直接看英文。算法
################################ 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 1 save 300 10 save 60 10000
save参数是Redis触发自动备份的触发策略,seconds
为统计时间(单位:秒), changes
为在统计时间内发生写入的次数。save m n
的意思是:m秒内有n条写入就触发一次快照,即备份一次。save参数能够配置多组,知足在不一样条件的备份要求。若是须要关闭RDB的自动备份策略,可使用save ""
。如下为几种配置的说明:shell
save 900 1:表示900秒(15分钟)内至少有1个key的值发生变化,则执行 save 300 10:表示300秒(5分钟)内至少有1个key的值发生变化,则执行 save 60 10000:表示60秒(1分钟)内至少有10000个key的值发生变化,则执行 save "": 该配置将会关闭RDB方式的持久化
# 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
# 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
# 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中),先简单说下二者差异:数据库
RDB持久化的触发必然离不开以上两个方法,触发的方式分为手动和自动。手动触发容易理解,是指咱们经过Redis客户端人为的对Redis服务端发起持久化备份指令,而后Redis服务端开始执行持久化流程,这里的指令有save和bgsave。自动触发是Redis根据自身运行要求,在知足预设条件时自动触发的持久化流程,自动触发的场景有以下几个(摘自这篇文章):数组
save m n
配置规则自动触发;debug reload
命令从新加载redis时;结合源码及参考文章,我整理了RDB持久化流程来帮助你们有个总体的了解,而后再从一些细节进行说明。从下图能够知道,自动触发流程是一个完整的链路,涵盖了rdbSaveBackground、rdbSave等,接下来我以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为上次持久化后发生修改的次数。app
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是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以操做的耗时。
Redis的rdbSave函数是真正进行RDB持久化的函数,流程、细节贼多,总体流程能够总结为:建立并打开临时文件、Redis内存数据写入临时文件、临时文件写入磁盘、临时文件重命名为正式RDB文件、更新持久化状态信息(dirty、lastsave)。其中“Redis内存数据写入临时文件”最为核心和复杂,写入过程直接体现了RDB文件的文件格式,本着一图胜千言的理念,我按照源码流程绘制了下图。
补充说明一下,上图右下角“遍历当前数据库的键值对并写入”这个环节会根据不一样类型的Redis数据类型及底层数据结构采用不一样的格式写入到RDB文件中,再也不展开了。我以为你们对整个过程有个直观的理解就好,这对于咱们理解Redis内部的运做机制大有裨益。
数据恢复是自动执行的,咱们将备份文件 (例如:dump.rdb) 移动到Redis备份文件目录并启动服务便可,Redis就会自动加载文件数据至内存。Redis 服务器在载入RDB文件期间,会一直处于阻塞状态,直到载入工做完成为止。
这里备份文件名称及目录须要于redis.conf中的配置信息保持一致。
了解了RDB的工做原理后,对于RDB的优缺点就比较容易总结了。先来看下RDB的优势:
事物老是有两面性的,RDB优势明显,一样也存在缺点: