文章目录
为避免数据丢失。将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/O
rioInitWithFile(&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 time
895 *
896 * 保存键的过期时间
897 */
898 if (expiretime != -1) {
899 /* If this key is already expired skip it
900 *
901 * 不写入已经过期的键
902 */
903 if (expiretime < now) return 0;
904
905 if (rdbSaveType(rdb,REDIS_RDB_OPCODE_EXPIRETIME_MS) == -1) return -1;
906 if (rdbSaveMillisecondTime(rdb,expiretime) == -1) return -1;
907 }
908
909 /* Save type, key, value
910 *
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;
916
917 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) {
660
661 switch (o->type) {
662
663 case REDIS_STRING:
664 return rdbSaveType(rdb,REDIS_RDB_TYPE_STRING);
665
666 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 else
672 redisPanic("Unknown list encoding");
673
674 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 else
680 redisPanic("Unknown set encoding");
681
682 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 else
688 redisPanic("Unknown sorted set encoding");
689
690 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 else
696 redisPanic("Unknown hash encoding");
697
698 default:
699 redisPanic("Unknown object type");
700 }
701
702 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) {
502
503 /* Avoid to decode the object, then encode it again, if the
504 * object is already integer encoded. */
505 // 尝试对 INT 编码的字符串进行特殊编码
506 if (obj->encoding == REDIS_ENCODING_INT) {
507 return rdbSaveLongLongAsStringObject(rdb,(long)obj->ptr);
508
509 // 保存 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;
464
465 // 尝试以节省空间的方式编码整数值 value
466 int enclen = rdbEncodeInteger(value,buf);
467
468 // 编码成功,直接写入编码后的缓存
469 // 比如,值 1 可以编码为 11 00 0001
470 if (enclen > 0) {
471 return rdbWriteRaw(rdb,buf,enclen);
472
473 // 编码失败,将整数值转换成对应的字符串来保存
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 }
488
489 // 返回长度
490 return nwritten;
491 }
492