系列
redis數(shù)據(jù)淘汰原理
redis過期數(shù)據(jù)刪除策略
redis server事件模型
redis cluster mget 引發(fā)的討論
redis 3.x windows 集群搭建
redis 命令執(zhí)行過程
redis string底層數(shù)據(jù)結構
redis list底層數(shù)據(jù)結構
redis hash底層數(shù)據(jù)結構
redis set底層數(shù)據(jù)結構
redis zset底層數(shù)據(jù)結構
redis 客戶端管理
redis 主從同步-slave端
redis 主從同步-master端
redis 主從超時檢測
redis aof持久化
redis rdb持久化
redis 數(shù)據(jù)恢復過程
redis TTL實現(xiàn)原理
redis cluster集群建立
redis cluster集群選主
redis rdb和aof持久化的區(qū)別
?關于這兩者的區(qū)別敬尺,網(wǎng)上有很多資料枚尼,這里我只想補充下自己理解的兩個比較核心的點:
- 持久化過程是否異步,rdb持久化是后臺異步進程執(zhí)行砂吞,aof是同步執(zhí)行
- 持久化內容格式署恍,rdb是直接存儲實際內存存儲數(shù)據(jù),aof是轉為redis執(zhí)行命令行存儲
redis rdb持久化過程
?分析redis的rdb持久化過程直接從bgsaveCommand命令的執(zhí)行過程開始分析
- 首先不能同時執(zhí)行多個bgsave命令或同時執(zhí)行bgrewriteaof命令
- 其次進入后臺fork線程生成rdb文件過程rdbSaveBackground
void bgsaveCommand(redisClient *c) {
// 不能重復執(zhí)行 BGSAVE
if (server.rdb_child_pid != -1) {
addReplyError(c,"Background save already in progress");
// 不能在 BGREWRITEAOF 正在運行時執(zhí)行
} else if (server.aof_child_pid != -1) {
addReplyError(c,"Can't BGSAVE while AOF log rewriting is in progress");
// 執(zhí)行 BGSAVE
} else if (rdbSaveBackground(server.rdb_filename) == REDIS_OK) {
addReplyStatus(c,"Background saving started");
} else {
addReply(c,shared.err);
}
}
?在rdbSaveBackground內部執(zhí)行了fork子進程開始進行rdb的持久化操作蜻直,核心邏輯在執(zhí)行rdbSave(filename)的命令盯质。
int rdbSaveBackground(char *filename) {
pid_t childpid;
long long start;
// 如果 BGSAVE 已經(jīng)在執(zhí)行袁串,那么出錯
if (server.rdb_child_pid != -1) return REDIS_ERR;
// 記錄 BGSAVE 執(zhí)行前的數(shù)據(jù)庫被修改次數(shù)
server.dirty_before_bgsave = server.dirty;
// 最近一次嘗試執(zhí)行 BGSAVE 的時間
server.lastbgsave_try = time(NULL);
// fork() 開始前的時間,記錄 fork() 返回耗時用
start = ustime();
if ((childpid = fork()) == 0) {
int retval;
/* Child */
// 關閉網(wǎng)絡連接 fd
closeListeningSockets(0);
// 設置進程的標題唤殴,方便識別
redisSetProcTitle("redis-rdb-bgsave");
// 執(zhí)行保存操作
retval = rdbSave(filename);
// 打印 copy-on-write 時使用的內存數(shù)
if (retval == REDIS_OK) {
size_t private_dirty = zmalloc_get_private_dirty();
if (private_dirty) {
redisLog(REDIS_NOTICE,
"RDB: %zu MB of memory used by copy-on-write",
private_dirty/(1024*1024));
}
}
// 向父進程發(fā)送信號
exitFromChild((retval == REDIS_OK) ? 0 : 1);
}
// 省略非核心的邏輯
return REDIS_OK;
}
?整個生成rdb文件的核心般婆,整體邏輯如下
- 創(chuàng)建rdb磁盤文件
- 遍歷redis的所有db進行寫入
整個寫入數(shù)據(jù)是將redis內存中的數(shù)據(jù)原封不動的寫入到rdb文件當中,整個寫入過程按照以下順序進行執(zhí)行:
- 通過rdbSaveType方法寫入type
- 通過rdbSaveLen寫入數(shù)據(jù)(或者是下面的集中替代)
- 通過rdbSaveObjectType存儲redis value的數(shù)據(jù)類型
- 通過rdbSaveStringObject存儲redis key的數(shù)據(jù)
- 通過rdbSaveObject存儲redis value的數(shù)據(jù)
整個過程中我們發(fā)現(xiàn)redis就是把實際內存數(shù)據(jù)庫的數(shù)據(jù)dump到rdb文件當中
/*
* 將數(shù)據(jù)庫保存到磁盤上朵逝。
*
* 保存成功返回 REDIS_OK ,出錯/失敗返回 REDIS_ERR 乡范。
*/
int rdbSave(char *filename) {
dictIterator *di = NULL;
dictEntry *de;
char tmpfile[256];
char magic[10];
int j;
long long now = mstime();
FILE *fp;
rio rdb;
uint64_t cksum;
// 創(chuàng)建臨時文件
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);
// 設置校驗和函數(shù)
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;
// 遍歷所有數(shù)據(jù)庫
for (j = 0; j < server.dbnum; j++) {
// 指向數(shù)據(jù)庫
redisDb *db = server.db+j;
// 指向數(shù)據(jù)庫鍵空間
dict *d = db->dict;
// 跳過空數(shù)據(jù)庫
if (dictSize(d) == 0) continue;
// 創(chuàng)建鍵空間迭代器
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
*
* 遍歷數(shù)據(jù)庫配名,并寫入每個鍵值對的數(shù)據(jù)
*/
while((de = dictNext(di)) != NULL) {
sds keystr = dictGetKey(de);
robj key, *o = dictGetVal(de);
long long expire;
// 根據(jù) keystr ,在棧中創(chuàng)建一個 key 對象
initStaticStringObject(key,keystr);
// 獲取鍵的過期時間
expire = getExpire(db,&key);
// 保存鍵值對數(shù)據(jù)
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 校驗和晋辆。
*
* 如果校驗和功能已關閉渠脉,那么 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 */
// 沖洗緩存,確保數(shù)據(jù)已寫入磁盤
if (fflush(fp) == EOF) goto werr;
if (fsync(fileno(fp)) == -1) goto werr;
if (fclose(fp) == EOF) goto werr;
/*
* 使用 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");
// 清零數(shù)據(jù)庫臟狀態(tài)
server.dirty = 0;
// 記錄最后一次完成 SAVE 的時間
server.lastsave = time(NULL);
// 記錄最后一次執(zhí)行 SAVE 的狀態(tài)
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;
}
?將鍵值對的鍵习寸、值、過期時間和類型寫入到 RDB 中
/*
* 將鍵值對的鍵傻工、值霞溪、過期時間和類型寫入到 RDB 中。
*
* 出錯返回 -1 中捆。
*
* On success if the key was actually saved 1 is returned, otherwise 0
* is returned (the key was already expired).
*
* 成功保存返回 1 鸯匹,當鍵已經(jīng)過期時,返回 0 泄伪。
*/
int rdbSaveKeyValuePair(rio *rdb, robj *key, robj *val,
long long expiretime, long long now)
{
/* Save the expire time
*
* 保存鍵的過期時間
*/
if (expiretime != -1) {
/* If this key is already expired skip it
*
* 不寫入已經(jīng)過期的鍵
*/
if (expiretime < now) return 0;
if (rdbSaveType(rdb,REDIS_RDB_OPCODE_EXPIRETIME_MS) == -1) return -1;
if (rdbSaveMillisecondTime(rdb,expiretime) == -1) return -1;
}
/* Save type, key, value
*
* 保存類型殴蓬,鍵,值
*/
if (rdbSaveObjectType(rdb,val) == -1) return -1;
if (rdbSaveStringObject(rdb,key) == -1) return -1;
if (rdbSaveObject(rdb,val) == -1) return -1;
return 1;
}
?將鍵值對的值類型寫入到 rdb 中
/*
* 將對象 o 的類型寫入到 rdb 中
*/
int rdbSaveObjectType(rio *rdb, robj *o) {
switch (o->type) {
case REDIS_STRING:
return rdbSaveType(rdb,REDIS_RDB_TYPE_STRING);
case REDIS_LIST:
if (o->encoding == REDIS_ENCODING_ZIPLIST)
return rdbSaveType(rdb,REDIS_RDB_TYPE_LIST_ZIPLIST);
else if (o->encoding == REDIS_ENCODING_LINKEDLIST)
return rdbSaveType(rdb,REDIS_RDB_TYPE_LIST);
else
redisPanic("Unknown list encoding");
case REDIS_SET:
if (o->encoding == REDIS_ENCODING_INTSET)
return rdbSaveType(rdb,REDIS_RDB_TYPE_SET_INTSET);
else if (o->encoding == REDIS_ENCODING_HT)
return rdbSaveType(rdb,REDIS_RDB_TYPE_SET);
else
redisPanic("Unknown set encoding");
case REDIS_ZSET:
if (o->encoding == REDIS_ENCODING_ZIPLIST)
return rdbSaveType(rdb,REDIS_RDB_TYPE_ZSET_ZIPLIST);
else if (o->encoding == REDIS_ENCODING_SKIPLIST)
return rdbSaveType(rdb,REDIS_RDB_TYPE_ZSET);
else
redisPanic("Unknown sorted set encoding");
case REDIS_HASH:
if (o->encoding == REDIS_ENCODING_ZIPLIST)
return rdbSaveType(rdb,REDIS_RDB_TYPE_HASH_ZIPLIST);
else if (o->encoding == REDIS_ENCODING_HT)
return rdbSaveType(rdb,REDIS_RDB_TYPE_HASH);
else
redisPanic("Unknown hash encoding");
default:
redisPanic("Unknown object type");
}
return -1; /* avoid warning */
}
?將給定的字符串對象 obj 保存到 rdb 中臂容,我們的key就是通過這個方法保存的
/*
* 將給定的字符串對象 obj 保存到 rdb 中科雳。
*
* 函數(shù)返回 rdb 保存字符串對象所需的字節(jié)數(shù)。
*
* p.s. 代碼原本的注釋 rdbSaveStringObjectRaw() 函數(shù)已經(jīng)不存在了脓杉。
*/
int rdbSaveStringObject(rio *rdb, robj *obj) {
/* Avoid to decode the object, then encode it again, if the
* object is already integer encoded. */
// 嘗試對 INT 編碼的字符串進行特殊編碼
if (obj->encoding == REDIS_ENCODING_INT) {
return rdbSaveLongLongAsStringObject(rdb,(long)obj->ptr);
// 保存 STRING 編碼的字符串
} else {
redisAssertWithInfo(NULL,obj,sdsEncodedObject(obj));
return rdbSaveRawString(rdb,obj->ptr,sdslen(obj->ptr));
}
}
?將給定對象 o 保存到 rdb 中糟秘。
/*
* 將給定對象 o 保存到 rdb 中。
*
* 保存成功返回 rdb 保存該對象所需的字節(jié)數(shù) 球散,失敗返回 0 尿赚。
*
* p.s.上面原文注釋所說的返回值是不正確的
*/
int rdbSaveObject(rio *rdb, robj *o) {
int n, nwritten = 0;
// 保存字符串對象
if (o->type == REDIS_STRING) {
/* Save a string value */
if ((n = rdbSaveStringObject(rdb,o)) == -1) return -1;
nwritten += n;
// 保存列表對象
} else if (o->type == REDIS_LIST) {
/* Save a list value */
if (o->encoding == REDIS_ENCODING_ZIPLIST) {
size_t l = ziplistBlobLen((unsigned char*)o->ptr);
// 以字符串對象的形式保存整個 ZIPLIST 列表
if ((n = rdbSaveRawString(rdb,o->ptr,l)) == -1) return -1;
nwritten += n;
} else if (o->encoding == REDIS_ENCODING_LINKEDLIST) {
list *list = o->ptr;
listIter li;
listNode *ln;
if ((n = rdbSaveLen(rdb,listLength(list))) == -1) return -1;
nwritten += n;
// 遍歷所有列表項
listRewind(list,&li);
while((ln = listNext(&li))) {
robj *eleobj = listNodeValue(ln);
// 以字符串對象的形式保存列表項
if ((n = rdbSaveStringObject(rdb,eleobj)) == -1) return -1;
nwritten += n;
}
} else {
redisPanic("Unknown list encoding");
}
// 保存集合對象
} else if (o->type == REDIS_SET) {
/* Save a set value */
if (o->encoding == REDIS_ENCODING_HT) {
dict *set = o->ptr;
dictIterator *di = dictGetIterator(set);
dictEntry *de;
if ((n = rdbSaveLen(rdb,dictSize(set))) == -1) return -1;
nwritten += n;
// 遍歷集合成員
while((de = dictNext(di)) != NULL) {
robj *eleobj = dictGetKey(de);
// 以字符串對象的方式保存成員
if ((n = rdbSaveStringObject(rdb,eleobj)) == -1) return -1;
nwritten += n;
}
dictReleaseIterator(di);
} else if (o->encoding == REDIS_ENCODING_INTSET) {
size_t l = intsetBlobLen((intset*)o->ptr);
// 以字符串對象的方式保存整個 INTSET 集合
if ((n = rdbSaveRawString(rdb,o->ptr,l)) == -1) return -1;
nwritten += n;
} else {
redisPanic("Unknown set encoding");
}
// 保存有序集對象
} else if (o->type == REDIS_ZSET) {
/* Save a sorted set value */
if (o->encoding == REDIS_ENCODING_ZIPLIST) {
size_t l = ziplistBlobLen((unsigned char*)o->ptr);
// 以字符串對象的形式保存整個 ZIPLIST 有序集
if ((n = rdbSaveRawString(rdb,o->ptr,l)) == -1) return -1;
nwritten += n;
} else if (o->encoding == REDIS_ENCODING_SKIPLIST) {
zset *zs = o->ptr;
dictIterator *di = dictGetIterator(zs->dict);
dictEntry *de;
if ((n = rdbSaveLen(rdb,dictSize(zs->dict))) == -1) return -1;
nwritten += n;
// 遍歷有序集
while((de = dictNext(di)) != NULL) {
robj *eleobj = dictGetKey(de);
double *score = dictGetVal(de);
// 以字符串對象的形式保存集合成員
if ((n = rdbSaveStringObject(rdb,eleobj)) == -1) return -1;
nwritten += n;
// 成員分值(一個雙精度浮點數(shù))會被轉換成字符串
// 然后保存到 rdb 中
if ((n = rdbSaveDoubleValue(rdb,*score)) == -1) return -1;
nwritten += n;
}
dictReleaseIterator(di);
} else {
redisPanic("Unknown sorted set encoding");
}
// 保存哈希表
} else if (o->type == REDIS_HASH) {
/* Save a hash value */
if (o->encoding == REDIS_ENCODING_ZIPLIST) {
size_t l = ziplistBlobLen((unsigned char*)o->ptr);
// 以字符串對象的形式保存整個 ZIPLIST 哈希表
if ((n = rdbSaveRawString(rdb,o->ptr,l)) == -1) return -1;
nwritten += n;
} else if (o->encoding == REDIS_ENCODING_HT) {
dictIterator *di = dictGetIterator(o->ptr);
dictEntry *de;
if ((n = rdbSaveLen(rdb,dictSize((dict*)o->ptr))) == -1) return -1;
nwritten += n;
// 迭代字典
while((de = dictNext(di)) != NULL) {
robj *key = dictGetKey(de);
robj *val = dictGetVal(de);
// 鍵和值都以字符串對象的形式來保存
if ((n = rdbSaveStringObject(rdb,key)) == -1) return -1;
nwritten += n;
if ((n = rdbSaveStringObject(rdb,val)) == -1) return -1;
nwritten += n;
}
dictReleaseIterator(di);
} else {
redisPanic("Unknown hash encoding");
}
} else {
redisPanic("Unknown object type");
}
return nwritten;
}