redis源码剖析(十二)—— RDB持久化

文章目录

    • RBD文件载入
    • RDB文件分析
    • 源码分析
      • 核心代码
    • rdb文件写入
    • rdb写入关键函数rdbSaveObjectType
      • rdbSaveStringObjectRaw
      • rdbSaveLongLongAsStringObject

为避免数据丢失。将redis中的数据保存到磁盘中,避免数据意外丢失。

RBD文件载入

在redis启动时检测是否有rdb文件,有的话会自动载入。

命令作用
save阻塞服务器进程,知道rbd文件创建完成
bgsavefork子进程,由子进程负责创建RDB文件

RDB文件分析

 [root@python redis-4.0.14]# od -c dump.rdb
0000000   R   E   D   I   S   0   0   0   8 372  \t   r   e   d   i   s
0000020   -   v   e   r 006   4   .   0   .   1   4 372  \n   r   e   d
0000040   i   s   -   b   i   t   s 300   @ 372 005   c   t   i   m   e
0000060 302 212  \b 331   ] 372  \b   u   s   e   d   -   m   e   m 302
0000100 210  \0  \f  \0 372  \f   a   o   f   -   p   r   e   a   m   b
0000120   l   e 300  \0 376  \0 373 001  \0  \0 004   n   a   m   e 300
0000140   { 377   8 033   _ 360   I 223 254 343
0000152
[root@python redis-4.0.14]# redis-cli
127.0.0.1:6379> flushall
OK
127.0.0.1:6379> set name xxxx
OK
127.0.0.1:6379> save
OK
127.0.0.1:6379> exit
[root@python redis-4.0.14]# od -c dump.rdb
0000000   R   E   D   I   S   0   0   0   8 372  \t   r   e   d   i   s
0000020   -   v   e   r 006   4   .   0   .   1   4 372  \n   r   e   d
0000040   i   s   -   b   i   t   s 300   @ 372 005   c   t   i   m   e
0000060 302   b   | 333   ] 372  \b   u   s   e   d   -   m   e   m 302
0000100   0 356  \f  \0 372  \f   a   o   f   -   p   r   e   a   m   b
0000120   l   e 300  \0 376  \0 373 001  \0  \0 004   n   a   m   e 004
0000140   x   x   x   x 377 314   " 221 277   [ 223 026   $
0000155
  • 0000000 R E D I S 0 0 0 8 372 \t r e d i s
    1、五个字节的REDIS
    2、四个字节版本号
    3、 一个字节的eof常量
    4、八个字节校验和

  • 004 n a m e 004 0000140 x x x x 377 314 " 221 277 [ 223 026 $
    004是key的长度
    377 314 " 221 277 [ 223 026 $ 八字节长的校验和

源码分析

Save the DB on disk. Return REDIS_ERR on error, REDIS_OK on success
将数据库保存到磁盘上。
保存成功返回 REDIS_OK ,出错/失败返回 REDIS_ERR 。

int rdbSave(char *filename) {
    // 创建临时文件snprintf(tmpfile,256,"temp-%d.rdb", (int) getpid());fp = fopen(tmpfile,"w");if (!fp) {redisLog(REDIS_WARNING, "Failed opening .rdb for saving: %s",strerror(errno));return REDIS_ERR;}// 初始化 I/OrioInitWithFile(&rdb,fp);// 设置校验和函数if (server.rdb_checksum)rdb.update_cksum = rioGenericUpdateChecksum;// 写入 RDB 版本号snprintf(magic,sizeof(magic),"REDIS%04d",REDIS_RDB_VERSION);if (rdbWriteRaw(&rdb,magic,9) == -1) goto werr;// 遍历所有数据库for (j = 0; j < server.dbnum; j++) {// 指向数据库redisDb *db = server.db+j;// 指向数据库键空间dict *d = db->dict;// 跳过空数据库if (dictSize(d) == 0) continue;// 创建键空间迭代器di = dictGetSafeIterator(d);if (!di) {fclose(fp);return REDIS_ERR;}/* Write the SELECT DB opcode** 写入 DB 选择器*/if (rdbSaveType(&rdb,REDIS_RDB_OPCODE_SELECTDB) == -1) goto werr;if (rdbSaveLen(&rdb,j) == -1) goto werr;/* Iterate this DB writing every entry** 遍历数据库,并写入每个键值对的数据*/while((de = dictNext(di)) != NULL) {sds keystr = dictGetKey(de);robj key, *o = dictGetVal(de);long long expire;// 根据 keystr ,在栈中创建一个 key 对象initStaticStringObject(key,keystr);// 获取键的过期时间expire = getExpire(db,&key);// 保存键值对数据if (rdbSaveKeyValuePair(&rdb,&key,o,expire,now) == -1) goto werr;}dictReleaseIterator(di);}di = NULL; /* So that we don't release it again on error. *//* EOF opcode** 写入 EOF 代码*/if (rdbSaveType(&rdb,REDIS_RDB_OPCODE_EOF) == -1) goto werr;/* CRC64 checksum. It will be zero if checksum computation is disabled, the* loading code skips the check in this case.** CRC64 校验和。** 如果校验和功能已关闭,那么 rdb.cksum 将为 0 ,* 在这种情况下, RDB 载入时会跳过校验和检查。*/cksum = rdb.cksum;memrev64ifbe(&cksum);rioWrite(&rdb,&cksum,8);/* Make sure data will not remain on the OS's output buffers */// 冲洗缓存,确保数据已写入磁盘if (fflush(fp) == EOF) goto werr;if (fsync(fileno(fp)) == -1) goto werr;if (fclose(fp) == EOF) goto werr;/* Use RENAME to make sure the DB file is changed atomically only* if the generate DB file is ok.** 使用 RENAME ,原子性地对临时文件进行改名,覆盖原来的 RDB 文件。*/if (rename(tmpfile,filename) == -1) {redisLog(REDIS_WARNING,"Error moving temp DB file on the final destination: %s", strerror(errno));unlink(tmpfile);return REDIS_ERR;}// 写入完成,打印日志redisLog(REDIS_NOTICE,"DB saved on disk");// 清零数据库脏状态server.dirty = 0;// 记录最后一次完成 SAVE 的时间server.lastsave = time(NULL);// 记录最后一次执行 SAVE 的状态server.lastbgsave_status = REDIS_OK;return REDIS_OK;werr:// 关闭文件fclose(fp);// 删除文件unlink(tmpfile);redisLog(REDIS_WARNING,"Write error saving DB on disk: %s", strerror(errno));if (di) dictReleaseIterator(di);return REDIS_ERR;
}

核心代码

// 遍历所有数据库for (j = 0; j < server.dbnum; j++) {// 创建键空间迭代器di = dictGetSafeIterator(d);if (!di) {fclose(fp);return REDIS_ERR;}/* Write the SELECT DB opcode** 写入 DB 选择器*//*  * 遍历数据库,并写入每个键值对的数据*/while((de = dictNext(di)) != NULL) {sds keystr = dictGetKey(de);robj key, *o = dictGetVal(de);long long expire;// 根据 keystr ,在栈中创建一个 key 对象initStaticStringObject(key,keystr);// 获取键的过期时间expire = getExpire(db,&key);// 保存键值对数据if (rdbSaveKeyValuePair(&rdb,&key,o,expire,now) == -1) goto werr;}dictReleaseIterator(di);}
  • 获取键值和键
237 // 计算给定键的哈希值
238 #define dictHashKey(d, key) (d)->type->hashFunction(key)
239 // 返回获取给定节点的键
240 #define dictGetKey(he) ((he)->key)
241 // 返回获取给定节点的值
242 #define dictGetVal(he) ((he)->v.val)

rdb文件写入

 891 int rdbSaveKeyValuePair(rio *rdb, robj *key, robj *val,892                         long long expiretime, long long now)893 {894     /* Save the expire time895      *896      * 保存键的过期时间897      */898     if (expiretime != -1) {899         /* If this key is already expired skip it900          *901          * 不写入已经过期的键902          */903         if (expiretime < now) return 0;904905         if (rdbSaveType(rdb,REDIS_RDB_OPCODE_EXPIRETIME_MS) == -1) return -1;906         if (rdbSaveMillisecondTime(rdb,expiretime) == -1) return -1;907     }908909     /* Save type, key, value910      *911      * 保存类型,键,值912      */913     if (rdbSaveObjectType(rdb,val) == -1) return -1;914     if (rdbSaveStringObject(rdb,key) == -1) return -1;915     if (rdbSaveObject(rdb,val) == -1) return -1;916917     return 1;918 }

