文章目錄
- 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