# AOF
[TOC=2,3]
Redis 分別提供了 RDB 和 AOF 兩種持久化機制:
- RDB 將數據庫的快照(snapshot)以二進制的方式保存到磁盤中。
- AOF 則以協議文本的方式,將所有對數據庫進行過寫入的命令(及其參數)記錄到 AOF 文件,以此達到記錄數據庫狀態的目的。
![digraph persistent { rankdir = LR; node [shape = circle, style = filled]; edge [style = "dashed, bold"]; // node client [label = "客戶端", fillcolor = "#FADCAD"]; server [label = "服務器", fillcolor = "#A8E270"]; aof [label = "AOF \n 文件", fillcolor = "#95BBE3"]; // edge client -> server [label = "命令請求"]; server -> aof [ label = "網絡協議格式的\n命令內容"];}](https://box.kancloud.cn/2015-09-13_55f4effe8e494.svg)
本章首先介紹 AOF 功能的運作機制,了解命令是如何被保存到 AOF 文件里的,觀察不同的 AOF 保存模式對數據的安全性、以及 Redis 性能的影響。
之后會介紹從 AOF 文件中恢復數據庫狀態的方法,以及該方法背后的實現機制。
最后還會介紹對 AOF 進行重寫以調整文件體積的方法,并研究這種方法是如何在不改變數據庫狀態的前提下進行的。
因為本章涉及 AOF 運行的相關機制,如果還沒了解過 AOF 功能的話,請先閱讀 [Redis 持久化手冊中關于 AOF 的部分](http://redis.io/topics/persistence) 。
### AOF 命令同步
Redis 將所有對數據庫進行過寫入的命令(及其參數)記錄到 AOF 文件,以此達到記錄數據庫狀態的目的,為了方便起見,我們稱呼這種記錄過程為同步。
舉個例子,如果執行以下命令:
~~~
redis> RPUSH list 1 2 3 4
(integer) 4
redis> LRANGE list 0 -1
1) "1"
2) "2"
3) "3"
4) "4"
redis> KEYS *
1) "list"
redis> RPOP list
"4"
redis> LPOP list
"1"
redis> LPUSH list 1
(integer) 3
redis> LRANGE list 0 -1
1) "1"
2) "2"
3) "3"
~~~
那么其中四條對數據庫有修改的寫入命令就會被同步到 AOF 文件中:
~~~
RPUSH list 1 2 3 4
RPOP list
LPOP list
LPUSH list 1
~~~
為了處理的方便,AOF 文件使用網絡通訊協議的格式來保存這些命令。
比如說,上面列舉的四個命令在 AOF 文件中就實際保存如下:
~~~
*2
$6
SELECT
$1
0
*6
$5
RPUSH
$4
list
$1
1
$1
2
$1
3
$1
4
*2
$4
RPOP
$4
list
*2
$4
LPOP
$4
list
*3
$5
LPUSH
$4
list
$1
1
~~~
除了 [SELECT](http://redis.readthedocs.org/en/latest/connection/select.html#select "(in Redis 命令參考 v2.8)") 命令是 AOF 程序自己加上去的之外,其他命令都是之前我們在終端里執行的命令。
同步命令到 AOF 文件的整個過程可以分為三個階段:
1. 命令傳播:Redis 將執行完的命令、命令的參數、命令的參數個數等信息發送到 AOF 程序中。
1. 緩存追加:AOF 程序根據接收到的命令數據,將命令轉換為網絡通訊協議的格式,然后將協議內容追加到服務器的 AOF 緩存中。
1. 文件寫入和保存:AOF 緩存中的內容被寫入到 AOF 文件末尾,如果設定的 AOF 保存條件被滿足的話, `fsync` 函數或者 `fdatasync` 函數會被調用,將寫入的內容真正地保存到磁盤中。
以下幾個小節將詳細地介紹這三個步驟。
### 命令傳播
當一個 Redis 客戶端需要執行命令時,它通過網絡連接,將協議文本發送給 Redis 服務器。
比如說,要執行命令 `SET KEY VALUE` ,客戶端將向服務器發送文本 `"*3\r\n$3\r\nSET\r\n$3\r\nKEY\r\n$5\r\nVALUE\r\n"` 。
服務器在接到客戶端的請求之后,它會根據協議文本的內容,選擇適當的命令函數,并將各個參數從字符串文本轉換為 Redis 字符串對象(`StringObject`)。
比如說,針對上面的 [SET](http://redis.readthedocs.org/en/latest/string/set.html#set "(in Redis 命令參考 v2.8)") 命令例子,Redis 將客戶端的命令指針指向實現 [SET](http://redis.readthedocs.org/en/latest/string/set.html#set "(in Redis 命令參考 v2.8)") 命令的 `setCommand` 函數,并創建三個 Redis 字符串對象,分別保存 `SET` 、 `KEY` 和 `VALUE` 三個參數(命令也算作參數)。
每當命令函數成功執行之后,命令參數都會被傳播到 AOF 程序,以及 REPLICATION 程序(本節不討論這個,列在這里只是為了完整性的考慮)。
這個執行并傳播命令的過程可以用以下偽代碼表示:
~~~
if (execRedisCommand(cmd, argv, argc) == EXEC_SUCCESS):
if aof_is_turn_on():
# 傳播命令到 AOF 程序
propagate_aof(cmd, argv, argc)
if replication_is_turn_on():
# 傳播命令到 REPLICATION 程序
propagate_replication(cmd, argv, argc)
~~~
以下是該過程的流程圖:
![digraph propagate { node [shape = plaintext, style = filled]; edge [style = bold]; // node exec [label = "命令執行成功", fillcolor = "#FADCAD"]; aof_choice [label = "AOF\n 功能已打開?", shape = diamond, fillcolor = "#95BBE3"]; propagate_aof [label = "傳播命令到 AOF 程序", fillcolor = "#A8E270"]; replication_choice [label = "REPLICATION\n 功能已打開?", shape = diamond, fillcolor = "#95BBE3"]; propagate_replication [label = "傳播命令到 REPLICATION 程序", fillcolor = "#A8E270"]; remaind_jobs [label = "處理后續步驟:\n清理資源、\n等等", fillcolor = "#FADCAD"]; // edge exec -> aof_choice; aof_choice -> propagate_aof [label = "是"]; propagate_aof -> replication_choice; aof_choice -> replication_choice [label = "否"]; replication_choice -> remaind_jobs [label = "否"]; replication_choice -> propagate_replication [label = "是"]; propagate_replication -> remaind_jobs;}](https://box.kancloud.cn/2015-09-13_55f4effe9975f.svg)
### 緩存追加
當命令被傳播到 AOF 程序之后,程序會根據命令以及命令的參數,將命令從字符串對象轉換回原來的協議文本。
比如說,如果 AOF 程序接受到的三個參數分別保存著 `SET` 、 `KEY` 和 `VALUE` 三個字符串,那么它將生成協議文本 `"*3\r\n$3\r\nSET\r\n$3\r\nKEY\r\n$5\r\nVALUE\r\n"` 。
協議文本生成之后,它會被追加到 `redis.h/redisServer` 結構的 `aof_buf` 末尾。
`redisServer` 結構維持著 Redis 服務器的狀態,`aof_buf` 域則保存著所有等待寫入到 AOF 文件的協議文本:
~~~
struct redisServer {
// 其他域...
sds aof_buf;
// 其他域...
};
~~~
至此,追加命令到緩存的步驟執行完畢。
綜合起來,整個緩存追加過程可以分為以下三步:
1. 接受命令、命令的參數、以及參數的個數、所使用的數據庫等信息。
1. 將命令還原成 Redis 網絡通訊協議。
1. 將協議文本追加到 `aof_buf` 末尾。
### 文件寫入和保存
每當服務器常規任務函數被執行、或者事件處理器被執行時,`aof.c/flushAppendOnlyFile` 函數都會被調用,這個函數執行以下兩個工作:
WRITE:根據條件,將 `aof_buf` 中的緩存寫入到 AOF 文件。
SAVE:根據條件,調用 `fsync` 或 `fdatasync` 函數,將 AOF 文件保存到磁盤中。
兩個步驟都需要根據一定的條件來執行,而這些條件由 AOF 所使用的保存模式來決定,以下小節就來介紹 AOF 所使用的三種保存模式,以及在這些模式下,步驟 WRITE 和 SAVE 的調用條件。
### AOF 保存模式
Redis 目前支持三種 AOF 保存模式,它們分別是:
1. `AOF_FSYNC_NO` :不保存。
1. `AOF_FSYNC_EVERYSEC` :每一秒鐘保存一次。
1. `AOF_FSYNC_ALWAYS` :每執行一個命令保存一次。
以下三個小節將分別討論這三種保存模式。
### 不保存
在這種模式下,每次調用 `flushAppendOnlyFile` 函數,WRITE 都會被執行,但 SAVE 會被略過。
在這種模式下, SAVE 只會在以下任意一種情況中被執行:
- Redis 被關閉
- AOF 功能被關閉
- 系統的寫緩存被刷新(可能是緩存已經被寫滿,或者定期保存操作被執行)
這三種情況下的 SAVE 操作都會引起 Redis 主進程阻塞。
### 每一秒鐘保存一次
在這種模式中,SAVE 原則上每隔一秒鐘就會執行一次,因為 SAVE 操作是由后臺子線程調用的,所以它不會引起服務器主進程阻塞。
注意,在上一句的說明里面使用了詞語“原則上”,在實際運行中,程序在這種模式下對 `fsync` 或 `fdatasync` 的調用并不是每秒一次,它和調用 `flushAppendOnlyFile` 函數時 Redis 所處的狀態有關。
每當 `flushAppendOnlyFile` 函數被調用時,可能會出現以下四種情況:
-
子線程正在執行 SAVE ,并且:
>
> 1. 這個 SAVE 的執行時間未超過 2 秒,那么程序直接返回,并不執行 WRITE 或新的 SAVE 。
> 1. 這個 SAVE 已經執行超過 2 秒,那么程序執行 WRITE ,但不執行新的 SAVE 。注意,因為這時 WRITE 的寫入必須等待子線程先完成(舊的) SAVE ,因此這里 WRITE 會比平時阻塞更長時間。
-
子線程沒有在執行 SAVE ,并且:
>
> 1. 上次成功執行 SAVE 距今不超過 1 秒,那么程序執行 WRITE ,但不執行 SAVE 。
> 1. 上次成功執行 SAVE 距今已經超過 1 秒,那么程序執行 WRITE 和 SAVE 。
可以用流程圖表示這四種情況:
![digraph flush { node [shape = plaintext, style = filled, fillcolor = "#FADCAD"]; edge [style = bold]; // SAVE_running_choice [label = "SAVE 正在執行?", shape = diamond, fillcolor = "#A8E270"]; over_2_second_choice [label = "運行時間\n超過 2 秒?", shape = diamond, fillcolor = "#95BBE3"]; not_over_2_second [label = "情況 1 :\n函數直接返回\n 不執行 WRITE 或\n新的 SAVE"]; over_2_second [label = "情況 2 :\n執行 WRITE \n 但不執行新的 SAVE \n"]; SAVE_running_choice -> over_2_second_choice [label = "是"]; over_2_second_choice -> not_over_2_second [label = "否"]; over_2_second_choice -> over_2_second [label = "是"]; finish_over_2_second [label = "距離上次 SAVE\n 執行成功\n超過 1 秒?", shape = diamond, fillcolor = "#95BBE3"]; no [label = "情況 3 :\n 執行 WRITE \n 但不執行新的 SAVE "]; yes [label = "情況 4 :\n 執行 WRITE 和\n新的 SAVE\n"]; SAVE_running_choice -> finish_over_2_second [label = "否"]; finish_over_2_second -> yes [label = "是"]; finish_over_2_second -> no [label = "否"];}](https://box.kancloud.cn/2015-09-13_55f4effea42f9.svg)
根據以上說明可以知道,在“每一秒鐘保存一次”模式下,如果在情況 1 中發生故障停機,那么用戶最多損失小于 2 秒內所產生的所有數據。
如果在情況 2 中發生故障停機,那么用戶損失的數據是可以超過 2 秒的。
Redis 官網上所說的,AOF 在“每一秒鐘保存一次”時發生故障,只丟失 1 秒鐘數據的說法,實際上并不準確。
### 每執行一個命令保存一次
在這種模式下,每次執行完一個命令之后, WRITE 和 SAVE 都會被執行。
另外,因為 SAVE 是由 Redis 主進程執行的,所以在 SAVE 執行期間,主進程會被阻塞,不能接受命令請求。
### AOF 保存模式對性能和安全性的影響
在上一個小節,我們簡短地描述了三種 AOF 保存模式的工作方式,現在,是時候研究一下這三個模式在安全性和性能方面的區別了。
對于三種 AOF 保存模式,它們對服務器主進程的阻塞情況如下:
1. 不保存(`AOF_FSYNC_NO`):寫入和保存都由主進程執行,兩個操作都會阻塞主進程。
1. 每一秒鐘保存一次(`AOF_FSYNC_EVERYSEC`):寫入操作由主進程執行,阻塞主進程。保存操作由子線程執行,不直接阻塞主進程,但保存操作完成的快慢會影響寫入操作的阻塞時長。
1. 每執行一個命令保存一次(`AOF_FSYNC_ALWAYS`):和模式 1 一樣。
因為阻塞操作會讓 Redis 主進程無法持續處理請求,所以一般說來,阻塞操作執行得越少、完成得越快,Redis 的性能就越好。
模式 1 的保存操作只會在AOF 關閉或 Redis 關閉時執行,或者由操作系統觸發,在一般情況下,這種模式只需要為寫入阻塞,因此它的寫入性能要比后面兩種模式要高,當然,這種性能的提高是以降低安全性為代價的:在這種模式下,如果運行的中途發生停機,那么丟失數據的數量由操作系統的緩存沖洗策略決定。
模式 2 在性能方面要優于模式 3 ,并且在通常情況下,這種模式最多丟失不多于 2 秒的數據,所以它的安全性要高于模式 1 ,這是一種兼顧性能和安全性的保存方案。
模式 3 的安全性是最高的,但性能也是最差的,因為服務器必須阻塞直到命令信息被寫入并保存到磁盤之后,才能繼續處理請求。
綜合起來,三種 AOF 模式的操作特性可以總結如下:
| 模式 | WRITE 是否阻塞? | SAVE 是否阻塞? | 停機時丟失的數據量 |
|-----|-----|-----|-----|
| `AOF_FSYNC_NO` | 阻塞 | 阻塞 | 操作系統最后一次對 AOF 文件觸發 SAVE 操作之后的數據。 |
| `AOF_FSYNC_EVERYSEC` | 阻塞 | 不阻塞 | 一般情況下不超過 2 秒鐘的數據。 |
| `AOF_FSYNC_ALWAYS` | 阻塞 | 阻塞 | 最多只丟失一個命令的數據。 |
### AOF 文件的讀取和數據還原
AOF 文件保存了 Redis 的數據庫狀態,而文件里面包含的都是符合 Redis 通訊協議格式的命令文本。
這也就是說,只要根據 AOF 文件里的協議,重新執行一遍里面指示的所有命令,就可以還原 Redis 的數據庫狀態了。
Redis 讀取 AOF 文件并還原數據庫的詳細步驟如下:
1. 創建一個不帶網絡連接的偽客戶端(fake client)。
1. 讀取 AOF 所保存的文本,并根據內容還原出命令、命令的參數以及命令的個數。
1. 根據命令、命令的參數和命令的個數,使用偽客戶端執行該命令。
1. 執行 2 和 3 ,直到 AOF 文件中的所有命令執行完畢。
完成第 4 步之后,AOF 文件所保存的數據庫就會被完整地還原出來。
注意,因為 Redis 的命令只能在客戶端的上下文中被執行,而 AOF 還原時所使用的命令來自于 AOF 文件,而不是網絡,所以程序使用了一個沒有網絡連接的偽客戶端來執行命令。偽客戶端執行命令的效果,和帶網絡連接的客戶端執行命令的效果,完全一樣。
整個讀取和還原過程可以用以下偽代碼表示:
~~~
def READ_AND_LOAD_AOF():
# 打開并讀取 AOF 文件
file = open(aof_file_name)
while file.is_not_reach_eof():
# 讀入一條協議文本格式的 Redis 命令
cmd_in_text = file.read_next_command_in_protocol_format()
# 根據文本命令,查找命令函數,并創建參數和參數個數等對象
cmd, argv, argc = text_to_command(cmd_in_text)
# 執行命令
execRedisCommand(cmd, argv, argc)
# 關閉文件
file.close()
~~~
作為例子,以下是一個簡短的 AOF 文件的內容:
~~~
*2
$6
SELECT
$1
0
*3
$3
SET
$3
key
$5
value
*8
$5
RPUSH
$4
list
$1
1
$1
2
$1
3
$1
4
$1
5
$1
6
~~~
當程序讀入這個 AOF 文件時,它首先執行 `SELECT 0` 命令 ——這個 `SELECT` 命令是由 AOF 寫入程序自動生成的,它確保程序可以將數據還原到正確的數據庫上。
然后執行后面的 `SET key value` 和 `RPUSH 1 2 3 4` 命令,還原 `key` 和 `list` 兩個鍵的數據。
Note
為了避免對數據的完整性產生影響,在服務器載入數據的過程中,只有和數據庫無關的訂閱與發布功能可以正常使用,其他命令一律返回錯誤。
### AOF 重寫
AOF 文件通過同步 Redis 服務器所執行的命令,從而實現了數據庫狀態的記錄,但是,這種同步方式會造成一個問題:隨著運行時間的流逝,AOF 文件會變得越來越大。
舉個例子,如果服務器執行了以下命令:
~~~
RPUSH list 1 2 3 4 // [1, 2, 3, 4]
RPOP list // [1, 2, 3]
LPOP list // [2, 3]
LPUSH list 1 // [1, 2, 3]
~~~
那么光是記錄 `list` 鍵的狀態,AOF 文件就需要保存四條命令。
另一方面,有些被頻繁操作的鍵,對它們所調用的命令可能有成百上千、甚至上萬條,如果這樣被頻繁操作的鍵有很多的話,AOF 文件的體積就會急速膨脹,對 Redis 、甚至整個系統的造成影響。
為了解決以上的問題,Redis 需要對 AOF 文件進行重寫(rewrite):創建一個新的 AOF 文件來代替原有的 AOF 文件,新 AOF 文件和原有 AOF 文件保存的數據庫狀態完全一樣,但新 AOF 文件的體積小于等于原有 AOF 文件的體積。
以下就來介紹 AOF 重寫的實現方式。
### AOF 重寫的實現
所謂的“重寫”其實是一個有歧義的詞語,實際上,AOF 重寫并不需要對原有的 AOF 文件進行任何寫入和讀取,它針對的是數據庫中鍵的當前值。
考慮這樣一個情況,如果服務器對鍵 `list` 執行了以下四條命令:
~~~
RPUSH list 1 2 3 4 // [1, 2, 3, 4]
RPOP list // [1, 2, 3]
LPOP list // [2, 3]
LPUSH list 1 // [1, 2, 3]
~~~
那么當前列表鍵 `list` 在數據庫中的值就為 `[1, 2, 3]` 。
如果我們要保存這個列表的當前狀態,并且盡量減少所使用的命令數,那么最簡單的方式不是去 AOF 文件上分析前面執行的四條命令,而是直接讀取 `list` 鍵在數據庫的當前值,然后用一條 `RPUSH 1 2 3` 命令來代替前面的四條命令。
再考慮這樣一個例子,如果服務器對集合鍵 `animal` 執行了以下命令:
~~~
SADD animal cat // {cat}
SADD animal dog panda tiger // {cat, dog, panda, tiger}
SREM animal cat // {dog, panda, tiger}
SADD animal cat lion // {cat, lion, dog, panda, tiger}
~~~
那么使用一條 `SADD animal cat lion dog panda tiger` 命令,就可以還原 `animal` 集合的狀態,這比之前的四條命令調用要大大減少。
除了列表和集合之外,字符串、有序集、哈希表等鍵也可以用類似的方法來保存狀態,并且保存這些狀態所使用的命令數量,比起之前建立這些鍵的狀態所使用命令的數量要大大減少。
根據鍵的類型,使用適當的寫入命令來重現鍵的當前值,這就是 AOF 重寫的實現原理。整個重寫過程可以用偽代碼表示如下:
~~~
def AOF_REWRITE(tmp_tile_name):
f = create(tmp_tile_name)
# 遍歷所有數據庫
for db in redisServer.db:
# 如果數據庫為空,那么跳過這個數據庫
if db.is_empty(): continue
# 寫入 SELECT 命令,用于切換數據庫
f.write_command("SELECT " + db.number)
# 遍歷所有鍵
for key in db:
# 如果鍵帶有過期時間,并且已經過期,那么跳過這個鍵
if key.have_expire_time() and key.is_expired(): continue
if key.type == String:
# 用 SET key value 命令來保存字符串鍵
value = get_value_from_string(key)
f.write_command("SET " + key + value)
elif key.type == List:
# 用 RPUSH key item1 item2 ... itemN 命令來保存列表鍵
item1, item2, ..., itemN = get_item_from_list(key)
f.write_command("RPUSH " + key + item1 + item2 + ... + itemN)
elif key.type == Set:
# 用 SADD key member1 member2 ... memberN 命令來保存集合鍵
member1, member2, ..., memberN = get_member_from_set(key)
f.write_command("SADD " + key + member1 + member2 + ... + memberN)
elif key.type == Hash:
# 用 HMSET key field1 value1 field2 value2 ... fieldN valueN 命令來保存哈希鍵
field1, value1, field2, value2, ..., fieldN, valueN =\
get_field_and_value_from_hash(key)
f.write_command("HMSET " + key + field1 + value1 + field2 + value2 +\
... + fieldN + valueN)
elif key.type == SortedSet:
# 用 ZADD key score1 member1 score2 member2 ... scoreN memberN
# 命令來保存有序集鍵
score1, member1, score2, member2, ..., scoreN, memberN = \
get_score_and_member_from_sorted_set(key)
f.write_command("ZADD " + key + score1 + member1 + score2 + member2 +\
... + scoreN + memberN)
else:
raise_type_error()
# 如果鍵帶有過期時間,那么用 EXPIREAT key time 命令來保存鍵的過期時間
if key.have_expire_time():
f.write_command("EXPIREAT " + key + key.expire_time_in_unix_timestamp())
# 關閉文件
f.close()
~~~
### AOF 后臺重寫
上一節展示的 AOF 重寫程序可以很好地完成創建一個新 AOF 文件的任務,但是,在執行這個程序的時候,調用者線程會被阻塞。
很明顯,作為一種輔佐性的維護手段,Redis 不希望 AOF 重寫造成服務器無法處理請求,所以 Redis 決定將 AOF 重寫程序放到(后臺)子進程里執行,這樣處理的最大好處是:
1. 子進程進行 AOF 重寫期間,主進程可以繼續處理命令請求。
1. 子進程帶有主進程的數據副本,使用子進程而不是線程,可以在避免鎖的情況下,保證數據的安全性。
不過,使用子進程也有一個問題需要解決:因為子進程在進行 AOF 重寫期間,主進程還需要繼續處理命令,而新的命令可能對現有的數據進行修改,這會讓當前數據庫的數據和重寫后的 AOF 文件中的數據不一致。
為了解決這個問題,Redis 增加了一個 AOF 重寫緩存,這個緩存在 fork 出子進程之后開始啟用,Redis 主進程在接到新的寫命令之后,除了會將這個寫命令的協議內容追加到現有的 AOF 文件之外,還會追加到這個緩存中:
![digraph p { node [style = filled]; edge [style = "bold, dashed"]; // client [label = "客戶端", fillcolor = "#95BBE3"]; server [label = "服務器", fillcolor = "#A8E270"]; client -> server [label = "命令請求"]; current_aof [label = "現有 AOF 文件", shape = box, fillcolor = "#FADCAD"]; aof_rewrite_buf [label = "AOF 重寫緩存", shape = box, fillcolor = "#FADCAD"]; server -> current_aof [label = "命令協議內容"]; server -> aof_rewrite_buf [label = "命令協議內容"];}](https://box.kancloud.cn/2015-09-13_55f4effeaf2a1.svg)
換言之,當子進程在執行 AOF 重寫時,主進程需要執行以下三個工作:
1. 處理命令請求。
1. 將寫命令追加到現有的 AOF 文件中。
1. 將寫命令追加到 AOF 重寫緩存中。
這樣一來可以保證:
1. 現有的 AOF 功能會繼續執行,即使在 AOF 重寫期間發生停機,也不會有任何數據丟失。
1. 所有對數據庫進行修改的命令都會被記錄到 AOF 重寫緩存中。
當子進程完成 AOF 重寫之后,它會向父進程發送一個完成信號,父進程在接到完成信號之后,會調用一個信號處理函數,并完成以下工作:
1. 將 AOF 重寫緩存中的內容全部寫入到新 AOF 文件中。
1. 對新的 AOF 文件進行改名,覆蓋原有的 AOF 文件。
當步驟 1 執行完畢之后,現有 AOF 文件、新 AOF 文件和數據庫三者的狀態就完全一致了。
當步驟 2 執行完畢之后,程序就完成了新舊兩個 AOF 文件的交替。
這個信號處理函數執行完畢之后,主進程就可以繼續像往常一樣接受命令請求了。在整個 AOF 后臺重寫過程中,只有最后的寫入緩存和改名操作會造成主進程阻塞,在其他時候,AOF 后臺重寫都不會對主進程造成阻塞,這將 AOF 重寫對性能造成的影響降到了最低。
以上就是 AOF 后臺重寫,也即是 [BGREWRITEAOF](http://redis.readthedocs.org/en/latest/server/bgrewriteaof.html#bgrewriteaof "(in Redis 命令參考 v2.8)") 命令的工作原理。
### AOF 后臺重寫的觸發條件
AOF 重寫可以由用戶通過調用 [BGREWRITEAOF](http://redis.readthedocs.org/en/latest/server/bgrewriteaof.html#bgrewriteaof "(in Redis 命令參考 v2.8)") 手動觸發。
另外,服務器在 AOF 功能開啟的情況下,會維持以下三個變量:
- 記錄當前 AOF 文件大小的變量 `aof_current_size` 。
- 記錄最后一次 AOF 重寫之后, AOF 文件大小的變量 `aof_rewrite_base_size` 。
- 增長百分比變量 `aof_rewrite_perc` 。
每次當 `serverCron` 函數執行時,它都會檢查以下條件是否全部滿足,如果是的話,就會觸發自動的 AOF 重寫:
1. 沒有 [BGSAVE](http://redis.readthedocs.org/en/latest/server/bgsave.html#bgsave "(in Redis 命令參考 v2.8)") 命令在進行。
1. 沒有 [BGREWRITEAOF](http://redis.readthedocs.org/en/latest/server/bgrewriteaof.html#bgrewriteaof "(in Redis 命令參考 v2.8)") 在進行。
1. 當前 AOF 文件大小大于 `server.aof_rewrite_min_size` (默認值為 1 MB)。
1. 當前 AOF 文件大小和最后一次 AOF 重寫后的大小之間的比率大于等于指定的增長百分比。
默認情況下,增長百分比為 `100%` ,也即是說,如果前面三個條件都已經滿足,并且當前 AOF 文件大小比最后一次 AOF 重寫時的大小要大一倍的話,那么觸發自動 AOF 重寫。
### 小結
- AOF 文件通過保存所有修改數據庫的命令來記錄數據庫的狀態。
- AOF 文件中的所有命令都以 Redis 通訊協議的格式保存。
- 不同的 AOF 保存模式對數據的安全性、以及 Redis 的性能有很大的影響。
- AOF 重寫的目的是用更小的體積來保存數據庫狀態,整個重寫過程基本上不影響 Redis 主進程處理命令請求。
- AOF 重寫是一個有歧義的名字,實際的重寫工作是針對數據庫的當前值來進行的,程序既不讀寫、也不使用原有的 AOF 文件。
- AOF 可以由用戶手動觸發,也可以由服務器自動觸發。