5. 备份机制

Redis是内存数据库,支持两种方式——snapshot和aof,将数据从内存dump到磁盘。 snapshot是快照式备份,每次将Redis全库dump到磁盘, aof是流水式备份,每次将Redis数据库的修改日志dump到磁盘,并定期整理日志。

5.1. Snapshot

Redis支持Snapshot(快照)式备份。 Redis可以自动检测备份时机,设置每N秒检查,若发生M次以上数据更新操作,则开始Snapshot备份,也可以由客户端发送save或bgsave命令手动启动备份。save是阻塞式备份,备份过程中Redis会停止服务; bgsave是后台备份, Redis将新建一个备份进程负责将全库dump到磁盘。saveCommand()中,首先检查是否有bgsave的后台备份进程,若没有,则执行rdbSave()进行全库备份。bgsaveCommand()中,也会检查是否有bgsave的后台备份进程,若没有,则执行rdbSaveBackground(),启动备份子进程,在后台全库备份。备份子进程最终也是通过rdbSave()备份数据库。rdbSaveBackground()中,首先通过前文所说的waitEmptyIOJobsQueue()检查是否有vm的IO线程在进行数据交换,如果有则阻塞,等待所有vm的IO线程完成工作。然后fork()备份子进程。在父进程中, server.bgsavechildpid记录了备份子进程的pid,并通过updateDictResizePolicy()禁止Hash表自动扩容。在子进程中,如果启用了vm,则打开vm swap文件,然后调用rdbSave()执行备份。因为之前已经检查了所有vm的IO线程是否完成,因此这里打开vm的swap文件并不会和vm的IO线程冲突。

int rdbSaveBackground(char *filename) {

pid_t childpid;

if (server.bgsavechildpid != -1) return REDIS_ERR;

if (server.vm_enabled) waitEmptyIOJobsQueue();

server.dirty_before_bgsave = server.dirty;

if ((childpid = fork()) == 0) {

/* Child */

if (server.vm_enabled) vmReopenSwapFile();

if (server.ipfd > 0) close(server.ipfd);

if (server.sofd > 0) close(server.sofd);

if (rdbSave(filename) == REDIS_OK) {

_exit(0);

} else {

_exit(1);

}

} else {

/* Parent */

if (childpid == -1) {

redisLog(REDIS_WARNING,"Can't save in background: fork: %s",

strerror(errno));

return REDIS_ERR;

}

redisLog(REDIS_NOTICE,"Background saving started by pid %d",childpid);

server.bgsavechildpid = childpid;

updateDictResizePolicy();

return REDIS_OK;

}

return REDIS_OK; /* unreached */

}

rdbSave()中首先通过waitEmptyIOJobsQueue()检查vm的IO线程,并阻塞直到所有vm的IO线程完成。因为备份过程中,需要备份vm的swap文件,如果有未完成的vm的IO线程,会导致数据不同步。首先建立一个tmp文件,向它写入备份数据。依次循环每个db: server.db,server.db+1, ..., server.db+server.dbnum,通过dict的迭代器dictIterator访问db中的所有数据,依次写入tmp文件。Redis在写备份文件时做了许多优化,例如rdbSaveLen()用于写入长度信息,写入的数字不超过32bit整数。 Redis将长度数字按二进制长度分三类:小于6bit, 6bit至14bit, 14bit至32bit,并用前缀REDIS_RDB_6BITLEN(00)、REDIS_RDB_14BITLEN(01)、 REDIS_RDB_32BITLEN(10)标示数字长度。将前缀与数字一起写入磁盘。这个优化类似于Protobuf里压缩数字的方法。类似的压缩还有很多,再此不一一分析。

int rdbSaveLen(FILE *fp, uint32_t len) {

unsigned char buf[2];

int nwritten;

if (len < (1<<6)) {

/* Save a 6 bit len */

buf[0] = (len&0xFF)|(REDIS_RDB_6BITLEN<<6);

if (rdbWriteRaw(fp,buf,1) == -1) return -1;

nwritten = 1;

} else if (len < (1<<14)) {

/* Save a 14 bit len */

buf[0] = ((len>>8)&0xFF)|(REDIS_RDB_14BITLEN<<6);

buf[1] = len&0xFF;

if (rdbWriteRaw(fp,buf,2) == -1) return -1;

nwritten = 2;

} else {

/* Save a 32 bit len */

buf[0] = (REDIS_RDB_32BITLEN<<6);

if (rdbWriteRaw(fp,buf,1) == -1) return -1;

len = htonl(len);

if (rdbWriteRaw(fp,&len,4) == -1) return -1;

nwritten = 1+4;

}

return nwritten;

}