rdb写入关键函数rdbSaveObjectType

 655 /* Save the object type of object "o".656  *657  * 将对象 o 的类型写入到 rdb 中658  */659 int rdbSaveObjectType(rio *rdb, robj *o) {660661     switch (o->type) {662663     case REDIS_STRING:664         return rdbSaveType(rdb,REDIS_RDB_TYPE_STRING);665666     case REDIS_LIST:667         if (o->encoding == REDIS_ENCODING_ZIPLIST)668             return rdbSaveType(rdb,REDIS_RDB_TYPE_LIST_ZIPLIST);669         else if (o->encoding == REDIS_ENCODING_LINKEDLIST)670             return rdbSaveType(rdb,REDIS_RDB_TYPE_LIST);671         else672             redisPanic("Unknown list encoding");673674     case REDIS_SET:675         if (o->encoding == REDIS_ENCODING_INTSET)676             return rdbSaveType(rdb,REDIS_RDB_TYPE_SET_INTSET);677         else if (o->encoding == REDIS_ENCODING_HT)678             return rdbSaveType(rdb,REDIS_RDB_TYPE_SET);679         else680             redisPanic("Unknown set encoding");681682     case REDIS_ZSET:683         if (o->encoding == REDIS_ENCODING_ZIPLIST)684             return rdbSaveType(rdb,REDIS_RDB_TYPE_ZSET_ZIPLIST);685         else if (o->encoding == REDIS_ENCODING_SKIPLIST)686             return rdbSaveType(rdb,REDIS_RDB_TYPE_ZSET);687         else688             redisPanic("Unknown sorted set encoding");689690     case REDIS_HASH:691         if (o->encoding == REDIS_ENCODING_ZIPLIST)692             return rdbSaveType(rdb,REDIS_RDB_TYPE_HASH_ZIPLIST);693         else if (o->encoding == REDIS_ENCODING_HT)694             return rdbSaveType(rdb,REDIS_RDB_TYPE_HASH);695         else696             redisPanic("Unknown hash encoding");697698     default:699         redisPanic("Unknown object type");700     }701702     return -1; /* avoid warning */703 }

