Redis源码阅读 (备份机制)
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源码阅读 (备份机制)相关推荐
- redis源码阅读-持久化之RDB
持久化介绍: redis的持久化有两种方式: rdb :可以在指定的时间间隔内生成数据集的时间点快照(point-in-time snapshot) aof : 记录redis执行的所有写操作命令 根 ...
- redis源码阅读-持久化之aof与aof重写详解
aof相关配置 aof-rewrite-incremental-fsync yes # aof 开关,默认是关闭的,改为yes表示开启 appendonly no # aof的文件名,默认 appen ...
- Redis源码阅读-Adlist双向链表
Redis源码阅读-链表部分- 链表数据结构在Adlist.h Adlist.c Redis的链表是双向链表,内部定义了一个迭代器. 双向链表的函数主要是链表创建.删除.节点插入.头插入.尾插入. ...
- redis源码阅读-zset
前段时间给小伙伴分享redis,顺带又把redis撸了一遍了,对其源码,又有了比较深入的了解.(ps: 分享的文章再丰富下再放出来). 数据结构 我们先看下redis 5.0的代码.本次讲解主要是zs ...
- Redis源码阅读,从入门到放弃
作为后端工程师,我们在面试和工作中都会用到 Redis,特别是大型互联网公司面试时,不仅要求面试者能简单使用 Redis,还要求懂 Redis 源码层面的实现原理,具备解决常见问题的能力.可以说,熟练 ...
- redis 源码阅读
双向链表(adlist.h/adlist.c) - redis 源码解析 1.0 documentation (redissrc.readthedocs.io) 如何阅读 Redis 源码? - bl ...
- Redis源码阅读01-读了一下redis启动流程涉及的源码我都读了个啥
阅读源码是学习一门技术的必经之路,经过1周左右的c语言入门学习,我就开始硬读redis的源码了.因为公司的多版本的改造,所以源码就选择redis6.x的最高版本redis6.2.7. 在阅读源码前,首 ...
- redis源码阅读(1)
redis 是c 编写的,首先看下redis 代码目录结构(对应版本3.25): 开发相关的放在deps下面: 主要代码放置在deps和src下面,utils 下面放置的是rb 脚本 首先看下src ...
- Redis源码阅读笔记(1)——简单动态字符串sds实现原理
首先,sds即simple dynamic string,redis实现这个的时候使用了一个技巧,并且C99将其收录为标准,即柔性数组成员(flexible array member),参考资料见这里 ...
- redis源码阅读--hashTable
为什么80%的码农都做不了架构师?>>> 公司有个项目用到hash,同事从redis的代码里抠了hashTable的源码出来,最近抽空看了一下,设计还是很精妙的. 现在把阅读的 ...
最新文章
- 机器学习资料推荐 URL
- linux常用命令(2)常用系统工作命令
- redhat下安装apache-tomcat-7.0.47.tar.gz
- LeetCode刷题记录(2)
- vscode浏览器打开html
- lucene api
- UML九种图 之 包图和对象图
- 新东方预计6个月亏损超8亿美元
- mysql模式匹配详解_MySQL SQL模式匹配
- 如何给服务器重装系统时,安装Raid驱动
- Python3抓取猫眼电影排行
- MySQL的索引失效问题
- pixhawk硬件设计粗略解析
- python广义矩估计_怎么用软件做广义矩估计GMM的参数估计?
- php生成网页快照图片,PHP100精华:PHP生成网页快照_PHP教程
- ASP.NET的默认数据文件ASP.NETDB.MDF说明
- changelog 生成 npm install -g conventional-changelog-cli
- OpenVZ平台魔改BBR之Rinetd
- 网易2019年春招笔试:爬塔玩法
- 牛客小白月赛58 B(暴力)C(思维)D(dp滚动数组优化)