在备份文件中, Redis写入type/key/value数据,并放弃所有过期的数据。特别地,对于vm中的数据,会先读入内存,再将数据写入备份文件。父进程在serverCron中检查备份进程,如果存在bgsave进程或者bgrewrite进程(AOF备份,下节分析),则wait3()等待进程完成,完成后调用backgroundSaveDoneHandler()或者backgroundRewriteDoneHandler()处理。snapshot式备份中, Redis会停止vm的换入换出操作,停更新LRU策略中的标记信息,但不会影响Redis对内存中的数据的读写。 Redis通过调整snapshot备份的粒度,适应各种应用需求。

5.2. AOF

除了Snapshot备份模式外Redis还支持AOF(流水式)备份,它保存所有操作的commit log,并能自动地进行全库备份和删除无用的commit log。Redis在写commit log时, Redis并不是每次处理请求时都将请求写入磁盘,它会用一段内存Buffer缓存commit log,并在一定时间将缓存中的内容统一写入磁盘。 为了避免磁盘缓存的影响,可以设置三种策略进行fsync():每次写操作后均调用fsync(),每秒调用一次fsync(),从不调用fsync(),三种不同的策略获得不同的性能和一致性。

Redis将所有操作保存在commit log中, commit log的文件是追加写。当commitlog的大小无法承受时,可以手工通过bgrewriteaof命令产生一个快照(这个不同于Snapshot备份),具体构造方式后文分析。

AOF有四种操作:

•feedAppendOnlyFile()-将命令写入AOF。该方法在call()中被调用,用于将

所有Redis处理的命令写入AOF;该方法在Redis发现过期的Keys被调用,记录清除过期Key的操作

call()是Redis中所有命令处理的入口,当启用了AOF且数据有变化时

(server.dirty有变化),将命令用feedAppendOnlyFile()写入AOF,该函数中,将命令编码后写入server.aofbuf,当后台rewriteaof进程存在时,同时将编码后的命令写入server.bgrewritebuf, bgrewritebuf的作用后文解释。

/* Append to the AOF buffer. This will be flushed on disk just before

* of re-entering the event loop, so before the client will get a

* positive reply about the operation performed. */

server.aofbuf = sdscatlen(server.aofbuf,buf,sdslen(buf));

/* If a background append only file rewriting is in progress we want to

* accumulate the differences between the child DB and the current one

* in a buffer, so that when the child process will do its work we

* can append the differences to the new append only file. */

if (server.bgrewritechildpid != -1)

server.bgrewritebuf = sdscatlen(server.bgrewritebuf,buf,sdslen(buf));

•rewriteAppendOnlyFile()-产生快照,并更新aof文件。

rewriteAppendOnlyFile()只在 rewriteAppendOnlyFileBackground()中

被调用。 Redis产生AOF快照主要分为以下几步:

(1) fork()一个rewrite子进程,调用rewriteAppendOnlyFile()产生快照。

(2) 当rewrite进程开始后, rewriteAppendOnlyFile()扫描数据库中所有数

据,将他们编码后写入临时文件。 AOF文件的编码形式为文本,即人肉可读的编码。

(3)rewrite时父进程照常接收请求,并将流水日志写入

server.bgrewriteaofbuf中。

(4)子进程完成工作,父进程在serverCron()中通过wait3()获知其状态后,调用

backgroundRewriteDoneHandler()将server.bgrewriteaofbuf中的内容作为新

的commit log,覆盖原有server.aofbuf。

(5) 父进程将rewrite子进程生成的临时文件改名,作为新的aof文件。用于未来恢

复数据。

Redis的AOF快照机制的基础,是数据库的大小一定小于操作日志的大小,否则产生

的快照文件可能远远超过commit log文件的大小。因此,此处有一点值得考虑,

rewrite过程是否可以只写入aofbuf中改变了的数据的快照?

•flushAppendOnlyFile()-将内存buffer中的commit log写到磁盘上。

Redis在beforeSleep()中,及关闭AOF功能时,将内存buffer中的log写入磁盘。

•loadAppendOnlyFile()-重放AOF文件。该方法在Redis启动时被调用,从AOF

文件中重建数据库。

Redis通过AOF重建时,构造一个虚拟的client(),向自己发送重建的命令。而恢复

数据只需要将AOF快照重新载入数据库,并回放commit log中的操作。

