redisaof持久化
redis aof快取資料結構
redis用於儲存aof記憶體資料的資料結構是aof_buf資料結構,所有資料先追加到記憶體的aof_buf後,再通過定時任務檢查是否能夠持久化到磁碟檔案當中。
struct redisServer {
// AOF 緩衝區
sds aof_buf; /* AOF buffer, written before entering the event loop */
redis aof記憶體化
redis aof記憶體化的操作主要有以下三部曲:
- 執行redis命令後開始儲存資料至記憶體當中的aof_buf當中
- 將執行的命令解析成redis的命令格式// 例如 $3
SET
$3
KEY
$5
VALUE - 儲存資料至aof_buf當中
/*
* 將指定命令(以及執行該命令的上下文,比如資料庫 id 等資訊)傳播到 AOF 和 slave 。
* FLAG 可以是以下標識的 xor :
* + REDIS_PROPAGATE_NONE (no propagation of command at all)
* 不傳播
* + REDIS_PROPAGATE_AOF (propagate into the AOF file if is enabled)
* 傳播到 AOF
* + REDIS_PROPAGATE_REPL (propagate into the replication link)
* 傳播到 slave
*/
void propagate(struct redisCommand *cmd, int dbid, robj **argv, int argc,
int flags)
{
// 傳播到 AOF
if (server.aof_state != REDIS_AOF_OFF && flags & REDIS_PROPAGATE_AOF)
feedAppendOnlyFile(cmd,dbid,argv,argc);
}
執行的操作主要是解析成redis的命令格式並儲存到記憶體的aof_buf當中。
/*
* 將命令追加到 AOF 檔案中,
* 如果 AOF 重寫正在進行,那麼也將命令追加到 AOF 重寫快取中。
*/
void feedAppendOnlyFile(struct redisCommand *cmd, int dictid, robj **argv, int argc) {
sds buf = sdsempty();
robj *tmpargv[3];
/*
* 使用 SELECT 命令,顯式設定資料庫,確保之後的命令被設定到正確的資料庫
*/
if (dictid != server.aof_selected_db) {
char seldb[64];
snprintf(seldb,sizeof(seldb),"%d",dictid);
buf = sdscatprintf(buf,"*2
$6
SELECT
$%lu
%s
",
(unsigned long)strlen(seldb),seldb);
server.aof_selected_db = dictid;
}
// EXPIRE 、 PEXPIRE 和 EXPIREAT 命令
if (cmd->proc == expireCommand || cmd->proc == pexpireCommand ||
cmd->proc == expireatCommand) {
/* Translate EXPIRE/PEXPIRE/EXPIREAT into PEXPIREAT
*
* 將 EXPIRE 、 PEXPIRE 和 EXPIREAT 都翻譯成 PEXPIREAT
*/
buf = catAppendOnlyExpireAtCommand(buf,cmd,argv[1],argv[2]);
// SETEX 和 PSETEX 命令
} else if (cmd->proc == setexCommand || cmd->proc == psetexCommand) {
/* Translate SETEX/PSETEX to SET and PEXPIREAT
*
* 將兩個命令都翻譯成 SET 和 PEXPIREAT
*/
// SET
tmpargv[0] = createStringObject("SET",3);
tmpargv[1] = argv[1];
tmpargv[2] = argv[3];
buf = catAppendOnlyGenericCommand(buf,3,tmpargv);
// PEXPIREAT
decrRefCount(tmpargv[0]);
buf = catAppendOnlyExpireAtCommand(buf,cmd,argv[1],argv[2]);
// 其他命令
} else {
buf = catAppendOnlyGenericCommand(buf,argc,argv);
}
/*
* 將命令追加到 AOF 快取中,
* 在重新進入事件迴圈之前,這些命令會被沖洗到磁碟上,
* 並向客戶端返回一個回覆。
*/
if (server.aof_state == REDIS_AOF_ON)
server.aof_buf = sdscatlen(server.aof_buf,buf,sdslen(buf));
/*
* 如果 BGREWRITEAOF 正在進行,
* 那麼我們還需要將命令追加到重寫快取中,
* 從而記錄當前正在重寫的 AOF 檔案和資料庫當前狀態的差異。
*/
if (server.aof_child_pid != -1)
aofRewriteBufferAppend((unsigned char*)buf,sdslen(buf));
// 釋放
sdsfree(buf);
}
解析成redis的命令格式:例如 $3
SET
$3
KEY
$5
VALUE
/*
* 根據傳入的命令和命令引數,將它們還原成協議格式。
*/
sds catAppendOnlyGenericCommand(sds dst, int argc, robj **argv) {
char buf[32];
int len, j;
robj *o;
// 重建命令的個數,格式為 *<count>
// 例如 *3
buf[0] = `*`;
len = 1+ll2string(buf+1,sizeof(buf)-1,argc);
buf[len++] = `
`;
buf[len++] = `
`;
dst = sdscatlen(dst,buf,len);
// 重建命令和命令引數,格式為 $<length>
<content>
// 例如 $3
SET
$3
KEY
$5
VALUE
for (j = 0; j < argc; j++) {
o = getDecodedObject(argv[j]);
// 組合 $<length>
buf[0] = `$`;
len = 1+ll2string(buf+1,sizeof(buf)-1,sdslen(o->ptr));
buf[len++] = `
`;
buf[len++] = `
`;
dst = sdscatlen(dst,buf,len);
// 組合 <content>
dst = sdscatlen(dst,o->ptr,sdslen(o->ptr));
dst = sdscatlen(dst,"
",2);
decrRefCount(o);
}
// 返回重建後的協議內容
return dst;
}
redis aof持久化
serverCron內部定期執行flushAppendOnlyFile,這裡的if判斷是判斷是否延遲執行,暫且忽略這個判斷而認為每次都會進行aof持久化。
int serverCron(struct aeEventLoop *eventLoop, long long id, void *clientData) {
// 根據 AOF 政策,
// 考慮是否需要將 AOF 緩衝區中的內容寫入到 AOF 檔案中
if (server.aof_flush_postponed_start) flushAppendOnlyFile(0);
}
執行aof記憶體資料持久化的過程,考慮異常情況的情況主要分為以下步驟:
- 寫入aof_buf資料到aof_fd代表的aof持久化檔案當中
- 處理aof寫入異常的情況,嘗試修復失敗後會移除失敗的命令
- 更新aof相關的統計引數
- 如果aof_buf資料過大那麼就情況aof_buf的內容
void flushAppendOnlyFile(int force) {
ssize_t nwritten;
int sync_in_progress = 0;
// 緩衝區中沒有任何內容,直接返回
if (sdslen(server.aof_buf) == 0) return;
// 策略為每秒 FSYNC
if (server.aof_fsync == AOF_FSYNC_EVERYSEC)
// 是否有 SYNC 正在後臺進行?
sync_in_progress = bioPendingJobsOfType(REDIS_BIO_AOF_FSYNC) != 0;
// 每秒 fsync ,並且強制寫入為假
if (server.aof_fsync == AOF_FSYNC_EVERYSEC && !force) {
/*
* 當 fsync 策略為每秒鐘一次時, fsync 在後臺執行。
*
* 如果後臺仍在執行 FSYNC ,那麼我們可以延遲寫操作一兩秒
* (如果強制執行 write 的話,伺服器主執行緒將阻塞在 write 上面)
*/
if (sync_in_progress) {
// 有 fsync 正在後臺進行 。。。
if (server.aof_flush_postponed_start == 0) {
/*
* 前面沒有推遲過 write 操作,這裡將推遲寫操作的時間記錄下來
* 然後就返回,不執行 write 或者 fsync
*/
server.aof_flush_postponed_start = server.unixtime;
return;
} else if (server.unixtime - server.aof_flush_postponed_start < 2) {
/*
* 如果之前已經因為 fsync 而推遲了 write 操作
* 但是推遲的時間不超過 2 秒,那麼直接返回
* 不執行 write 或者 fsync
*/
return;
}
/*
* 如果後臺還有 fsync 在執行,並且 write 已經推遲 >= 2 秒
* 那麼執行寫操作(write 將被阻塞)
*/
server.aof_delayed_fsync++;
}
}
/*
* 執行到這裡,程式會對 AOF 檔案進行寫入。
*
* 清零延遲 write 的時間記錄
*/
server.aof_flush_postponed_start = 0;
/*
* 執行單個 write 操作,如果寫入裝置是物理的話,那麼這個操作應該是原子的
*
* 當然,如果出現像電源中斷這樣的不可抗現象,那麼 AOF 檔案也是可能會出現問題的
* 這時就要用 redis-check-aof 程式來進行修復。
*/
nwritten = write(server.aof_fd,server.aof_buf,sdslen(server.aof_buf));
if (nwritten != (signed)sdslen(server.aof_buf)) {
static time_t last_write_error_log = 0;
int can_log = 0;
// 將日誌的記錄頻率限制在每行 AOF_WRITE_LOG_ERROR_RATE 秒
if ((server.unixtime - last_write_error_log) > AOF_WRITE_LOG_ERROR_RATE) {
can_log = 1;
last_write_error_log = server.unixtime;
}
// 如果寫入出錯,那麼嘗試將該情況寫入到日誌裡面
if (nwritten == -1) {
if (can_log) {
redisLog(REDIS_WARNING,"Error writing to the AOF file: %s",
strerror(errno));
server.aof_last_write_errno = errno;
}
} else {
if (can_log) {
redisLog(REDIS_WARNING,"Short write while writing to "
"the AOF file: (nwritten=%lld, "
"expected=%lld)",
(long long)nwritten,
(long long)sdslen(server.aof_buf));
}
// 嘗試移除新追加的不完整內容
if (ftruncate(server.aof_fd, server.aof_current_size) == -1) {
if (can_log) {
redisLog(REDIS_WARNING, "Could not remove short write "
"from the append-only file. Redis may refuse "
"to load the AOF the next time it starts. "
"ftruncate: %s", strerror(errno));
}
} else {
/* If the ftrunacate() succeeded we can set nwritten to
* -1 since there is no longer partial data into the AOF. */
nwritten = -1;
}
server.aof_last_write_errno = ENOSPC;
}
// 處理寫入 AOF 檔案時出現的錯誤
if (server.aof_fsync == AOF_FSYNC_ALWAYS) {
redisLog(REDIS_WARNING,"Can`t recover from AOF write error when the AOF fsync policy is `always`. Exiting...");
exit(1);
} else {
server.aof_last_write_status = REDIS_ERR;
/* Trim the sds buffer if there was a partial write, and there
* was no way to undo it with ftruncate(2). */
if (nwritten > 0) {
server.aof_current_size += nwritten;
sdsrange(server.aof_buf,nwritten,-1);
}
return; /* We`ll try again on the next call... */
}
} else {
/* Successful write(2). If AOF was in error state, restore the
* OK state and log the event. */
// 寫入成功,更新最後寫入狀態
if (server.aof_last_write_status == REDIS_ERR) {
redisLog(REDIS_WARNING,
"AOF write error looks solved, Redis can write again.");
server.aof_last_write_status = REDIS_OK;
}
}
// 更新寫入後的 AOF 檔案大小
server.aof_current_size += nwritten;
/*
* 如果 AOF 快取的大小足夠小的話,那麼重用這個快取,
* 否則的話,釋放 AOF 快取。
*/
if ((sdslen(server.aof_buf)+sdsavail(server.aof_buf)) < 4000) {
// 清空快取中的內容,等待重用
sdsclear(server.aof_buf);
} else {
// 釋放快取
sdsfree(server.aof_buf);
server.aof_buf = sdsempty();
}
/*
* 如果 no-appendfsync-on-rewrite 選項為開啟狀態,
* 並且有 BGSAVE 或者 BGREWRITEAOF 正在進行的話,
* 那麼不執行 fsync
*/
if (server.aof_no_fsync_on_rewrite &&
(server.aof_child_pid != -1 || server.rdb_child_pid != -1))
return;
// 總是執行 fsnyc
if (server.aof_fsync == AOF_FSYNC_ALWAYS) {
aof_fsync(server.aof_fd); /* Let`s try to get this data on the disk */
// 更新最後一次執行 fsnyc 的時間
server.aof_last_fsync = server.unixtime;
// 策略為每秒 fsnyc ,並且距離上次 fsync 已經超過 1 秒
} else if ((server.aof_fsync == AOF_FSYNC_EVERYSEC &&
server.unixtime > server.aof_last_fsync)) {
// 放到後臺執行
if (!sync_in_progress) aof_background_fsync(server.aof_fd);
// 更新最後一次執行 fsync 的時間
server.aof_last_fsync = server.unixtime;
}
}
相關文章
- Redis資料持久化—RDB持久化與AOF持久化Redis持久化
- redis系列:RDB持久化與AOF持久化Redis持久化
- redis持久化Redis持久化
- Docker 持久化Docker持久化
- Redis 持久化Redis持久化
- [Redis]持久化Redis持久化
- Redis - 持久化Redis持久化
- SpringCloud使用Sentinel,Sentinel持久化,Sentinel使用nacos持久化SpringGCCloud持久化
- Redis:持久化篇Redis持久化
- redis-持久化Redis持久化
- redis 之 持久化Redis持久化
- 可持久化trie持久化
- Redis 七 持久化Redis持久化
- Redis 持久化方案Redis持久化
- redis ——AOF持久化Redis持久化
- redis 持久化策略Redis持久化
- Redis的持久化Redis持久化
- Redis 持久化(Persistence)Redis持久化
- Redis 的持久化Redis持久化
- RDD持久化,不使用RDD持久化的問題的工作原理持久化
- 2.3 持久化命令列持久化命令列
- redis快照--RDB持久化Redis持久化
- vuex持久化方案探究Vue持久化
- fabric資料持久化持久化
- Redis 持久化之 AOFRedis持久化
- 精講Redis:持久化Redis持久化
- Redis原理二:持久化Redis持久化
- 可持久化 01 trie持久化
- Java emoji持久化mysqlJava持久化MySql
- Redis的持久化方案Redis持久化
- 使用 Java 持久化 APIJava持久化API
- Redis的持久化策略Redis持久化
- 可持久化專題持久化
- Redis 持久化詳解Redis持久化
- Android持久化技術Android持久化
- 淺談redis持久化Redis持久化
- Redis的持久化方式Redis持久化
- Redis持久化及其配置Redis持久化