rdbSaveStringObjectRaw

 493 /* Like rdbSaveStringObjectRaw() but handle encoded objects */494 /*495  * 将给定的字符串对象 obj 保存到 rdb 中。496  *497  * 函数返回 rdb 保存字符串对象所需的字节数。498  *499  * p.s. 代码原本的注释 rdbSaveStringObjectRaw() 函数已经不存在了。500  */501 int rdbSaveStringObject(rio *rdb, robj *obj) {502503     /* Avoid to decode the object, then encode it again, if the504      * object is already integer encoded. */505     // 尝试对 INT 编码的字符串进行特殊编码506     if (obj->encoding == REDIS_ENCODING_INT) {507         return rdbSaveLongLongAsStringObject(rdb,(long)obj->ptr);508509     // 保存 STRING 编码的字符串510     } else {511         redisAssertWithInfo(NULL,obj,sdsEncodedObject(obj));512         return rdbSaveRawString(rdb,obj->ptr,sdslen(obj->ptr));513     }514 }515

rdbSaveLongLongAsStringObject

 453 /* Save a long long value as either an encoded string or a string.454  *455  * 将输入的 long long 类型的 value 转换成一个特殊编码的字符串,456  * 或者是一个普通的字符串表示的整数,457  * 然后将它写入到 rdb 中。458  *459  * 函数返回在 rdb 中保存 value 所需的字节数。460  */461 int rdbSaveLongLongAsStringObject(rio *rdb, long long value) {462     unsigned char buf[32];463     int n, nwritten = 0;464465     // 尝试以节省空间的方式编码整数值 value466     int enclen = rdbEncodeInteger(value,buf);467468     // 编码成功,直接写入编码后的缓存469     // 比如,值 1 可以编码为 11 00 0001470     if (enclen > 0) {471         return rdbWriteRaw(rdb,buf,enclen);472473     // 编码失败,将整数值转换成对应的字符串来保存474     // 比如,值 999999999 要编码成 "999999999" ,475     // 因为这个值没办法用节省空间的方式编码476     } else {477         /* Encode as string */478         // 转换成字符串表示479         enclen = ll2string((char*)buf,32,value);480         redisAssert(enclen < 32);481         // 写入字符串长度482         if ((n = rdbSaveLen(rdb,enclen)) == -1) return -1;483         nwritten += n;484         // 写入字符串485         if ((n = rdbWriteRaw(rdb,buf,enclen)) == -1) return -1;486         nwritten += n;487     }488489     // 返回长度490     return nwritten;491 }492

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/382343.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

linux操作系统之exec函数族

当我们想在进程中执行另外一个函数或程序时&#xff0c;可以使用exec函数。进程调用exec函数&#xff0c;则该进程中用户空间所有代码和数据会完全被新程序替换&#xff0c;但是不会创建新进程&#xff0c;因此进程id不会发生改变。 函数族的头文件&#xff1a;unistd.h l:li…

redis源码剖析(十三)—— dump.rdb文件分析

文章目录操作方式查看rdb文件参考文档redis作者解释rdb和aof的不同redisRDB文件格式Sripathi Krishnamredis各个版本变化操作方式 127.0.0.1:9999> flushall OK 127.0.0.1:9999> set name hodge OK 127.0.0.1:9999> save OK查看rdb文件 [rootpython src]# od -c dum…

linux操作系统之子进程回收函数wait和waitpid函数小结

一个进程在终止时会关闭所有的文件描述符&#xff0c;释放用户空间分配的内存&#xff0c;但是它的PCB还保留着&#xff0c;内核在其中还保留着进程的一些信息&#xff1a;如果正常终止&#xff0c;则保留着退出状态&#xff1b;如果异常终止则保存着导致进程种植的信号。 在父…

一键登录云阿里云

免密登录堡垒机 安装oathtool和sshpass 这两个文件安装比较耗费时间&#xff01; brew install oath-toolkit brew install https://raw.githubusercontent.com/kadwanev/bigboybrew/master/Library/Formula/sshpass.rb阿里云item2一键连接 1 #!/bin/bash23 sshpass -p 密码…

linux操作系统进程间通信IPC之管道pipe及FIFO

linux环境下,各进程相互独立&#xff0c;如果想要交换两个进程之间的数据&#xff0c;需要通过内核&#xff0c;在内存中提供一个缓存区&#xff0c;一个进程往缓存区中写数据&#xff0c;一个往缓存区读数据&#xff0c;内核提供的这种机制称为进程间通信&#xff08;IPC&…

MySQL为什么要用数字做自增主键?

1.MySQL为什么要用数字做自增主键&#xff1f; 首先为什么我们使用的是int类型&#xff0c;而不是varchar类型 int永远是固定的4个字节&#xff0c;而char类型是1~255字节之间 优点 占用空间小&#xff0c;节省CPU开销在使用中&#xff0c;通常会在主键上建立索引&#xff…

linux操作系统进程间通信IPC之共享存储映射

&#xff08;1&#xff09;文件存储映射I/O&#xff08;Memory-mapped I/O&#xff09; 一个磁盘文件与存储空间中的一个缓存区相对应&#xff0c;这样可以在不适合read/write函数的情况下&#xff0c;使用地址&#xff08;指针&#xff09;完成I/O操作。具体实现通过内核指定一…

redis源码剖析(十四)—— dump.rdb文件分析工具

分析rdb文件的工具 安装 git clone https://github.com/sripathikrishnan/redis-rdb-tools.git sudo pip install --upgrade pip sudo pip install python-lzf分析以n开头的key rdb --command justkeyvals --key "n*" /home/kou/redis_tar/redis-3.0-annotated/s…

linux操作系统之信号

&#xff08;1&#xff09;信号的概念 信号的特点&#xff1a;简单&#xff0c;不能携带大量信息&#xff0c;满足某种特定条件才触发。 信号的机制&#xff1b;“软中断”&#xff0c;通过软件方式实现&#xff0c;具有很强的延时性。每个进程收到的信号&#xff0c;都由内核负…

redis源码学习笔记目录

Redis源码分析&#xff08;零&#xff09;学习路径笔记 Redis源码分析&#xff08;一&#xff09;redis.c //redis-server.c Redis源码分析&#xff08;二&#xff09;redis-cli.c Redis源码剖析&#xff08;三&#xff09;——基础数据结构 Redis源码剖析&#xff08;四&…

linux操作系统信号捕捉函数之sigaction用法小结

&#xff08;1&#xff09;sigaction函数&#xff1a;注册一个信号捕捉函数&#xff08;不参与捕捉信号&#xff0c;信号由内核捕捉&#xff09;&#xff0c;并修改原来的信号处理动作 &#xff08;2&#xff09;函数原型及头文件 头文件&#xff1a;#include<signal.h>…

redis源码剖析(十五)——客户端思维导图整理

redis源码剖析&#xff08;十五&#xff09;——客户端执行逻辑结构整理 加载略慢

linux操作系统信号捕捉函数之回调函数小结

&#xff08;1&#xff09;signal 信号捕捉函数&#xff1a;注册一个信号捕捉函数&#xff08;不参与捕捉&#xff0c;那是内核的事情&#xff09; 函数实现&#xff1a; typedef void(*sighandler_t)(int); //声明了一个函数指针&#xff08;代表着一类函数&#xff1a;参…

Redis运维和开发学习笔记-全书思维导图

Redis运维和开发学习笔记-全书思维导图 图片过大&#xff0c;无法上传。 链接:https://pan.baidu.com/s/13pnEMBEdLgjZNOOEAuDvEQ 密码:qhch

linux操作系统之竞态条件(时序竞态)

&#xff08;1&#xff09;时序竞态&#xff1a;前后两次运行同一个程序&#xff0c;出现的结果不同。 &#xff08;2&#xff09;pause函数&#xff1a;使用该函数会造成进程主动挂起&#xff0c;并等待信号唤醒&#xff0c;调用该系统调用的进程会处于阻塞状态&#xff08;主…

linux操作系统之全局异步IO及可重入/不可重入函数

&#xff08;1&#xff09;全局变量异步I/O实现父子进程交替数数 1&#xff09;信号捕捉函数 2&#xff09;main函数实现信号交替 3&#xff09;程序实现 1》创建子进程&#xff0c;父进程等待1s&#xff0c;等待子进程完成捕捉函数注册&#xff08;捕捉信号SIGUSR1&#xff09…

RDB和AOF速度测试

同一台机器测试 Redis3.2 Redis5.0.7 Linux python 3.10.0-693.11.1.el7.x86_64 #1 SMP Mon Dec 4 23:52:40 UTC 2017 x86_64 x86_64 x86_64 GNU/Linux rdb测试步骤 1. 修改redis.conf配置文件 开启rdb(测试aof时&#xff0c;注释掉rdb&#xff0c;并重启redis) # save &quo…

LInux操作系统之SIGCHLD信号

&#xff08;1&#xff09;SIGCHLD产生条件 1&#xff09;子进程终止的时候 2&#xff09;子进程接收到SIGSTOP信号停止时 3&#xff09;子进程处于停止状态&#xff0c;接受到SIGCONT后唤醒 &#xff08;2&#xff09;借助SIGCHLD使用waitpid信号实现父进程对子进程的回收 &a…

rdb和aof到底哪个快

rdb和aof到底哪个快&#xff1f; 大多数情况rdb比aof快&#xff01;取决因素是fsync策略 具体选择aof还是rdb应根据业务场景选择。纠结于两者哪个更快意义不大 测试数据 数据量rdb时间rdb文件大小5000076s1.1M100000197s2.1M150000235s3.1M200000305s4.3M 数据量aof时间ao…