Redis 分別提供了 RDB 和 AOF 兩種持久化機(jī)制:
server [label = "命令請(qǐng)求"]; server -> aof [ label = "網(wǎng)絡(luò)協(xié)議格式的\n命令內(nèi)容"];}" />
本章首先介紹 AOF 功能的運(yùn)作機(jī)制,了解命令是如何被保存到 AOF 文件里的,觀察不同的 AOF 保存模式對(duì)數(shù)據(jù)的安全性、以及 Redis 性能的影響。
之后會(huì)介紹從 AOF 文件中恢復(fù)數(shù)據(jù)庫(kù)狀態(tài)的方法,以及該方法背后的實(shí)現(xiàn)機(jī)制。
最后還會(huì)介紹對(duì) AOF 進(jìn)行重寫(xiě)以調(diào)整文件體積的方法,并研究這種方法是如何在不改變數(shù)據(jù)庫(kù)狀態(tài)的前提下進(jìn)行的。
因?yàn)楸菊律婕?AOF 運(yùn)行的相關(guān)機(jī)制,如果還沒(méi)了解過(guò) AOF 功能的話,請(qǐng)先閱讀 Redis 持久化手冊(cè)中關(guān)于 AOF 的部分 。
Redis 將所有對(duì)數(shù)據(jù)庫(kù)進(jìn)行過(guò)寫(xiě)入的命令(及其參數(shù))記錄到 AOF 文件,以此達(dá)到記錄數(shù)據(jù)庫(kù)狀態(tài)的目的,為了方便起見(jiàn),我們稱呼這種記錄過(guò)程為同步。
舉個(gè)例子,如果執(zhí)行以下命令:
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"
那么其中四條對(duì)數(shù)據(jù)庫(kù)有修改的寫(xiě)入命令就會(huì)被同步到 AOF 文件中:
RPUSH list 1 2 3 4
RPOP list
LPOP list
LPUSH list 1
為了處理的方便,AOF 文件使用網(wǎng)絡(luò)通訊協(xié)議的格式來(lái)保存這些命令。
比如說(shuō),上面列舉的四個(gè)命令在 AOF 文件中就實(shí)際保存如下:
*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 命令是 AOF 程序自己加上去的之外,其他命令都是之前我們?cè)诮K端里執(zhí)行的命令。
同步命令到 AOF 文件的整個(gè)過(guò)程可以分為三個(gè)階段:
fsync
函數(shù)或者 fdatasync
函數(shù)會(huì)被調(diào)用,將寫(xiě)入的內(nèi)容真正地保存到磁盤(pán)中。以下幾個(gè)小節(jié)將詳細(xì)地介紹這三個(gè)步驟。
當(dāng)一個(gè) Redis 客戶端需要執(zhí)行命令時(shí),它通過(guò)網(wǎng)絡(luò)連接,將協(xié)議文本發(fā)送給 Redis 服務(wù)器。
比如說(shuō),要執(zhí)行命令 SET KEY VALUE
,客戶端將向服務(wù)器發(fā)送文本 "*3\r\n$3\r\nSET\r\n$3\r\nKEY\r\n$5\r\nVALUE\r\n"
。
服務(wù)器在接到客戶端的請(qǐng)求之后,它會(huì)根據(jù)協(xié)議文本的內(nèi)容,選擇適當(dāng)?shù)拿詈瘮?shù),并將各個(gè)參數(shù)從字符串文本轉(zhuǎn)換為 Redis 字符串對(duì)象(StringObject
)。
比如說(shuō),針對(duì)上面的 SET 命令例子,Redis 將客戶端的命令指針指向?qū)崿F(xiàn) SET 命令的 setCommand
函數(shù),并創(chuàng)建三個(gè) Redis 字符串對(duì)象,分別保存 SET
、 KEY
和 VALUE
三個(gè)參數(shù)(命令也算作參數(shù))。
每當(dāng)命令函數(shù)成功執(zhí)行之后,命令參數(shù)都會(huì)被傳播到 AOF 程序,以及 REPLICATION 程序(本節(jié)不討論這個(gè),列在這里只是為了完整性的考慮)。
這個(gè)執(zhí)行并傳播命令的過(guò)程可以用以下偽代碼表示:
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)
以下是該過(guò)程的流程圖:
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;}" />
當(dāng)命令被傳播到 AOF 程序之后,程序會(huì)根據(jù)命令以及命令的參數(shù),將命令從字符串對(duì)象轉(zhuǎn)換回原來(lái)的協(xié)議文本。
比如說(shuō),如果 AOF 程序接受到的三個(gè)參數(shù)分別保存著 SET
、 KEY
和 VALUE
三個(gè)字符串,那么它將生成協(xié)議文本 "*3\r\n$3\r\nSET\r\n$3\r\nKEY\r\n$5\r\nVALUE\r\n"
。
協(xié)議文本生成之后,它會(huì)被追加到 redis.h/redisServer
結(jié)構(gòu)的 aof_buf
末尾。
redisServer
結(jié)構(gòu)維持著 Redis 服務(wù)器的狀態(tài),aof_buf
域則保存著所有等待寫(xiě)入到 AOF 文件的協(xié)議文本:
struct redisServer {
// 其他域...
sds aof_buf;
// 其他域...
};
至此,追加命令到緩存的步驟執(zhí)行完畢。
綜合起來(lái),整個(gè)緩存追加過(guò)程可以分為以下三步:
aof_buf
末尾。每當(dāng)服務(wù)器常規(guī)任務(wù)函數(shù)被執(zhí)行、或者事件處理器被執(zhí)行時(shí),aof.c/flushAppendOnlyFile
函數(shù)都會(huì)被調(diào)用,這個(gè)函數(shù)執(zhí)行以下兩個(gè)工作:
WRITE:根據(jù)條件,將 aof_buf
中的緩存寫(xiě)入到 AOF 文件。
SAVE:根據(jù)條件,調(diào)用 fsync
或 fdatasync
函數(shù),將 AOF 文件保存到磁盤(pán)中。
兩個(gè)步驟都需要根據(jù)一定的條件來(lái)執(zhí)行,而這些條件由 AOF 所使用的保存模式來(lái)決定,以下小節(jié)就來(lái)介紹 AOF 所使用的三種保存模式,以及在這些模式下,步驟 WRITE 和 SAVE 的調(diào)用條件。
Redis 目前支持三種 AOF 保存模式,它們分別是:
AOF_FSYNC_NO
:不保存。AOF_FSYNC_EVERYSEC
:每一秒鐘保存一次。AOF_FSYNC_ALWAYS
:每執(zhí)行一個(gè)命令保存一次。以下三個(gè)小節(jié)將分別討論這三種保存模式。
在這種模式下,每次調(diào)用 flushAppendOnlyFile
函數(shù),WRITE 都會(huì)被執(zhí)行,但 SAVE 會(huì)被略過(guò)。
在這種模式下, SAVE 只會(huì)在以下任意一種情況中被執(zhí)行:
這三種情況下的 SAVE 操作都會(huì)引起 Redis 主進(jìn)程阻塞。
在這種模式中,SAVE 原則上每隔一秒鐘就會(huì)執(zhí)行一次,因?yàn)?SAVE 操作是由后臺(tái)子線程調(diào)用的,所以它不會(huì)引起服務(wù)器主進(jìn)程阻塞。
注意,在上一句的說(shuō)明里面使用了詞語(yǔ)“原則上”,在實(shí)際運(yùn)行中,程序在這種模式下對(duì) fsync
或 fdatasync
的調(diào)用并不是每秒一次,它和調(diào)用 flushAppendOnlyFile
函數(shù)時(shí) Redis 所處的狀態(tài)有關(guān)。
每當(dāng) flushAppendOnlyFile
函數(shù)被調(diào)用時(shí),可能會(huì)出現(xiàn)以下四種情況:
子線程正在執(zhí)行 SAVE ,并且:
- 這個(gè) SAVE 的執(zhí)行時(shí)間未超過(guò) 2 秒,那么程序直接返回,并不執(zhí)行 WRITE 或新的 SAVE 。
- 這個(gè) SAVE 已經(jīng)執(zhí)行超過(guò) 2 秒,那么程序執(zhí)行 WRITE ,但不執(zhí)行新的 SAVE 。注意,因?yàn)檫@時(shí) WRITE 的寫(xiě)入必須等待子線程先完成(舊的) SAVE ,因此這里 WRITE 會(huì)比平時(shí)阻塞更長(zhǎng)時(shí)間。
子線程沒(méi)有在執(zhí)行 SAVE ,并且:
- 上次成功執(zhí)行 SAVE 距今不超過(guò) 1 秒,那么程序執(zhí)行 WRITE ,但不執(zhí)行 SAVE 。
- 上次成功執(zhí)行 SAVE 距今已經(jīng)超過(guò) 1 秒,那么程序執(zhí)行 WRITE 和 SAVE 。
可以用流程圖表示這四種情況:
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 執(zhí)行成功\n超過(guò) 1 秒?", shape = diamond, fillcolor = "#95BBE3"]; no [label = "情況 3 :\n 執(zhí)行 WRITE \n 但不執(zhí)行新的 SAVE "]; yes [label = "情況 4 :\n 執(zhí)行 WRITE 和\n新的 SAVE\n"]; SAVE_running_choice -> finish_over_2_second [label = "否"]; finish_over_2_second -> yes [label = "是"]; finish_over_2_second -> no [label = "否"];}" />
根據(jù)以上說(shuō)明可以知道,在“每一秒鐘保存一次”模式下,如果在情況 1 中發(fā)生故障停機(jī),那么用戶最多損失小于 2 秒內(nèi)所產(chǎn)生的所有數(shù)據(jù)。
如果在情況 2 中發(fā)生故障停機(jī),那么用戶損失的數(shù)據(jù)是可以超過(guò) 2 秒的。
Redis 官網(wǎng)上所說(shuō)的,AOF 在“每一秒鐘保存一次”時(shí)發(fā)生故障,只丟失 1 秒鐘數(shù)據(jù)的說(shuō)法,實(shí)際上并不準(zhǔn)確。
在這種模式下,每次執(zhí)行完一個(gè)命令之后, WRITE 和 SAVE 都會(huì)被執(zhí)行。
另外,因?yàn)?SAVE 是由 Redis 主進(jìn)程執(zhí)行的,所以在 SAVE 執(zhí)行期間,主進(jìn)程會(huì)被阻塞,不能接受命令請(qǐng)求。
在上一個(gè)小節(jié),我們簡(jiǎn)短地描述了三種 AOF 保存模式的工作方式,現(xiàn)在,是時(shí)候研究一下這三個(gè)模式在安全性和性能方面的區(qū)別了。
對(duì)于三種 AOF 保存模式,它們對(duì)服務(wù)器主進(jìn)程的阻塞情況如下:
AOF_FSYNC_NO
):寫(xiě)入和保存都由主進(jìn)程執(zhí)行,兩個(gè)操作都會(huì)阻塞主進(jìn)程。AOF_FSYNC_EVERYSEC
):寫(xiě)入操作由主進(jìn)程執(zhí)行,阻塞主進(jìn)程。保存操作由子線程執(zhí)行,不直接阻塞主進(jìn)程,但保存操作完成的快慢會(huì)影響寫(xiě)入操作的阻塞時(shí)長(zhǎng)。AOF_FSYNC_ALWAYS
):和模式 1 一樣。因?yàn)樽枞僮鲿?huì)讓 Redis 主進(jìn)程無(wú)法持續(xù)處理請(qǐng)求,所以一般說(shuō)來(lái),阻塞操作執(zhí)行得越少、完成得越快,Redis 的性能就越好。
模式 1 的保存操作只會(huì)在AOF 關(guān)閉或 Redis 關(guān)閉時(shí)執(zhí)行,或者由操作系統(tǒng)觸發(fā),在一般情況下,這種模式只需要為寫(xiě)入阻塞,因此它的寫(xiě)入性能要比后面兩種模式要高,當(dāng)然,這種性能的提高是以降低安全性為代價(jià)的:在這種模式下,如果運(yùn)行的中途發(fā)生停機(jī),那么丟失數(shù)據(jù)的數(shù)量由操作系統(tǒng)的緩存沖洗策略決定。
模式 2 在性能方面要優(yōu)于模式 3 ,并且在通常情況下,這種模式最多丟失不多于 2 秒的數(shù)據(jù),所以它的安全性要高于模式 1 ,這是一種兼顧性能和安全性的保存方案。
模式 3 的安全性是最高的,但性能也是最差的,因?yàn)榉?wù)器必須阻塞直到命令信息被寫(xiě)入并保存到磁盤(pán)之后,才能繼續(xù)處理請(qǐng)求。
綜合起來(lái),三種 AOF 模式的操作特性可以總結(jié)如下:
模式 | WRITE 是否阻塞? | SAVE 是否阻塞? | 停機(jī)時(shí)丟失的數(shù)據(jù)量 |
---|---|---|---|
AOF_FSYNC_NO |
阻塞 | 阻塞 | 操作系統(tǒng)最后一次對(duì) AOF 文件觸發(fā) SAVE 操作之后的數(shù)據(jù)。 |
AOF_FSYNC_EVERYSEC |
阻塞 | 不阻塞 | 一般情況下不超過(guò) 2 秒鐘的數(shù)據(jù)。 |
AOF_FSYNC_ALWAYS |
阻塞 | 阻塞 | 最多只丟失一個(gè)命令的數(shù)據(jù)。 |
AOF 文件保存了 Redis 的數(shù)據(jù)庫(kù)狀態(tài),而文件里面包含的都是符合 Redis 通訊協(xié)議格式的命令文本。
這也就是說(shuō),只要根據(jù) AOF 文件里的協(xié)議,重新執(zhí)行一遍里面指示的所有命令,就可以還原 Redis 的數(shù)據(jù)庫(kù)狀態(tài)了。
Redis 讀取 AOF 文件并還原數(shù)據(jù)庫(kù)的詳細(xì)步驟如下:
完成第 4 步之后,AOF 文件所保存的數(shù)據(jù)庫(kù)就會(huì)被完整地還原出來(lái)。
注意,因?yàn)?Redis 的命令只能在客戶端的上下文中被執(zhí)行,而 AOF 還原時(shí)所使用的命令來(lái)自于 AOF 文件,而不是網(wǎng)絡(luò),所以程序使用了一個(gè)沒(méi)有網(wǎng)絡(luò)連接的偽客戶端來(lái)執(zhí)行命令。偽客戶端執(zhí)行命令的效果,和帶網(wǎng)絡(luò)連接的客戶端執(zhí)行命令的效果,完全一樣。
整個(gè)讀取和還原過(guò)程可以用以下偽代碼表示:
def READ_AND_LOAD_AOF():
# 打開(kāi)并讀取 AOF 文件
file = open(aof_file_name)
while file.is_not_reach_eof():
# 讀入一條協(xié)議文本格式的 Redis 命令
cmd_in_text = file.read_next_command_in_protocol_format()
# 根據(jù)文本命令,查找命令函數(shù),并創(chuàng)建參數(shù)和參數(shù)個(gè)數(shù)等對(duì)象
cmd, argv, argc = text_to_command(cmd_in_text)
# 執(zhí)行命令
execRedisCommand(cmd, argv, argc)
# 關(guān)閉文件
file.close()
作為例子,以下是一個(gè)簡(jiǎn)短的 AOF 文件的內(nèi)容:
*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
當(dāng)程序讀入這個(gè) AOF 文件時(shí),它首先執(zhí)行 SELECT 0
命令 ——這個(gè) SELECT
命令是由 AOF 寫(xiě)入程序自動(dòng)生成的,它確保程序可以將數(shù)據(jù)還原到正確的數(shù)據(jù)庫(kù)上。
然后執(zhí)行后面的 SET key value
和 RPUSH 1 2 3 4
命令,還原 key
和 list
兩個(gè)鍵的數(shù)據(jù)。
Note
為了避免對(duì)數(shù)據(jù)的完整性產(chǎn)生影響,在服務(wù)器載入數(shù)據(jù)的過(guò)程中,只有和數(shù)據(jù)庫(kù)無(wú)關(guān)的訂閱與發(fā)布功能可以正常使用,其他命令一律返回錯(cuò)誤。
AOF 文件通過(guò)同步 Redis 服務(wù)器所執(zhí)行的命令,從而實(shí)現(xiàn)了數(shù)據(jù)庫(kù)狀態(tài)的記錄,但是,這種同步方式會(huì)造成一個(gè)問(wèn)題:隨著運(yùn)行時(shí)間的流逝,AOF 文件會(huì)變得越來(lái)越大。
舉個(gè)例子,如果服務(wù)器執(zhí)行了以下命令:
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
鍵的狀態(tài),AOF 文件就需要保存四條命令。
另一方面,有些被頻繁操作的鍵,對(duì)它們所調(diào)用的命令可能有成百上千、甚至上萬(wàn)條,如果這樣被頻繁操作的鍵有很多的話,AOF 文件的體積就會(huì)急速膨脹,對(duì) Redis 、甚至整個(gè)系統(tǒng)的造成影響。
為了解決以上的問(wèn)題,Redis 需要對(duì) AOF 文件進(jìn)行重寫(xiě)(rewrite):創(chuàng)建一個(gè)新的 AOF 文件來(lái)代替原有的 AOF 文件,新 AOF 文件和原有 AOF 文件保存的數(shù)據(jù)庫(kù)狀態(tài)完全一樣,但新 AOF 文件的體積小于等于原有 AOF 文件的體積。
以下就來(lái)介紹 AOF 重寫(xiě)的實(shí)現(xiàn)方式。
所謂的“重寫(xiě)”其實(shí)是一個(gè)有歧義的詞語(yǔ),實(shí)際上,AOF 重寫(xiě)并不需要對(duì)原有的 AOF 文件進(jìn)行任何寫(xiě)入和讀取,它針對(duì)的是數(shù)據(jù)庫(kù)中鍵的當(dāng)前值。
考慮這樣一個(gè)情況,如果服務(wù)器對(duì)鍵 list
執(zhí)行了以下四條命令:
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]
那么當(dāng)前列表鍵 list
在數(shù)據(jù)庫(kù)中的值就為 [1, 2, 3]
。
如果我們要保存這個(gè)列表的當(dāng)前狀態(tài),并且盡量減少所使用的命令數(shù),那么最簡(jiǎn)單的方式不是去 AOF 文件上分析前面執(zhí)行的四條命令,而是直接讀取 list
鍵在數(shù)據(jù)庫(kù)的當(dāng)前值,然后用一條 RPUSH 1 2 3
命令來(lái)代替前面的四條命令。
再考慮這樣一個(gè)例子,如果服務(wù)器對(duì)集合鍵 animal
執(zhí)行了以下命令:
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
集合的狀態(tài),這比之前的四條命令調(diào)用要大大減少。
除了列表和集合之外,字符串、有序集、哈希表等鍵也可以用類似的方法來(lái)保存狀態(tài),并且保存這些狀態(tài)所使用的命令數(shù)量,比起之前建立這些鍵的狀態(tài)所使用命令的數(shù)量要大大減少。
根據(jù)鍵的類型,使用適當(dāng)?shù)膶?xiě)入命令來(lái)重現(xiàn)鍵的當(dāng)前值,這就是 AOF 重寫(xiě)的實(shí)現(xiàn)原理。整個(gè)重寫(xiě)過(guò)程可以用偽代碼表示如下:
def AOF_REWRITE(tmp_tile_name):
f = create(tmp_tile_name)
# 遍歷所有數(shù)據(jù)庫(kù)
for db in redisServer.db:
# 如果數(shù)據(jù)庫(kù)為空,那么跳過(guò)這個(gè)數(shù)據(jù)庫(kù)
if db.is_empty(): continue
# 寫(xiě)入 SELECT 命令,用于切換數(shù)據(jù)庫(kù)
f.write_command("SELECT " + db.number)
# 遍歷所有鍵
for key in db:
# 如果鍵帶有過(guò)期時(shí)間,并且已經(jīng)過(guò)期,那么跳過(guò)這個(gè)鍵
if key.have_expire_time() and key.is_expired(): continue
if key.type == String:
# 用 SET key value 命令來(lái)保存字符串鍵
value = get_value_from_string(key)
f.write_command("SET " + key + value)
elif key.type == List:
# 用 RPUSH key item1 item2 ... itemN 命令來(lái)保存列表鍵
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 命令來(lái)保存集合鍵
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 命令來(lái)保存哈希鍵
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
# 命令來(lái)保存有序集鍵
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()
# 如果鍵帶有過(guò)期時(shí)間,那么用 EXPIREAT key time 命令來(lái)保存鍵的過(guò)期時(shí)間
if key.have_expire_time():
f.write_command("EXPIREAT " + key + key.expire_time_in_unix_timestamp())
# 關(guān)閉文件
f.close()
上一節(jié)展示的 AOF 重寫(xiě)程序可以很好地完成創(chuàng)建一個(gè)新 AOF 文件的任務(wù),但是,在執(zhí)行這個(gè)程序的時(shí)候,調(diào)用者線程會(huì)被阻塞。
很明顯,作為一種輔佐性的維護(hù)手段,Redis 不希望 AOF 重寫(xiě)造成服務(wù)器無(wú)法處理請(qǐng)求,所以 Redis 決定將 AOF 重寫(xiě)程序放到(后臺(tái))子進(jìn)程里執(zhí)行,這樣處理的最大好處是:
不過(guò),使用子進(jìn)程也有一個(gè)問(wèn)題需要解決:因?yàn)樽舆M(jìn)程在進(jìn)行 AOF 重寫(xiě)期間,主進(jìn)程還需要繼續(xù)處理命令,而新的命令可能對(duì)現(xiàn)有的數(shù)據(jù)進(jìn)行修改,這會(huì)讓當(dāng)前數(shù)據(jù)庫(kù)的數(shù)據(jù)和重寫(xiě)后的 AOF 文件中的數(shù)據(jù)不一致。
為了解決這個(gè)問(wèn)題,Redis 增加了一個(gè) AOF 重寫(xiě)緩存,這個(gè)緩存在 fork 出子進(jìn)程之后開(kāi)始啟用,Redis 主進(jìn)程在接到新的寫(xiě)命令之后,除了會(huì)將這個(gè)寫(xiě)命令的協(xié)議內(nèi)容追加到現(xiàn)有的 AOF 文件之外,還會(huì)追加到這個(gè)緩存中:
server [label = "命令請(qǐng)求"]; current_aof [label = "現(xiàn)有 AOF 文件", shape = box, fillcolor = "#FADCAD"]; aof_rewrite_buf [label = "AOF 重寫(xiě)緩存", shape = box, fillcolor = "#FADCAD"]; server -> current_aof [label = "命令協(xié)議內(nèi)容"]; server -> aof_rewrite_buf [label = "命令協(xié)議內(nèi)容"];}" />
換言之,當(dāng)子進(jìn)程在執(zhí)行 AOF 重寫(xiě)時(shí),主進(jìn)程需要執(zhí)行以下三個(gè)工作:
這樣一來(lái)可以保證:
當(dāng)子進(jìn)程完成 AOF 重寫(xiě)之后,它會(huì)向父進(jìn)程發(fā)送一個(gè)完成信號(hào),父進(jìn)程在接到完成信號(hào)之后,會(huì)調(diào)用一個(gè)信號(hào)處理函數(shù),并完成以下工作:
當(dāng)步驟 1 執(zhí)行完畢之后,現(xiàn)有 AOF 文件、新 AOF 文件和數(shù)據(jù)庫(kù)三者的狀態(tài)就完全一致了。
當(dāng)步驟 2 執(zhí)行完畢之后,程序就完成了新舊兩個(gè) AOF 文件的交替。
這個(gè)信號(hào)處理函數(shù)執(zhí)行完畢之后,主進(jìn)程就可以繼續(xù)像往常一樣接受命令請(qǐng)求了。在整個(gè) AOF 后臺(tái)重寫(xiě)過(guò)程中,只有最后的寫(xiě)入緩存和改名操作會(huì)造成主進(jìn)程阻塞,在其他時(shí)候,AOF 后臺(tái)重寫(xiě)都不會(huì)對(duì)主進(jìn)程造成阻塞,這將 AOF 重寫(xiě)對(duì)性能造成的影響降到了最低。
以上就是 AOF 后臺(tái)重寫(xiě),也即是 BGREWRITEAOF 命令的工作原理。
AOF 重寫(xiě)可以由用戶通過(guò)調(diào)用 BGREWRITEAOF 手動(dòng)觸發(fā)。
另外,服務(wù)器在 AOF 功能開(kāi)啟的情況下,會(huì)維持以下三個(gè)變量:
aof_current_size
。aof_rewrite_base_size
。aof_rewrite_perc
。每次當(dāng) serverCron
函數(shù)執(zhí)行時(shí),它都會(huì)檢查以下條件是否全部滿足,如果是的話,就會(huì)觸發(fā)自動(dòng)的 AOF 重寫(xiě):
server.aof_rewrite_min_size
(默認(rèn)值為 1 MB)。默認(rèn)情況下,增長(zhǎng)百分比為 100%
,也即是說(shuō),如果前面三個(gè)條件都已經(jīng)滿足,并且當(dāng)前 AOF 文件大小比最后一次 AOF 重寫(xiě)時(shí)的大小要大一倍的話,那么觸發(fā)自動(dòng) AOF 重寫(xiě)。
更多建議: