Redis提供兩種持久化方式,RDB和AOF;與RDB不同,AOF可以完整的記錄整個資料庫,而不像RDB只是資料庫某一時刻的快照;
那麼AOF模式為什麼可以完整的記錄整個資料庫呢。
原理 :在AOF模式下,Redis會把執行過的每一條更新命令記錄下來,儲存到AOF檔案中;當Redis需要恢複資料庫資料時,只需要從之前儲存的AOF檔案中依次讀取命令,執行即可 eg.
Shell代碼 我們執行了以下命令: redis 127.0.0.1:6379> set name diaocow OK redis 127.0.0.1:6379> lpush country china usa (integer) 4 這時候在AOF檔案中的類容類似下面: *3\r\n$3\r\nset\r\n$4\r\nname\r\n$7\r\ndiaocow\r\n *4\r\n$5\r\nlpush\r\n$7\r\ncountry\r\n$5\r\nchina\r\n$3\r\nusa\r\n
看了上面的內容,我想不用我過多解釋,你也能大致猜出AOF協議格式,因為它實在太簡單明了了
Redis把更新命令記錄到AOF檔案,分為兩個階段:
階段1:把更新命令寫入aof緩衝
Python代碼 def processCommand(cmd, argc, argv): # 執行命令 call(cmd, argc, argv) # 該命令變更了鍵空間並且AOF模式開啟 if redisServer.update_key_space and redisServer.aof_state & REDIS_AOF_ON: feedAppendOnlyFile(cmd, argc, argv) def feedAppendOnlyFile(cmd, argc, argv): # 把命令轉換成AOF協議格式 aofCmdStr = getAofProtocolStr(cmd, argc, argv) redisServer.aof_buf.append(aofCmdStr ) # 存在一個子進程進行中AOF_REWRITE(關於AOF_REWRITE,稍後詳說) if redisServer.aof_child_pid != -1: redisServer.aof_rewrite_buf_blocks.append(aofCmdStr )
階段2: 把aof緩衝寫入檔案
當我們開始下一次 事件迴圈 之前,redis會把AOF緩衝中的內容寫入到檔案:
Python代碼 def flushAppendOnlyFile(force): if len(redisServer.aof_buf) == 0: return # 把快取資料寫入檔案 if writeByPolicy(force, redisServer.aof_fsync): write(redisServer.aof_fd, redisServer.aof_buf, len(redisServer.aof_buf)) # 同步資料到硬碟 if fsyncByPolicy(force, redisServer.aof_fsync): fsync(redisServer.aof_fd)
更多細節請看: aof.c/flushAppendOnlyFile函數 (ps: 這個函數代碼看起來比較晦澀)
看到這裡,你也許會有兩個疑問:
1. 為什麼要調用fsync函數,不是已經調用write把資料寫入到檔案了嗎。
2. 虛擬碼中aof_fsync是什麼,它有幾種類型。
首先回答問題1,為什麼寫入檔案後,還要調用fsync函數:
大多數unix系統為了減少磁碟IO,採用了“延遲寫”技術,也就是說當我們執行完write調用後,資料並不一定立馬被寫入磁碟(可能還是保留在系統的buffer cache或者page cache中),這樣當主機突然斷電,這些我們本以為已經寫入到磁碟檔案的資料可能就會丟失;所以當我們需要確保資料被完整正確的寫入磁碟(譬如資料庫的持久化),則需要調用同步函數fsync,它會一直阻塞直到資料全部被寫入到硬碟
問題2,aof_fysnc是什麼:
aof_fsync用來指定flush策略,也就是調用fsync函數的策略,它一共有三種:
a. AOF_FSYNC_NO :每次都會把aof_buf中的內容寫入到磁碟,但是不會調用fsync函數;
b. AOF_FSYNC_ALWAYS :每次都會把aof_buf中的內容寫入到磁碟,同時調用fsync函數;
c. AOF_FSYNC_EVERYSEC
由於AOF_FSYNC_ALWAYS每次都寫入檔案都會調用fsync,所以這種flush策略可以保證資料的完整性,缺點就是效能太差(因為fysnc是個同步調用,會阻塞主進程對用戶端請求的處理),而AOF_FSYNC_NO由於依賴於作業系統自動sync,因此不能保證資料的完整性;
那有沒有一種折中的方式:既能不過分降低系統的效能,又能最大程度上的保證資料的完整性,答案就是:AOF_FSYNC_EVERYSEC,AOF_FSYNC_EVERYSEC的flush策略是:定期(至少1s)去調用fsync,並且該操作是放到一個非同步隊列中(線程)去執行,因此不會阻塞主進程
AOF 後台執行的方式和 RDB 有類似的地方,fork 一個子進程,主進程仍進行服務,子進程執行 AOF 持久化,資料被 dump 到磁碟上。與 RDB 不同的是,後檯子進程持久化過程中,主進程會記錄期間的所有資料變更(主進程還在服務),並儲存在 server.aof_rewrite_buf_blocks 中;後檯子進程結束後,redis 更新緩衝追加到 AOF 檔案中,是 RDB 持久化所不具備的.
AOF模式至此我們已經基本說完,但是隨著Redis運行,AOF檔案會變得越來越大(在業務高分期增長的更快),原因有兩個 :
a. AOF協議本身是文本協議,比較占空間;
b. Redis需要記錄從開始到現在的所有更新命令;
這兩個原因導致了AOF檔案容易變得很大,那有什麼方式可以最佳化嗎。譬如使用者執行了三個命令:lpush name diaocow; lpush name jack; lpush name jobs
AOF檔案會記錄以下資料:
Aof_file代碼 *3\r\n$5\r\nlpush\r\n$4\r\nname\r\n$7\r\ndiaocow *3\r\n$5\r\nlpush\r\n$4\r\nname\r\n$4\r\njack *3\r\n$5\r\nlpush\r\n$4\r\nname\r\n$4\r\njobs
但其實只需要記錄一條:lpush name diaocow jack jbos 命令即可:
Aof_file代碼 *5\r\n$5\r\nlpush\r\n$4\r\nname\r\n$7\r\ndiaocow\r\n$4\r\njack\r\n$4\r\njobs
所以當AOF檔案達到 REDIS_AOF_REWRITE_MIN_SIZE(1M)時,Redis就會執行AOF_REWRITE來最佳化AOF檔案;