Article Directory
To avoid data loss. Redis save data to disk, avoiding accidental loss of data.
RBD files are loaded
When redis start to detect whether there rdb file, any, will be automatically loaded.
command | effect |
---|---|
save | Blocking server processes, know rbd file is created |
bgsave | fork child process, the child process is responsible for creating RDB file |
RDB file analysis
[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
-
Redis. 8 0 0 0 0000000 372 \ tredis
. 1, five byte Redis
2, four-byte version
3, a constant eof byte
4, eight-byte checksum -
0.00014 million XXXX name 004 377 004 314 "221 277 [223,026 $
004 key length of
377 314" 221 277 [$ 223,026 eight-byte long checksum
Source code analysis
Save the DB on disk. Return REDIS_ERR on error, REDIS_OK on success
will be saved to the database on disk.
Save successful return REDIS_OK, error / failure to return 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;
}
Core code
// 遍历所有数据库
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);
}
- Get the key and key
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 file write
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 write key function 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