Redis源码阅读 (备份机制)相关推荐

  1. redis源码阅读-持久化之RDB

    持久化介绍: redis的持久化有两种方式: rdb :可以在指定的时间间隔内生成数据集的时间点快照(point-in-time snapshot) aof : 记录redis执行的所有写操作命令 根 ...

  2. redis源码阅读-持久化之aof与aof重写详解

    aof相关配置 aof-rewrite-incremental-fsync yes # aof 开关,默认是关闭的,改为yes表示开启 appendonly no # aof的文件名,默认 appen ...

  3. Redis源码阅读-Adlist双向链表

    Redis源码阅读-链表部分- 链表数据结构在Adlist.h   Adlist.c Redis的链表是双向链表,内部定义了一个迭代器. 双向链表的函数主要是链表创建.删除.节点插入.头插入.尾插入. ...

  4. redis源码阅读-zset

    前段时间给小伙伴分享redis,顺带又把redis撸了一遍了,对其源码,又有了比较深入的了解.(ps: 分享的文章再丰富下再放出来). 数据结构 我们先看下redis 5.0的代码.本次讲解主要是zs ...

  5. Redis源码阅读,从入门到放弃

    作为后端工程师,我们在面试和工作中都会用到 Redis,特别是大型互联网公司面试时,不仅要求面试者能简单使用 Redis,还要求懂 Redis 源码层面的实现原理,具备解决常见问题的能力.可以说,熟练 ...

  6. redis 源码阅读

    双向链表(adlist.h/adlist.c) - redis 源码解析 1.0 documentation (redissrc.readthedocs.io) 如何阅读 Redis 源码? - bl ...

  7. Redis源码阅读01-读了一下redis启动流程涉及的源码我都读了个啥

    阅读源码是学习一门技术的必经之路,经过1周左右的c语言入门学习,我就开始硬读redis的源码了.因为公司的多版本的改造,所以源码就选择redis6.x的最高版本redis6.2.7. 在阅读源码前,首 ...

  8. redis源码阅读(1)

    redis 是c 编写的,首先看下redis 代码目录结构(对应版本3.25): 开发相关的放在deps下面: 主要代码放置在deps和src下面,utils 下面放置的是rb 脚本 首先看下src ...

  9. Redis源码阅读笔记(1)——简单动态字符串sds实现原理

    首先,sds即simple dynamic string,redis实现这个的时候使用了一个技巧,并且C99将其收录为标准,即柔性数组成员(flexible array member),参考资料见这里 ...

  10. redis源码阅读--hashTable

    为什么80%的码农都做不了架构师?>>>    公司有个项目用到hash,同事从redis的代码里抠了hashTable的源码出来,最近抽空看了一下,设计还是很精妙的. 现在把阅读的 ...

最新文章

  1. 机器学习资料推荐 URL
  2. linux常用命令(2)常用系统工作命令
  3. redhat下安装apache-tomcat-7.0.47.tar.gz
  4. LeetCode刷题记录(2)
  5. vscode浏览器打开html
  6. lucene api
  7. UML九种图 之 包图和对象图
  8. 新东方预计6个月亏损超8亿美元
  9. mysql模式匹配详解_MySQL SQL模式匹配
  10. 如何给服务器重装系统时,安装Raid驱动
  11. Python3抓取猫眼电影排行
  12. MySQL的索引失效问题
  13. pixhawk硬件设计粗略解析
  14. python广义矩估计_怎么用软件做广义矩估计GMM的参数估计?
  15. php生成网页快照图片,PHP100精华:PHP生成网页快照_PHP教程
  16. ASP.NET的默认数据文件ASP.NETDB.MDF说明
  17. changelog 生成  npm install -g conventional-changelog-cli
  18. OpenVZ平台魔改BBR之Rinetd
  19. 网易2019年春招笔试:爬塔玩法
  20. 牛客小白月赛58 B(暴力)C(思维)D(dp滚动数组优化)

热门文章

  1. 7-24 求集合数据的均方差 (15 分)
  2. L1-051 打折 (5 分)—团体程序设计天梯赛
  3. mongo 监听指定语句
  4. 调用ICodeCompiler来计算字符串表达式
  5. 作团队感悟(4)----分享的心态
  6. 求二叉树的深度(C++)
  7. ELK详解(八)——Logstash收集系统日志实战
  8. Linux进程控制与进程优先级
  9. Linux目录结构详解
  10. 和为S的两个数字(python)