文章目录
- RBD文件载入
- RDB文件分析
- 源码分析
- 核心代码
- rdb文件写入
- rdb写入关键函数rdbSaveObjectType
- rdbSaveStringObjectRaw
- rdbSaveLongLongAsStringObject
为避免数据丢失。将redis中的数据保存到磁盘中,避免数据意外丢失。
RBD文件载入
在redis启动时检测是否有rdb文件,有的话会自动载入。
命令 | 作用 |
---|---|
save | 阻塞服务器进程,知道rbd文件创建完成 |
bgsave | fork子进程,由子进程负责创建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