在redis中, RDB主要用來進行數據庫的全量備份, AOF主要用來進行增量備份, 他們有不同的使用場景, 在真實的線上環境, 比較常見的是結合全量備份和增量備份來實現按時間點的回覆。
RDB備份
RDB是對備份時間點的數據庫現有數據的一個snapshot, 它的基本原理是遍歷數據庫內指定的或者全部庫, 將數據按照比較緊湊的方式保存在一份數據裏面。這裏, 比較緊湊的意思是將數據以特定的格式保存, 並且還可以以LZF壓縮的方式存儲(需要配置)。
RDB 的備份分爲2種方式:自動備份和手動備份。
自動備份是在serverCron裏面實現, 每當配置文件中指定的自動備份條件滿足的時候, 系統自動進行備份。比如, 在配置裏面指定如下的備份條件:
save 900 1
save 300 10
save 60 10000
相關的處理代碼如下:
int serverCron(struct aeEventLoop *eventLoop, long long id, void *clientData) {
...
if (server.rdb_child_pid != -1 || server.aof_child_pid != -1 ||
ldbPendingChildren())
{
if ((pid = wait3(&statloc,WNOHANG,NULL)) != 0) {
// RDB 或者AOF 子進程返回值的處理
} else {
// 當如下的條件之一滿足後, 調用RDB自動備份
for (j = 0; j < server.saveparamslen; j++) {
struct saveparam *sp = server.saveparams+j;
if (server.dirty >= sp->changes &&
server.unixtime-server.lastsave > sp->seconds &&
(server.unixtime-server.lastbgsave_try >
CONFIG_BGSAVE_RETRY_DELAY ||
server.lastbgsave_status == C_OK))
{
serverLog(LL_NOTICE,"%d changes in %d seconds. Saving...",
sp->changes, (int)sp->seconds);
rdbSaveInfo rsi, *rsiptr;
rsiptr = rdbPopulateSaveInfo(&rsi);
rdbSaveBackground(server.rdb_filename,rsiptr);
break;
}
}
同時, 也看到自動備份的入口爲rdbSaveBackground。
手工備份, 其入口函數是:
struct redisCommand redisCommandTable[] = {
{"save",saveCommand,1,
"admin no-script",
0,NULL,0,0,0,0,0,0},
{"bgsave",bgsaveCommand,-1,
"admin no-script",
0,NULL,0,0,0,0,0,0},
}
手工備份,有前臺備份和後臺備份。
前臺備份通過rdbSave來實現, 它是由主進程操作實現, 也就是說,在主進程實現RDB前臺備份的過程中, 是無法同時接受客戶端的相應的, 顯然這種方式的缺點很明顯:無法應對高併發的場景, 優點就是處理簡單;
後臺備份是通過rdbSaveBackground來實現, 通過主進程啓動一個新的進程, 該子進程調用rdbSave產生備份文件,主子進程之間通過pipe的方式啓動一個無名管道,進行信息的傳遞, 比如,子進程COW數據的大小。 這種備份的好處是不會影響對外提供服務, 因爲整個備份過程是異步操作, 缺點就是處理起來相對複雜一些。
AOF備份
AOF備份數據
AOF是將影響到redis數據庫狀態的所有操作記錄下來並寫入到一個指定的AOF文件裏面, 當需要的時候, 通過回放AOF文件內的一條條redis命令, 來重新構建內存狀態, 達到回覆數據庫的目的。它非常類似於數據庫的操作日誌的同步與回放。
redis裏面的命令非常多, 他們使用一樣的接口來處理, 那些命令需要記錄在AOF裏, 哪些不需要,是通過在調用call()之前是否是定PROPAGATE_AOF flag來表明該操作是否需要記錄到AOF, master-slave之間是否需要同步命令, 使用一樣的操作方式。
void propagate(struct redisCommand *cmd, int dbid, robj **argv, int argc,
int flags)
{
if (server.aof_state != AOF_OFF && flags & PROPAGATE_AOF)
feedAppendOnlyFile(cmd,dbid,argv,argc);
if (flags & PROPAGATE_REPL)
replicationFeedSlaves(server.slaves,dbid,argv,argc);
}
feedAppendOnlyFile裏面記錄了需要記錄在AOF中各種命令的存儲格式, 也就是AOF協議格式,以便讀取的時候能夠得到正確的回放。
每一條命令是如果都要寫入AOF文件然後返回客戶端, 就會在成太頻繁的IO操作, 影響redis的系統性能; 不加以控制由系統決定又無法保證數據的落盤, 因此, redis通過如下的配置項來指定:
appendfsync always/everysec/ no
通常, 我們需要續訂everysec, 兼顧性能和數據安全性。
AOF隨着時間的推移會越變越大, 回放的時間也就會越來越長, 爲了提升回放的效率, redis會定期重寫AOF。
AOF重寫
AOF重寫操作的觸發有2中方式:用戶觸發與後臺週期檢測觸發。
用戶觸發是由命令bgrewriteaof進行, 對應的入口函數爲
bgrewriteaofCommand:
struct redisCommand redisCommandTable[] = {
{"bgrewriteaof",bgrewriteaofCommand,1,
"admin no-script",
0,NULL,0,0,0,0,0,0},
}
後臺週期性的檢測觸發, 它的觸發點也在serverCron裏面, 當沒有RDB或者AOF 重寫操作的時候, 系統會自動檢查AOF的大小以及相對於上次重寫時的AOF大小, 決定是否進行重寫操作。如下2個配置項可以指定觸發的條件:
auto-aof-rewrite-percentage 100
auto-aof-rewrite-min-size 64mb
代碼的實現如下:
int serverCron(struct aeEventLoop *eventLoop, long long id, void *clientData) {
...
if (server.rdb_child_pid != -1 || server.aof_child_pid != -1 ||
ldbPendingChildren())
{
int statloc;
pid_t pid;
if ((pid = wait3(&statloc,WNOHANG,NULL)) != 0) {
// RDB 或者AOF 子進程返回值的處理
} else {
/* If there is not a background saving/rewrite in progress check if
* we have to save/rewrite now. */
/* Trigger an AOF rewrite if needed. */
if (server.aof_state == AOF_ON &&
server.rdb_child_pid == -1 &&
server.aof_child_pid == -1 &&
server.aof_rewrite_perc &&
server.aof_current_size > server.aof_rewrite_min_size)
{
long long base = server.aof_rewrite_base_size ?
server.aof_rewrite_base_size : 1;
long long growth = (server.aof_current_size*100/base) - 100;
if (growth >= server.aof_rewrite_perc) {
serverLog(LL_NOTICE,"Starting automatic rewriting of AOF on %lld%% growth",growth);
rewriteAppendOnlyFileBackground();
}
}
}
...
}
這裏的操作跟background RDB備份非常相似, 由主進程啓動一個子進程, 由子進程完成相關的重寫操作rewriteAppendOnlyFile; 在重寫操作進行過程中,主進程新的修改數據庫的操作被記錄在server.aof_buf裏面, 當該buffer滿之後, 寫入到aof_rewrite_buf_blocks 列表內,同時會產生一個寫入操作信號, 通知重寫子進程重寫期間產生的所有操作; 子進程與父進程的通信也是通過pipe的方式傳遞增量信息與ack信息:
server.aof_pipe_write_data_to_child = fds[1];
server.aof_pipe_read_data_from_parent = fds[0];
server.aof_pipe_write_ack_to_parent = fds[3];
server.aof_pipe_read_ack_from_child = fds[2];
server.aof_pipe_write_ack_to_child = fds[5];
server.aof_pipe_read_ack_from_parent = fds[4];
void aofRewriteBufferAppend(unsigned char *s, unsigned long len) {
listNode *ln = listLast(server.aof_rewrite_buf_blocks);
aofrwblock *block = ln ? ln->value : NULL;
while(len) {
/* If we already got at least an allocated block, try appending
* at least some piece into it. */
if (block) {
unsigned long thislen = (block->free < len) ? block->free : len;
if (thislen) { /* The current block is not already full. */
memcpy(block->buf+block->used, s, thislen);
block->used += thislen;
block->free -= thislen;
s += thislen;
len -= thislen;
}
}
if (len) { /* First block to allocate, or need another block. */
int numblocks;
block = zmalloc(sizeof(*block));
block->free = AOF_RW_BUF_BLOCK_SIZE;
block->used = 0;
listAddNodeTail(server.aof_rewrite_buf_blocks,block);
/* Log every time we cross more 10 or 100 blocks, respectively
* as a notice or warning. */
numblocks = listLength(server.aof_rewrite_buf_blocks);
if (((numblocks+1) % 10) == 0) {
int level = ((numblocks+1) % 100) == 0 ? LL_WARNING :
LL_NOTICE;
serverLog(level,"Background AOF buffer size: %lu MB",
aofRewriteBufferSize()/(1024*1024));
}
}
}
/* Install a file event to send data to the rewrite child if there is
* not one already. */
if (aeGetFileEvents(server.el,server.aof_pipe_write_data_to_child) == 0) {
aeCreateFileEvent(server.el, server.aof_pipe_write_data_to_child,
AE_WRITABLE, aofChildWriteDiffData, NULL);
}
}
備份數據的加載
在main函數內loadDataFromDisk來將備份數據加載到內存中, 如果AOF存在通過AOF文件加載, 否則通過RDB加載。
void loadDataFromDisk(void) {
long long start = ustime();
if (server.aof_state == AOF_ON) {
if (loadAppendOnlyFile(server.aof_filename) == C_OK)
serverLog(LL_NOTICE,"DB loaded from append only file: %.3f seconds",(float)(ustime()-start)/1000000);
} else {
rdbSaveInfo rsi = RDB_SAVE_INFO_INIT;
if (rdbLoad(server.rdb_filename,&rsi) == C_OK) {
}
}
}