系列
redis數(shù)據淘汰原理
redis過期數(shù)據刪除策略
redis server事件模型
redis cluster mget 引發(fā)的討論
redis 3.x windows 集群搭建
redis 命令執(zhí)行過程
redis string底層數(shù)據結構
redis list底層數(shù)據結構
redis hash底層數(shù)據結構
redis set底層數(shù)據結構
redis zset底層數(shù)據結構
redis 客戶端管理
redis 主從同步-slave端
redis 主從同步-master端
redis 主從超時檢測
redis aof持久化
redis rdb持久化
redis 數(shù)據恢復過程
redis TTL實現(xiàn)原理
redis cluster集群建立
redis cluster集群選主
redis的數(shù)據恢復過程
?redis的數(shù)據載入主要是指redis重啟時候恢復數(shù)據的過程少漆,恢復的數(shù)據總共有兩種:
- aof 數(shù)據文件
- rdb 數(shù)據文件
數(shù)據恢復的過程是二選一的過程分扎,也就是如果開啟aof持久化那么就會使用aof文件進行恢復,如果沒有才會選擇rdb文件進行恢復溜腐。
void loadDataFromDisk(void) {
// 記錄開始時間
long long start = ustime();
// AOF 持久化已打開?
if (server.aof_state == REDIS_AOF_ON) {
// 嘗試載入 AOF 文件
if (loadAppendOnlyFile(server.aof_filename) == REDIS_OK)
// 打印載入信息闲坎,并計算載入耗時長度
redisLog(REDIS_NOTICE,"DB loaded from append only file: %.3f seconds",(float)(ustime()-start)/1000000);
// AOF 持久化未打開
} else {
// 嘗試載入 RDB 文件
if (rdbLoad(server.rdb_filename) == REDIS_OK) {
// 打印載入信息崭闲,并計算載入耗時長度
redisLog(REDIS_NOTICE,"DB loaded from disk: %.3f seconds",
(float)(ustime()-start)/1000000);
} else if (errno != ENOENT) {
redisLog(REDIS_WARNING,"Fatal error loading the DB: %s. Exiting.",strerror(errno));
exit(1);
}
}
}
redis aof數(shù)據恢復過程
?整個aof文件載入的過程其實是非常簡單,整體步驟如下:
- 打開aof文件開始循環(huán)讀取
- 根據aof寫入的命令解析redis 命令行
- 通過偽命令行客戶端執(zhí)行解析的命令行
- redis接收到偽客戶端發(fā)送的命令行以后找到命令對應的函數(shù)負責執(zhí)行數(shù)據寫入
?aof保存的命令行格式類似"*3\r\n5\r\nmykey\r\n
字符開始標記字符串長度散劫,知道字符串長度就可以解析出命令字符串了稚机。
int loadAppendOnlyFile(char *filename) {
// 為客戶端
struct redisClient *fakeClient;
// 打開 AOF 文件
FILE *fp = fopen(filename,"r");
struct redis_stat sb;
int old_aof_state = server.aof_state;
long loops = 0;
// 檢查文件的正確性
if (fp && redis_fstat(fileno(fp),&sb) != -1 && sb.st_size == 0) {
server.aof_current_size = 0;
fclose(fp);
return REDIS_ERR;
}
// 檢查文件是否正常打開
if (fp == NULL) {
redisLog(REDIS_WARNING,"Fatal error: can't open the append log file for reading: %s",strerror(errno));
exit(1);
}
/*
*
* 暫時性地關閉 AOF ,防止在執(zhí)行 MULTI 時获搏,
* EXEC 命令被傳播到正在打開的 AOF 文件中赖条。
*/
server.aof_state = REDIS_AOF_OFF;
fakeClient = createFakeClient();
// 設置服務器的狀態(tài)為:正在載入
// startLoading 定義于 rdb.c
startLoading(fp);
while(1) {
int argc, j;
unsigned long len;
robj **argv;
char buf[128];
sds argsds;
struct redisCommand *cmd;
/*
* 間隔性地處理客戶端發(fā)送來的請求
* 因為服務器正處于載入狀態(tài),所以能正常執(zhí)行的只有 PUBSUB 等模塊
*/
if (!(loops++ % 1000)) {
loadingProgress(ftello(fp));
processEventsWhileBlocked();
}
// 讀入文件內容到緩存
if (fgets(buf,sizeof(buf),fp) == NULL) {
if (feof(fp))
// 文件已經讀完常熙,跳出
break;
else
goto readerr;
}
// 確認協(xié)議格式纬乍,比如 *3\r\n
if (buf[0] != '*') goto fmterr;
// 取出命令參數(shù),比如 *3\r\n 中的 3
argc = atoi(buf+1);
// 至少要有一個參數(shù)(被調用的命令)
if (argc < 1) goto fmterr;
// 從文本中創(chuàng)建字符串對象:包括命令症概,以及命令參數(shù)
// 例如 $3\r\nSET\r\n$3\r\nKEY\r\n$5\r\nVALUE\r\n
// 將創(chuàng)建三個包含以下內容的字符串對象:
// SET 蕾额、 KEY 、 VALUE
argv = zmalloc(sizeof(robj*)*argc);
for (j = 0; j < argc; j++) {
if (fgets(buf,sizeof(buf),fp) == NULL) goto readerr;
if (buf[0] != '$') goto fmterr;
// 讀取參數(shù)值的長度
len = strtol(buf+1,NULL,10);
// 讀取參數(shù)值
argsds = sdsnewlen(NULL,len);
if (len && fread(argsds,len,1,fp) == 0) goto fmterr;
// 為參數(shù)創(chuàng)建對象
argv[j] = createObject(REDIS_STRING,argsds);
if (fread(buf,2,1,fp) == 0) goto fmterr; /* discard CRLF */
}
/* Command lookup
*
* 查找命令
*/
cmd = lookupCommand(argv[0]->ptr);
if (!cmd) {
redisLog(REDIS_WARNING,"Unknown command '%s' reading the append only file", (char*)argv[0]->ptr);
exit(1);
}
/*
* 調用偽客戶端彼城,執(zhí)行命令
*/
fakeClient->argc = argc;
fakeClient->argv = argv;
cmd->proc(fakeClient);
/* The fake client should not have a reply */
redisAssert(fakeClient->bufpos == 0 && listLength(fakeClient->reply) == 0);
/* The fake client should never get blocked */
redisAssert((fakeClient->flags & REDIS_BLOCKED) == 0);
/*
* 清理命令和命令參數(shù)對象
*/
for (j = 0; j < fakeClient->argc; j++)
decrRefCount(fakeClient->argv[j]);
zfree(fakeClient->argv);
}
/*
* 如果能執(zhí)行到這里诅蝶,說明 AOF 文件的全部內容都可以正確地讀取,
* 但是募壕,還要檢查 AOF 是否包含未正確結束的事務
*/
if (fakeClient->flags & REDIS_MULTI) goto readerr;
// 關閉 AOF 文件
fclose(fp);
// 釋放偽客戶端
freeFakeClient(fakeClient);
// 復原 AOF 狀態(tài)
server.aof_state = old_aof_state;
// 停止載入
stopLoading();
// 更新服務器狀態(tài)中调炬, AOF 文件的當前大小
aofUpdateCurrentSize();
// 記錄前一次重寫時的大小
server.aof_rewrite_base_size = server.aof_current_size;
return REDIS_OK;
// 讀入錯誤
readerr:
// 非預期的末尾,可能是 AOF 文件在寫入的中途遭遇了停機
if (feof(fp)) {
redisLog(REDIS_WARNING,"Unexpected end of file reading the append only file");
// 文件內容出錯
} else {
redisLog(REDIS_WARNING,"Unrecoverable error reading the append only file: %s", strerror(errno));
}
exit(1);
// 內容格式錯誤
fmterr:
redisLog(REDIS_WARNING");
exit(1);
}
redis rdb數(shù)據恢復過程
?整個rdb文件載入的過程其實是非常簡單舱馅,不過和aof有些許差別:
rdb文件的數(shù)據恢復直接寫入內存而不是通過偽裝命令行執(zhí)行命令生成的
rdb文件的讀取過程和aof不一樣缰泡,rdb文件存儲按照type+key+value的格式存儲所以讀取也是這樣讀取的
整體恢復步驟如下:
- 打開rdb文件開始恢復數(shù)據
- 讀取type用于判斷讀取value的格式
- 讀取key且key的第一個字節(jié)標明了key的長度所以可以讀取準確長度的key
- 讀取value對象,讀取過程根據type進行讀取以及恢復
/*
* 將給定 rdb 中保存的數(shù)據載入到數(shù)據庫中。
*/
int rdbLoad(char *filename) {
uint32_t dbid;
int type, rdbver;
redisDb *db = server.db+0;
char buf[1024];
long long expiretime, now = mstime();
FILE *fp;
rio rdb;
// 打開 rdb 文件
if ((fp = fopen(filename,"r")) == NULL) return REDIS_ERR;
// 初始化寫入流
rioInitWithFile(&rdb,fp);
rdb.update_cksum = rdbLoadProgressCallback;
rdb.max_processing_chunk = server.loading_process_events_interval_bytes;
if (rioRead(&rdb,buf,9) == 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 > REDIS_RDB_VERSION) {
fclose(fp);
redisLog(REDIS_WARNING,"Can't handle RDB format version %d",rdbver);
errno = EINVAL;
return REDIS_ERR;
}
// 將服務器狀態(tài)調整到開始載入狀態(tài)
startLoading(fp);
while(1) {
robj *key, *val;
expiretime = -1;
/* Read type.
*
* 讀入類型指示棘钞,決定該如何讀入之后跟著的數(shù)據缠借。
*
* 這個指示可以是 rdb.h 中定義的所有以
* REDIS_RDB_TYPE_* 為前綴的常量的其中一個
* 或者所有以 REDIS_RDB_OPCODE_* 為前綴的常量的其中一個
*/
if ((type = rdbLoadType(&rdb)) == -1) goto eoferr;
// 讀入過期時間值
if (type == REDIS_RDB_OPCODE_EXPIRETIME) {
// 以秒計算的過期時間
if ((expiretime = rdbLoadTime(&rdb)) == -1) goto eoferr;
/* We read the time so we need to read the object type again.
*
* 在過期時間之后會跟著一個鍵值對,我們要讀入這個鍵值對的類型
*/
if ((type = rdbLoadType(&rdb)) == -1) goto eoferr;
/* the EXPIRETIME opcode specifies time in seconds, so convert
* into milliseconds.
*
* 將格式轉換為毫秒*/
expiretime *= 1000;
} else if (type == REDIS_RDB_OPCODE_EXPIRETIME_MS) {
// 以毫秒計算的過期時間
/* Milliseconds precision expire times introduced with RDB
* version 3. */
if ((expiretime = rdbLoadMillisecondTime(&rdb)) == -1) goto eoferr;
/* We read the time so we need to read the object type again.
*
* 在過期時間之后會跟著一個鍵值對宜猜,我們要讀入這個鍵值對的類型
*/
if ((type = rdbLoadType(&rdb)) == -1) goto eoferr;
}
// 讀入數(shù)據 EOF (不是 rdb 文件的 EOF)
if (type == REDIS_RDB_OPCODE_EOF)
break;
/*
* 讀入切換數(shù)據庫指示
*/
if (type == REDIS_RDB_OPCODE_SELECTDB) {
// 讀入數(shù)據庫號碼
if ((dbid = rdbLoadLen(&rdb,NULL)) == REDIS_RDB_LENERR)
goto eoferr;
// 檢查數(shù)據庫號碼的正確性
if (dbid >= (unsigned)server.dbnum) {
redisLog(REDIS_WARNING,"FATAL: ", server.dbnum);
exit(1);
}
// 在程序內容切換數(shù)據庫
db = server.db+dbid;
// 跳過
continue;
}
/* Read key
*
* 讀入鍵
*/
if ((key = rdbLoadStringObject(&rdb)) == NULL) goto eoferr;
/* Read value
*
* 讀入值
*/
if ((val = rdbLoadObject(type,&rdb)) == NULL) goto eoferr;
/*
*
* 如果服務器為主節(jié)點的話泼返,
* 那么在鍵已經過期的時候,不再將它們關聯(lián)到數(shù)據庫中去
*/
if (server.masterhost == NULL && expiretime != -1 && expiretime < now) {
decrRefCount(key);
decrRefCount(val);
// 跳過
continue;
}
/* Add the new object in the hash table
*
* 將鍵值對關聯(lián)到數(shù)據庫中
*/
dbAdd(db,key,val);
/* Set the expire time if needed
*
* 設置過期時間
*/
if (expiretime != -1) setExpire(db,key,expiretime);
decrRefCount(key);
}
/* Verify the checksum if RDB version is >= 5
*
* 如果 RDB 版本 >= 5 姨拥,那么比對校驗和
*/
if (rdbver >= 5 && server.rdb_checksum) {
uint64_t cksum, expected = rdb.cksum;
// 讀入文件的校驗和
if (rioRead(&rdb,&cksum,8) == 0) goto eoferr;
memrev64ifbe(&cksum);
// 比對校驗和
if (cksum == 0) {
redisLog(REDIS_WARNING,"RDB file was saved with checksum disabled: no check performed.");
} else if (cksum != expected) {
redisLog(REDIS_WARNING,"Wrong RDB checksum. Aborting now.");
exit(1);
}
}
// 關閉 RDB
fclose(fp);
// 服務器從載入狀態(tài)中退出
stopLoading();
return REDIS_OK;
eoferr: /* unexpected end of file is handled here with a fatal exit */
redisLog(REDIS_WARNING,"Short read or OOM loading DB. Unrecoverable error, aborting now.");
exit(1);
return REDIS_ERR; /* Just to avoid warning */
}