AOF 持久化功能的實現可以分為命令追加(append)、文件寫入、文件同步(sync)三個步驟。
## 命令追加
當 AOF 持久化功能處于打開狀態時, 服務器在執行完一個寫命令之后, 會以協議格式將被執行的寫命令追加到服務器狀態的?`aof_buf`?緩沖區的末尾:
~~~
struct redisServer {
// ...
// AOF 緩沖區
sds aof_buf;
// ...
};
~~~
舉個例子, 如果客戶端向服務器發送以下命令:
~~~
redis> SET KEY VALUE
OK
~~~
那么服務器在執行這個?SET?命令之后, 會將以下協議內容追加到?`aof_buf`?緩沖區的末尾:
~~~
*3\r\n$3\r\nSET\r\n$3\r\nKEY\r\n$5\r\nVALUE\r\n
~~~
又比如說, 如果客戶端向服務器發送以下命令:
~~~
redis> RPUSH NUMBERS ONE TWO THREE
(integer) 3
~~~
那么服務器在執行這個?RPUSH?命令之后, 會將以下協議內容追加到?`aof_buf`?緩沖區的末尾:
~~~
*5\r\n$5\r\nRPUSH\r\n$7\r\nNUMBERS\r\n$3\r\nONE\r\n$3\r\nTWO\r\n$5\r\nTHREE\r\n
~~~
以上就是 AOF 持久化的命令追加步驟的實現原理。
## AOF 文件的寫入與同步
Redis 的服務器進程就是一個事件循環(loop), 這個循環中的文件事件負責接收客戶端的命令請求, 以及向客戶端發送命令回復, 而時間事件則負責執行像?`serverCron`?函數這樣需要定時運行的函數。
因為服務器在處理文件事件時可能會執行寫命令, 使得一些內容被追加到?`aof_buf`?緩沖區里面, 所以在服務器每次結束一個事件循環之前, 它都會調用?`flushAppendOnlyFile`?函數, 考慮是否需要將?`aof_buf`?緩沖區中的內容寫入和保存到 AOF 文件里面, 這個過程可以用以下偽代碼表示:
~~~
def eventLoop():
while True:
# 處理文件事件,接收命令請求以及發送命令回復
# 處理命令請求時可能會有新內容被追加到 aof_buf 緩沖區中
processFileEvents()
# 處理時間事件
processTimeEvents()
# 考慮是否要將 aof_buf 中的內容寫入和保存到 AOF 文件里面
flushAppendOnlyFile()
~~~
`flushAppendOnlyFile`?函數的行為由服務器配置的?`appendfsync`?選項的值來決定, 各個不同值產生的行為如表 TABLE_APPENDFSYNC 所示。
| `appendfsync`?選項的值 | `flushAppendOnlyFile`?函數的行為 |
| --- | --- |
| `always` | 將?`aof_buf`?緩沖區中的所有內容寫入并同步到 AOF 文件。 |
| `everysec` | 將?`aof_buf`?緩沖區中的所有內容寫入到 AOF 文件, 如果上次同步 AOF 文件的時間距離現在超過一秒鐘, 那么再次對 AOF 文件進行同步, 并且這個同步操作是由一個線程專門負責執行的。 |
| `no` | 將?`aof_buf`?緩沖區中的所有內容寫入到 AOF 文件, 但并不對 AOF 文件進行同步, 何時同步由操作系統來決定。 |
如果用戶沒有主動為?`appendfsync`?選項設置值, 那么?`appendfsync`?選項的默認值為?`everysec`?, 關于?`appendfsync`?選項的更多信息, 請參考 Redis 項目附帶的示例配置文件?`redis.conf`?。
文件的寫入和同步
為了提高文件的寫入效率, 在現代操作系統中, 當用戶調用?`write`?函數, 將一些數據寫入到文件的時候, 操作系統通常會將寫入數據暫時保存在一個內存緩沖區里面, 等到緩沖區的空間被填滿、或者超過了指定的時限之后, 才真正地將緩沖區中的數據寫入到磁盤里面。
這種做法雖然提高了效率, 但也為寫入數據帶來了安全問題, 因為如果計算機發生停機, 那么保存在內存緩沖區里面的寫入數據將會丟失。
為此, 系統提供了?`fsync`?和?`fdatasync`?兩個同步函數, 它們可以強制讓操作系統立即將緩沖區中的數據寫入到硬盤里面, 從而確保寫入數據的安全性。
舉個例子, 假設服務器在處理文件事件期間, 執行了以下三個寫入命令:
1. `SADD?databases?"Redis"?"MongoDB"?"MariaDB"`
2. `SET?date?"2013-9-5"`
3. `INCR?click_counter?10086`
那么?`aof_buf`?緩沖區將包含這三個命令的協議內容:
~~~
*5\r\n$4\r\nSADD\r\n$9\r\ndatabases\r\n$5\r\nRedis\r\n$7\r\nMongoDB\r\n$7\r\nMariaDB\r\n
*3\r\n$3\r\nSET\r\n$4\r\ndate\r\n$8\r\n2013-9-5\r\n
*3\r\n$4\r\nINCR\r\n$13\r\nclick_counter\r\n$5\r\n10086\r\n
~~~
如果這時?`flushAppendOnlyFile`?函數被調用, 假設服務器當前?`appendfsync`?選項的值為?`everysec`?, 并且根據?`server.aof_last_fsync`?屬性顯示, 距離上次同步 AOF 文件已經超過一秒鐘, 那么服務器會先將?`aof_buf`?中的內容寫入到 AOF 文件中, 然后再對 AOF 文件進行同步。
以上就是對 AOF 持久化功能的文件寫入和文件同步這兩個步驟的介紹。
AOF 持久化的效率和安全性
服務器配置?`appendfsync`?選項的值直接決定 AOF 持久化功能的效率和安全性。
當?`appendfsync`?的值為?`always`?時, 服務器在每個事件循環都要將?`aof_buf`?緩沖區中的所有內容寫入到 AOF 文件, 并且同步 AOF 文件, 所以?`always`?的效率是?`appendfsync`?選項三個值當中最慢的一個, 但從安全性來說,?`always`?也是最安全的, 因為即使出現故障停機, AOF 持久化也只會丟失一個事件循環中所產生的命令數據。
當?`appendfsync`?的值為?`everysec`?時, 服務器在每個事件循環都要將?`aof_buf`?緩沖區中的所有內容寫入到 AOF 文件, 并且每隔超過一秒就要在子線程中對 AOF 文件進行一次同步: 從效率上來講,?`everysec`?模式足夠快, 并且就算出現故障停機, 數據庫也只丟失一秒鐘的命令數據。
當?`appendfsync`?的值為?`no`?時, 服務器在每個事件循環都要將?`aof_buf`?緩沖區中的所有內容寫入到 AOF 文件, 至于何時對 AOF 文件進行同步, 則由操作系統控制。
因為處于?`no`?模式下的?`flushAppendOnlyFile`?調用無須執行同步操作, 所以該模式下的 AOF 文件寫入速度總是最快的, 不過因為這種模式會在系統緩存中積累一段時間的寫入數據, 所以該模式的單次同步時長通常是三種模式中時間最長的: 從平攤操作的角度來看,?`no`模式和?`everysec`?模式的效率類似, 當出現故障停機時, 使用?`no`?模式的服務器將丟失上次同步 AOF 文件之后的所有寫命令數據。
- 介紹
- 前言
- 致謝
- 簡介
- 第一部分:數據結構與對象
- 簡單動態字符串
- SDS 的定義
- SDS 與 C 字符串的區別
- SDS API
- 重點回顧
- 參考資料
- 鏈表
- 鏈表和鏈表節點的實現
- 鏈表和鏈表節點的 API
- 重點回顧
- 字典
- 字典的實現
- 哈希算法
- 解決鍵沖突
- rehash
- 漸進式 rehash
- 字典 API
- 重點回顧
- 跳躍表
- 跳躍表的實現
- 跳躍表 API
- 重點回顧
- 整數集合
- 整數集合的實現
- 升級
- 升級的好處
- 降級
- 整數集合 API
- 重點回顧
- 壓縮列表
- 壓縮列表的構成
- 壓縮列表節點的構成
- 連鎖更新
- 壓縮列表 API
- 重點回顧
- 對象
- 對象的類型與編碼
- 字符串對象
- 列表對象
- 哈希對象
- 集合對象
- 有序集合對象
- 類型檢查與命令多態
- 內存回收
- 對象共享
- 對象的空轉時長
- 重點回顧
- 第二部分:單機數據庫的實現
- 數據庫
- 數據庫鍵空間
- 重點回顧
- RDB 持久化
- RDB 文件結構
- 重點回顧
- AOF 持久化
- AOF 持久化的實現
- 重點回顧
- 事件
- 文件事件
- 重點回顧
- 參考資料
- 客戶端
- 客戶端屬性
- 重點回顧
- 服務器
- 命令請求的執行過程
- 重點回顧
- 第三部分:多機數據庫的實現
- 復制
- 舊版復制功能的實現
- 重點回顧
- Sentinel
- 啟動并初始化 Sentinel
- 重點回顧
- 參考資料
- 集群
- 節點
- 重點回顧
- 第四部分:獨立功能的實現
- 發布與訂閱
- 頻道的訂閱與退訂
- 重點回顧
- 參考資料
- 事務
- 事務的實現
- 重點回顧
- Lua 腳本
- 創建并修改 Lua 環境
- 重點回顧
- 排序
- SORT <key> 命令的實現
- 重點回顧
- 二進制位數組
- GETBIT 命令的實現
- 重點回顧
- 慢查詢日志
- 慢查詢記錄的保存
- 慢查詢日志的閱覽和刪除
- 添加新日志
- 重點回顧
- 監視器
- 成為監視器
- 向監視器發送命令信息
- 重點回顧
- 源碼、相關資源和勘誤