本文共 10303 字,大约阅读时间需要 34 分钟。
为避免数据丢失。将redis中的数据保存到磁盘中,避免数据意外丢失。
在redis启动时检测是否有rdb文件,有的话会自动载入。
命令 | 作用 |
---|---|
save | 阻塞服务器进程,知道rbd文件创建完成 |
bgsave | fork子进程,由子进程负责创建RDB文件 |
[root@python redis-4.0.14]# od -c dump.rdb0000000 R E D I S 0 0 0 8 372 \t r e d i s0000020 - v e r 006 4 . 0 . 1 4 372 \n r e d0000040 i s - b i t s 300 @ 372 005 c t i m e0000060 302 212 \b 331 ] 372 \b u s e d - m e m 3020000100 210 \0 \f \0 372 \f a o f - p r e a m b0000120 l e 300 \0 376 \0 373 001 \0 \0 004 n a m e 3000000140 { 377 8 033 _ 360 I 223 254 3430000152[root@python redis-4.0.14]# redis-cli127.0.0.1:6379> flushallOK127.0.0.1:6379> set name xxxxOK127.0.0.1:6379> saveOK127.0.0.1:6379> exit[root@python redis-4.0.14]# od -c dump.rdb0000000 R E D I S 0 0 0 8 372 \t r e d i s0000020 - v e r 006 4 . 0 . 1 4 372 \n r e d0000040 i s - b i t s 300 @ 372 005 c t i m e0000060 302 b | 333 ] 372 \b u s e d - m e m 3020000100 0 356 \f \0 372 \f a o f - p r e a m b0000120 l e 300 \0 376 \0 373 001 \0 \0 004 n a m e 0040000140 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)
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 }
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 }
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
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
转载地址:http://nkorb.baihongyu.com/