本文來自@凡趣科技 pesiwang同學的投稿分享,對Redis RDB檔案持久化的內部實現進行了源碼分析。
本文分析源碼基於 Redis 2.4.7 stable 版本。下面是其文章原文:
rdb是redis儲存記憶體資料到磁碟資料的其中一種方式(另一種是AOF)。Rdb的主要原理就是在某個時間點把記憶體中的所有資料的快照儲存一份到磁碟上。在條件達到時通過fork一個子進程把記憶體中的資料寫到一個臨時檔案中來實現儲存資料快照。在所有資料寫完後再把這個臨時檔案用原子函數rename(2)重新命名為目標rdb檔案。這種實現方式充分利用fork的copy on write。
另外一種是通過save命令主動觸發儲存資料快照,這種是阻塞式的,即不會通過產生子進程來進行資料集快照的儲存。 相關配置
save <seconds> <changes>
經過多少秒且多少個key有改變就進行,可以配置多個,只要有一個滿足就進行儲存資料快照到磁碟
rdbcompression yes
儲存資料到rdb檔案時是否進行壓縮,如果不想可以配置成’no’,預設是’yes’,因為壓縮可以減少I/O,當然,壓縮需要消耗一些cpu資源。
dbfilename dump.rdb
快照檔案名稱
dir ./
快照檔案所在的目錄,同時也是AOF檔案所在的目錄 Rdb檔案格式
[註:本節所說的類型,值在沒有特別標明的情況下都是針對rdb檔案來說的] Rdb檔案的整體格式
檔案簽名 | 版本號碼 | 類型 | 值 | 類型 | 值 | … | 類型 | 值
[註:豎線和空格是為了便於閱讀而加入的,rdb檔案中是沒有豎線和空格分隔的]
檔案簽名是字串:REDIS 版本號碼是字串:0002 類型是指值的類型,redis值的類型有很多種,下邊一一介紹 值是對應的類型下的值,不同類型的值格式不一樣。這裡的值包含了redis中的key與val。而不是單指redis中val。 REDIS_SELECTDB類型與REDIS_EOF類型 REDIS_SELECTDB類型:對應的值是redis db的編號,從0開始到比db數小1的數值。redis中可以配置db數,每個key只屬於一個db。 儲存redis db的編號時使用的是儲存長度時使用的格式,為了盡量壓縮rdb檔案,儲存長度使用的位元組數是不一樣的,具體見下邊rdb中長度的儲存 REDIS_EOF類型:沒有對應的值。rdb檔案的結束符。
把這REDIS_SELECTDB類型和REDIS_EOF類型代入到上邊的rdb檔案的格式中,那麼rdb檔案的整體格式變成為:
檔案簽名 | 版本號碼 | REDIS_SELECTDB類型 | db編號 | 類型 | 值 | … | REDIS_SELECTD 類型 | db編號 | 類型 | 值 | … | REDIS_EOF類型 每個db編號後邊到下一個REDIS_SELECTDB類型出現之前的資料都是該db下邊的key和value的資料
相關代碼
Rdb.c:394
int rdbSave(char *filename) { … fp = fopen(tmpfile,"w"); if (!fp) { redisLog(REDIS_WARNING, "Failed saving the DB: %s", strerror(errno)); return REDIS_ERR; } if (fwrite("REDIS0002",9,1,fp) == 0) goto werr; for (j = 0; j < server.dbnum; j ) { … /* Write the SELECT DB opcode */ if (rdbSaveType(fp,REDIS_SELECTDB) == -1) goto werr; if (rdbSaveLen(fp,j) == -1) goto werr; /* Iterate this DB writing every entry */ while((de = dictNext(di)) != NULL) { … initStaticStringObject(key,keystr); expiretime = getExpire(db,&key); /* Save the expire time */ if (expiretime != -1) { /* If this key is already expired skip it */ if (expiretime < now) continue; if (rdbSaveType(fp,REDIS_EXPIRETIME) == -1) goto werr; if (rdbSaveTime(fp,expiretime) == -1) goto werr; } /* Save the key and associated value. This requires special * handling if the value is swapped out. */ if (!server.vm_enabled || o->storage == REDIS_VM_MEMORY || o->storage == REDIS_VM_SWAPPING) { int otype = getObjectSaveType(o); /* Save type, key, value */ if (rdbSaveType(fp,otype) == -1) goto werr; if (rdbSaveStringObject(fp,&key) == -1) goto werr; if (rdbSaveObject(fp,o) == -1) goto werr; } else { /* REDIS_VM_SWAPPED or REDIS_VM_LOADING */ robj *po; /* Get a preview of the object in memory */ po = vmPreviewObject(o); /* Save type, key, value */ if (rdbSaveType(fp,getObjectSaveType(po)) == -1) goto werr; if (rdbSaveStringObject(fp,&key) == -1) goto werr; if (rdbSaveObject(fp,po) == -1) goto werr; /* Remove the loaded object from memory */ decrRefCount(po); } } dictReleaseIterator(di); } /* EOF opcode */ if (rdbSaveType(fp,REDIS_EOF) == -1) goto werr; …}
Rdb中長度的儲存
Redis為了盡量壓縮rdb檔案真是費盡心思,先來看看redis為了壓縮使用的長度儲存。長度主要用在字串長度,鏈表長度,hash表的大小儲存上。
Redis把長度的儲存分為四種,最左邊位元組的從左至右的前兩位用於區分長度的儲存類型。 類型位表示 類型整型表示 佔用位元組數 類型解析 00 0 1 當長度能用6位表示使用此類型 01 1 2 當長度不能用6位表示且能用14位表示使用此類型 10 2 5 當長度不能用14位表示且能用32位表示使用此類型
相關代碼
Rdb.c:31
int rdbSaveLen(FILE *fp, uint32_t len) { unsigned char buf[2]; int nwritten; if (len < (1<<6)) { /* Save a 6 bit len */ buf[0] = (len&0xFF)|(REDIS_RDB_6BITLEN<<6); if (rdbWriteRaw(fp,buf,1) == -1) return -1; nwritten = 1; } else if (len < (1<<14)) { /* Save a 14 bit len */ buf[0] = ((len>>8)&0xFF)|(REDIS_RDB_14BITLEN<<6); buf[1] = len&0xFF; if (rdbWriteRaw(fp,buf,2) == -1) return -1; nwritten = 2; } else { /* Save a 32 bit len */ buf[0] = (REDIS_RDB_32BITLEN<<6); if (rdbWriteRaw(fp,buf,1) == -1) return -1; len = htonl(len); if (rdbWriteRaw(fp,&len,4) == -1) return -1; nwritten = 1 4; } return nwritten;}
也許你發現了,上邊的表格中只有3種,還有一種哪去了呢。
把這種特別放開是因為這種比較特殊 類型位表示 類型整型表示 位元組後6位含義 類型解析 11 3 編碼類別型 如果字串是通過編碼後儲存的,則儲存長度的類型的位表示為11,然後根據後6位的編碼類別型來確定怎樣讀取和解析接下來的資料
是不是覺得這種長度類型很奇怪,為什麼要這樣做。
Redis在兩種情況下需要對儲存的內容進行編碼
1.把字串轉成整數儲存
比如:‘-100’需要4個位元組儲存,轉換整數只需要一個位元組
相關函數rdbTryIntegerEncoding(rdb.c:88)
2.使用lzf演算法壓縮字串
相關函數lzf_compress(lzf_c.c:99),lzf的演算法解釋見lzf字串壓縮演算法
當redis使用這兩種編碼對字串進行編碼時,在讀取時需要區分改字串有沒有被編碼過,對編碼過的字串需要特別處理,因為長度資訊是儲存在字串的前面得,所以可以通過在儲存長度的位置上加入編碼類別型的資訊。
我們來看看相關代碼
Rdb.c:557
uint32_t rdbLoadLen(FILE *fp, int *isencoded) { unsigned char buf[2]; uint32_t len; int type; if (isencoded) *isencoded = 0; if (fread(buf,1,1,fp) == 0) return REDIS_RDB_LENERR; type = (buf[0]&0xC0)>>6; if (type == REDIS_RDB_6BITLEN) { /* Read a 6 bit len */ return buf[0]&0x3F; } else if (type == REDIS_RDB_ENCVAL) { /* Read a 6 bit len encoding type */ if (isencoded) *isencoded = 1; return buf[0]&0x3F; } else if (type == REDIS_RDB_14BITLEN) { /* Read a 14 bit len */ if (fread(buf 1,1,1,fp) == 0) return REDIS_RDB_LENERR; return ((buf[0]&0x3F)<<8)|buf[1]; } else { /* Read a 32 bit len */ if (fread(&len,4,1,fp) == 0) return REDIS_RDB_LENERR; return ntohl(len); }}
我們可以看到,在讀取rdb檔案時,當發現長度類型是REDIS_RDB_ENCVAL,把編碼類別型返回。
我們來看看知道編碼類別型後的處理
Rdb.c:633
robj *rdbGenericLoadStringObject(FILE*fp, int encode) { int isencoded; uint32_t len; sds val; len = rdbLoadLen(fp,&isencoded); if (isencoded) { switch(len) { case REDIS_RDB_ENC_INT8: case REDIS_RDB_ENC_INT16: case REDIS_RDB_ENC_INT32: return rdbLoadIntegerObject(fp,len,encode); case REDIS_RDB_ENC_LZF: return rdbLoadLzfStringObject(fp); default: redisPanic("Unknown RDB encoding type"); } } if (len == REDIS_RDB_LENERR) return NULL; val = sdsnewlen(NULL,len); if (len && fread(val,len,1,fp) == 0) { sdsfree(val); return NULL; } return createObject(REDIS_STRING,val);}
讀取長度 如果長度類型是有編碼資訊的,則根據編碼類別型進行讀取 如果長度類型是有效長度,則根據長度資訊讀取字串 REDIS_EXPIRETIME類型 如果一個key被expire設定過,那麼在該key與value的前面會有一個REDIS_EXPIRETIME類型與其對應的值。 REDIS_EXPIRETIME類型對應的值是到期時間點的timestamp REDIS_EXPIRETIME類型與其值是可選的,不是必須的,只有被expire設定過的key才有這個值
假設有一個key被expire命令設定過,把這REDIS_EXPIRETIME類型代入到上邊的rdb檔案的格式中,那麼rdb檔案的整體格式變成為:
檔案簽名 | 版本號碼 | REDIS_SELECTDB類型 | db編號 | REDIS_EXPIRETIME類型 | timestamp | 類型 | 值 | … | REDIS_SELECTD 類型 | db編號 | 類型 | 值 | … | REDIS_EOF類型 資料類型
資料類型主要有以下類型: REDIS_STRING類型 REDIS_LIST類型 REDIS_SET類型 REDIS_ZSET類型 REDIS_HASH類型 REDIS_VMPOINTER類型 REDIS_HASH_ZIPMAP類型 REDIS_LIST_ZIPLIST類型 REDIS_SET_INTSET類型 REDIS_ZSET_ZIPLIST類型
其中REDIS_HASH_ZIPMAP,REDIS_LIST_ZIPLIST,REDIS_SET_INTSET和REDIS_ZSET_ZIPLIST這四種資料類型都是只在rdb檔案中才有的類型,其他的資料類型其實就是val對象中type欄位儲存的值。
下邊以REDIS_STRING類型和REDIS_LIST類型為例進行詳解,其他類型都類似
REDIS_STRING類型
假設rdb檔案中有一個值是REDIS_STRING類型,比如執行了一個set mykey myval命令,則在rdb檔案表示為:
REDIS_STRING類型 | 值
其中值包含了key的長度,key的值,val的長度和val的值,把REDIS_STRING類型值的格式代入得:
REDIS_STRING類型 | keylen | mykey | vallen | myval
長度的儲存格式見rdb中長度的儲存
REDIS_LIST類型
1.List
REDIS_LIST | listlen | len | value | len | value
Listlen是鏈表長度
Len是鏈表結點的值value的長度
Value是鏈表結點的值
2.Ziplist
REDIS_ENCODING_ZIPLIST | ziplist
Ziplist就是通過字串來實現的,直接將其儲存於rdb檔案中即可 快照儲存
我們接下來看看具體實現細節
不管是觸發條件滿足後通過fork子進程來儲存快照還是通過save命令來觸發,其實都是調用的同一個函數rdbSave(rdb.c:394)。
先來看看觸發條件滿足後通過fork子進程的實現儲存快照的的實現
在每100ms調用一次的serverCron函數中會對快照儲存的條件進行檢查,如果滿足了則進行快照儲存
Redis.c:604
/* Check if a background saving or AOF rewrite in progress terminated */ if (server.bgsavechildpid != -1 || server.bgrewritechildpid != -1) { int statloc; pid_t pid; if ((pid = wait3(&statloc,WNOHANG,NULL)) != 0) { if (pid == server.bgsavechildpid) { backgroundSaveDoneHandler(statloc); }… updateDictResizePolicy(); } } else { time_t now = time(NULL); /* If there is not a background saving in progress check if * we have to save now */ for (j = 0; j < server.saveparamslen; j ) { struct saveparam *sp = server.saveparams j; if (server.dirty >= sp->changes && now-server.lastsave > sp->seconds) { redisLog(REDIS_NOTICE,"%d changes in %d seconds. Saving…", sp->changes, sp->seconds); rdbSaveBackground(server.dbfilename); break; } }…}
如果後端有寫rdb的子進程或者寫aof的子進程,則檢查rdb子進程是否退出了,如果退出了則進行一些收尾處理,比如更新髒資料計數server.dirty和最近快照儲存時間server.lastsave。 如果後端沒有寫rdb的子進程且沒有寫aof的子進程,則判斷下是否有觸發寫rdb的條件滿足了,如果有條件滿足,則通過調用rdbSaveBackground函數進行快照儲存。
跟著進rdbSaveBackground函數裡邊看看
Rdb.c:499
int rdbSaveBackground(char *filename) { pid_t childpid; long long start; if (server.bgsavechildpid != -1) return REDIS_ERR; if (server.vm_enabled) waitEmptyIOJobsQueue(); server.dirty_before_bgsave = server.dirty; start = ustime(); if ((childpid = fork()) == 0) { /* Child */ if (server.vm_enabled) vmReopenSwapFile(); if (server.ipfd > 0) close(server.ipfd); if (server.sofd > 0) close(server.sofd); if (rdbSave(filename) == REDIS_OK) { _exit(0); } else { _exit(1); } } else { /* Parent */ casino server.stat_fork_time = ustime()-start; if (childpid == -1) { redisLog(REDIS_WARNING,"Can"t save in background: fork: %s", strerror(errno)); return REDIS_ERR; } redisLog(REDIS_NOTICE,"Background saving started by pid %d",childpid); server.bgsavechildpid = childpid; updateDictResizePolicy(); return REDIS_OK; } return REDIS_OK; /* unreached */}
對是否已經有寫rdb的子進程進行了判斷,如果已經有儲存快照的子進程,則返回錯誤。 如果啟動了虛擬記憶體,則等待所有處理換出換入的任務線程退出,如果還有vm任務在處理就會一直迴圈等待。一直到所有換入換出任務都完成且所有vm線程退出。 儲存當前的髒資料計數,當快照儲存完後用於更新當前的髒資料計數(見函數backgroundSaveDoneHandler,rdb.c:1062) 記下目前時間,用於統計fork一個進程需要的時間 Fork一個字進程,子進程調用rdbSave進行快照儲存 父進程統計fork一個子進程消耗的時間: server.stat_fork_time = ustime()-start,這個統計可以通過info命令獲得。 儲存子進程ID和更新增量重雜湊的策略,即此時不應該再進行增量重雜湊,不然大量key的改變可能導致fork的copy-on-write進行大量的寫。
到了這裡我們知道,rdb的快照儲存是通過函數rdbSave函數(rdb.c:394)來實現的。其實save命令也是通過調用這個函數來實現的。我們來簡單看看
Db.c:323
void saveCommand(redisClient *c) { if (server.bgsavechildpid != -1) { addReplyError(c,"Background save already in progress"); return; } if (rdbSave(server.dbfilename) == REDIS_OK) { addReply(c,shared.ok); } else { addReply(c,shared.err); }
最後我們進rdbSave函數看看
rdb.c:394
int rdbSave(char *filename) { ... /* Wait for I/O therads to terminate, just in case this is a * foreground-saving, to avoid seeking the swap file descriptor at the * same time. */ if (server.vm_enabled) waitEmptyIOJobsQueue(); snprintf(tmpfile,256,"temp-%d.rdb", (int) getpid()); fp = fopen(tmpfile,"w"); if (!fp) { redisLog(REDIS_WARNING, "Failed saving the DB: %s", strerror(errno)); return REDIS_ERR; } if (fwrite("REDIS0002",9,1,fp) == 0) 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 */ if (rdbSaveType(fp,REDIS_SELECTDB) == -1) goto werr; if (rdbSaveLen(fp,j) == -1) goto werr; /* Iterate this DB writing every entry */ while((de = dictNext(di)) != NULL) { sds keystr = dictGetEntryKey(de); robj key, *o = dictGetEntryVal(de); time_t expiretime; initStaticStringObject(key,keystr); expiretime = getExpire(db,&key); /* Save the expire time */ if (expiretime != -1) { /* If this key is already expired skip it */ if (expiretime < now) continue; if (rdbSaveType(fp,REDIS_EXPIRETIME) == -1) goto werr; if (rdbSaveTime(fp,expiretime) == -1) goto werr; } /* Save the key and associated value. This requires special * handling if the value is swapped out. */ if (!server.vm_enabled || o->storage == REDIS_VM_MEMORY || o->storage == REDIS_VM_SWAPPING) { int otype = getObjectSaveType(o); /* Save type, key, value */ if (rdbSaveType(fp,otype) == -1) goto werr; if (rdbSaveStringObject(fp,&key) == -1) goto werr; if (rdbSaveObject(fp,o) == -1) goto werr; } else { /* REDIS_VM_SWAPPED or REDIS_VM_LOADING */ robj *po; /* Get a preview of the object in memory */ po = vmPreviewObject(o); /* Save type, key, value */ if (rdbSaveType(fp,getObjectSaveType(po)) == -1) goto werr; if (rdbSaveStringObject(fp,&key) == -1) goto werr; if (rdbSaveObject(fp,po) == -1) goto werr; /* Remove the loaded object from memory */ decrRefCount(po); } } dictReleaseIterator(di); } /* EOF opcode */ if (rdbSaveType(fp,REDIS_EOF) == -1) goto werr; /* Make sure data will not remain on the OS"s output buffers */ fflush(fp); fsync(fileno(fp)); fclose(fp); /* Use RENAME to make sure the DB file is changed atomically only * if the generate DB file is ok. */ 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; server.lastsave = time(NULL); 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;}
對是否有vm線程進行再次判斷,因為如果是通過save命令過來的是沒有判斷過vm線程的。 建立並開啟臨時檔案 寫入檔案簽名“REDIS”和版本號碼“0002” 遍曆所有db中的所有key 對每個key,先判斷是否設定了expireTime, 如果設定了,則儲存expireTime到rdb檔案中。然後判斷該key對應的value是否則記憶體中,如果是在記憶體中,則取出來寫入到rdb檔案中儲存,如果被換出到虛擬記憶體了,則從虛擬記憶體讀取然後寫入到rdb檔案中。 不同類型有有不同的儲存格式,詳細見rdb檔案格式 最後寫入rdb檔案的結束符 關閉檔案並重新命名臨時檔案名稱到正式檔案名稱 更新髒資料計數server.dirty為0和最近寫rdb檔案的時間server.lastsave為目前時間,這個只是在通過save命令觸發的情況下有用。因為如果是通過fork一個子進程來寫rdb檔案的,更新無效,因為更新的是子進程的資料。
如果是通過fork一個子進程來寫rdb檔案(即不是通過save命令觸發的),在寫rdb檔案的過程中,可能又有一些資料被更改了,那此時的髒資料計數server.dirty怎麼更新呢。 redis是怎樣處理的呢?
我們來看看寫rdb的子進程推出時得處理
Redis.c:605
if (server.bgsavechildpid != -1 || server.bgrewritechildpid != -1) { int statloc; pid_t pid; if ((pid = wait3(&statloc,WNOHANG,NULL)) != 0) { if (pid == server.bgsavechildpid) { backgroundSaveDoneHandler(statloc); } else { backgroundRewriteDoneHandler(statloc); } updateDictResizePolicy(); }}
如果捕捉到寫rdb檔案的子進程退出,則調用backgroundSaveDoneHandler進行處理
接著看看backgroundSaveDoneHandler函數
Rdb.c:1062
void backgroundSaveDoneHandler(int statloc) { int exitcode = WEXITSTATUS(statloc); int bysignal = WIFSIGNALED(statloc); if (!bysignal && exitcode == 0) { redisLog(REDIS_NOTICE, "Background saving terminated with success"); server.dirty = server.dirty - server.dirty_before_bgsave; server.lastsave = time(NULL); } else if (!bysignal && exitcode != 0) { redisLog(REDIS_WARNING, "Background saving error"); } else { redisLog(REDIS_WARNING, "Background saving terminated by signal %d", WTERMSIG(statloc)); rdbRemoveTempFile(server.bgsavechildpid); } server.bgsavechildpid = -1; /* Possibly there are slaves waiting for a BGSAVE in order to be served * (the first stage of SYNC is a bulk transfer of dump.rdb) */ updateSlavesWaitingBgsave(exitcode == 0 ? REDIS_OK : REDIS_ERR);}
更新髒資料計數server.dirty為0和最近寫rdb檔案的時間server.lastsave為目前時間 喚醒因為正在儲存快照而等待的slave,關於slave的具體內容,見replication
快照匯入
當redis因為停電或者某些原因掛掉了,此時重啟redis時,我們就需要從rdb檔案中讀取快照檔案,把儲存到rdb檔案中的資料重新匯入到記憶體中。
先來看看啟動時對快照匯入的處理
Redis.c:1717
if (server.appendonly) { if (loadAppendOnlyFile(server.appendfilename) == REDIS_OK) redisLog(REDIS_NOTICE,"DB loaded from append only file: %ld seconds",time(NULL)-start); } else { if (rdbLoad(server.dbfilename) == REDIS_OK) { redisLog(REDIS_NOTICE,"DB loaded from disk: %ld seconds", time(NULL)-start); } else if (errno != ENOENT) { redisLog(REDIS_WARNING,"Fatal error loading the DB. Exiting."); exit(1); } }
如果儲存了AOF檔案,則使用AOF檔案來恢複資料,AOF的具體內容見AOF 如果沒有AOF,則使用rdb檔案恢複資料,調用rdbLoad函數
接著看看rdbLoad函數
Rdb.c:929
int rdbLoad(char *filename) { ... fp = fopen(filename,"r"); if (!fp) { errno = ENOENT; return REDIS_ERR; } if (fread(buf,9,1,fp) == 0) goto eoferr; buf[9] = "\0"; if (memcmp(buf,"REDIS",5) != 0) { fclose(fp); redisLog(REDIS_WARNING,"Wrong signature trying to load DB from file"); errno = EINVAL; return REDIS_ERR; } rdbver = atoi(buf 5); if (rdbver < 1 || rdbver > 2) { fclose(fp); redisLog(REDIS_WARNING,"Can"t handle RDB format version %d",rdbver); errno = EINVAL; return REDIS_ERR; } startLoading(fp); while(1) { robj *key, *val; int force_swapout; expiretime = -1; /* Serve the clients from time to time */ if (!(loops % 1000)) { loadingProgress(ftello(fp)); aeProcessEvents(server.el, AE_FILE_EVENTS|AE_DONT_WAIT); } /* Read type. */ if ((type = rdbLoadType(fp)) == -1) goto eoferr; if (type == REDIS_EXPIRETIME) { if ((expiretime = rdbLoadTime(fp)) == -1) goto eoferr; /* We read the time so we need to read the object type again */ if ((type = rdbLoadType(fp)) == -1) goto eoferr; } if (type == REDIS_EOF) break; /* Handle SELECT DB opcode as a special case */ if (type == REDIS_SELECTDB) { if ((dbid = rdbLoadLen(fp,NULL)) == REDIS_RDB_LENERR) goto eoferr; if (dbid >= (unsigned)server.dbnum) { redisLog(REDIS_WARNING,"FATAL: Data file was created with a Redis server configured to handle more than %d databases. Exiting\n", server.dbnum); exit(1); } db = server.db dbid; continue; } /* Read key */ if ((key = rdbLoadStringObject(fp)) == NULL) goto eoferr; /* Read value */ if ((val = rdbLoadObject(type,fp)) == NULL) goto eoferr; /* Check if the key already expired. This function is used when loading * an RDB file from disk, either at startup, or when an RDB was * received from the master. In the latter case, the master is * responsible for key expiry. If we would expire keys here, the * snapshot taken by the master may not be reflected on the slave. */ if (server.masterhost == NULL && expiretime != -1 && expiretime < now) { decrRefCount(key); decrRefCount(val); continue; } /* Add the new object in the hash table */ dbAdd(db,key,val); /* Set the expire time if needed */ if (expiretime != -1) setExpire(db,key,expiretime); /* Handle swapping while loading big datasets when VM is on */ /* If we detecter we are hopeless about fitting something in memory * we just swap every new key on disk. Directly… * Note that"s important to check for this condition before resorting * to random sampling, otherwise we may try to swap already * swapped keys. */ if (swap_all_values) { dictEntry *de = dictFind(db->dict,key->ptr); /* de may be NULL since the key already expired */ if (de) { vmpointer *vp; val = dictGetEntryVal(de); if (val->refcount == 1 && (vp = vmSwapObjectBlocking(val)) != NULL) dictGetEntryVal(de) = vp; } decrRefCount(key); continue; } decrRefCount(key); /* Flush data on disk once 32 MB of additional RAM are used… */ force_swapout = 0; if ((zmalloc_used_memory() - server.vm_max_memory) > 1024*1024*32) force_swapout = 1; /* If we have still some hope of having some value fitting memory * then we try random sampling. */ if (!swap_all_values && server.vm_enabled && force_swapout) { while (zmalloc_used_memory() > se