99re热这里只有精品视频,7777色鬼xxxx欧美色妇,国产成人精品一区二三区在线观看,内射爽无广熟女亚洲,精品人妻av一区二区三区

AOF

2018-08-02 11:56 更新

AOF

Redis 分別提供了 RDB 和 AOF 兩種持久化機(jī)制:

  • RDB 將數(shù)據(jù)庫(kù)的快照(snapshot)以二進(jìn)制的方式保存到磁盤(pán)中。
  • AOF 則以協(xié)議文本的方式,將所有對(duì)數(shù)據(jù)庫(kù)進(jìn)行過(guò)寫(xiě)入的命令(及其參數(shù))記錄到 AOF 文件,以此達(dá)到記錄數(shù)據(jù)庫(kù)狀態(tài)的目的。

digraph persistent {    rankdir = LR;    node [shape = circle, style = filled];    edge [style = 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 的部分

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è)階段:

  1. 命令傳播:Redis 將執(zhí)行完的命令、命令的參數(shù)、命令的參數(shù)個(gè)數(shù)等信息發(fā)送到 AOF 程序中。
  2. 緩存追加:AOF 程序根據(jù)接收到的命令數(shù)據(jù),將命令轉(zhuǎn)換為網(wǎng)絡(luò)通訊協(xié)議的格式,然后將協(xié)議內(nèi)容追加到服務(wù)器的 AOF 緩存中。
  3. 文件寫(xiě)入和保存:AOF 緩存中的內(nèi)容被寫(xiě)入到 AOF 文件末尾,如果設(shè)定的 AOF 保存條件被滿足的話, 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 、 KEYVALUE 三個(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ò)程的流程圖:

digraph propagate {    node [shape = plaintext, style = filled];    edge [style = bold];    // node     exec [label =

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ù)分別保存著 SETKEYVALUE 三個(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ò)程可以分為以下三步:

  1. 接受命令、命令的參數(shù)、以及參數(shù)的個(gè)數(shù)、所使用的數(shù)據(jù)庫(kù)等信息。
  2. 將命令還原成 Redis 網(wǎng)絡(luò)通訊協(xié)議。
  3. 將協(xié)議文本追加到 aof_buf 末尾。

文件寫(xiě)入和保存

每當(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)用 fsyncfdatasync 函數(shù),將 AOF 文件保存到磁盤(pán)中。

兩個(gè)步驟都需要根據(jù)一定的條件來(lái)執(zhí)行,而這些條件由 AOF 所使用的保存模式來(lái)決定,以下小節(jié)就來(lái)介紹 AOF 所使用的三種保存模式,以及在這些模式下,步驟 WRITE 和 SAVE 的調(diào)用條件。

AOF 保存模式

Redis 目前支持三種 AOF 保存模式,它們分別是:

  1. AOF_FSYNC_NO :不保存。
  2. AOF_FSYNC_EVERYSEC :每一秒鐘保存一次。
  3. AOF_FSYNC_ALWAYS :每執(zhí)行一個(gè)命令保存一次。

以下三個(gè)小節(jié)將分別討論這三種保存模式。

不保存

在這種模式下,每次調(diào)用 flushAppendOnlyFile 函數(shù),WRITE 都會(huì)被執(zhí)行,但 SAVE 會(huì)被略過(guò)。

在這種模式下, SAVE 只會(huì)在以下任意一種情況中被執(zhí)行:

  • Redis 被關(guān)閉
  • AOF 功能被關(guān)閉
  • 系統(tǒng)的寫(xiě)緩存被刷新(可能是緩存已經(jīng)被寫(xiě)滿,或者定期保存操作被執(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ì) fsyncfdatasync 的調(diào)用并不是每秒一次,它和調(diào)用 flushAppendOnlyFile 函數(shù)時(shí) Redis 所處的狀態(tài)有關(guān)。

每當(dāng) flushAppendOnlyFile 函數(shù)被調(diào)用時(shí),可能會(huì)出現(xiàn)以下四種情況:

  • 子線程正在執(zhí)行 SAVE ,并且:

  1. 這個(gè) SAVE 的執(zhí)行時(shí)間未超過(guò) 2 秒,那么程序直接返回,并不執(zhí)行 WRITE 或新的 SAVE 。
  2. 這個(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 ,并且:

  1. 上次成功執(zhí)行 SAVE 距今不超過(guò) 1 秒,那么程序執(zhí)行 WRITE ,但不執(zhí)行 SAVE 。
  2. 上次成功執(zhí)行 SAVE 距今已經(jīng)超過(guò) 1 秒,那么程序執(zhí)行 WRITE 和 SAVE 。

可以用流程圖表示這四種情況:

digraph flush {    node [shape = plaintext, style = filled, fillcolor = 

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è)命令保存一次

在這種模式下,每次執(zhí)行完一個(gè)命令之后, WRITE 和 SAVE 都會(huì)被執(zhí)行。

另外,因?yàn)?SAVE 是由 Redis 主進(jìn)程執(zhí)行的,所以在 SAVE 執(zhí)行期間,主進(jìn)程會(huì)被阻塞,不能接受命令請(qǐng)求。

AOF 保存模式對(duì)性能和安全性的影響

在上一個(gè)小節(jié),我們簡(jiǎn)短地描述了三種 AOF 保存模式的工作方式,現(xiàn)在,是時(shí)候研究一下這三個(gè)模式在安全性和性能方面的區(qū)別了。

對(duì)于三種 AOF 保存模式,它們對(duì)服務(wù)器主進(jìn)程的阻塞情況如下:

  1. 不保存(AOF_FSYNC_NO):寫(xiě)入和保存都由主進(jìn)程執(zhí)行,兩個(gè)操作都會(huì)阻塞主進(jìn)程。
  2. 每一秒鐘保存一次(AOF_FSYNC_EVERYSEC):寫(xiě)入操作由主進(jìn)程執(zhí)行,阻塞主進(jìn)程。保存操作由子線程執(zhí)行,不直接阻塞主進(jìn)程,但保存操作完成的快慢會(huì)影響寫(xiě)入操作的阻塞時(shí)長(zhǎng)。
  3. 每執(zhí)行一個(gè)命令保存一次(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 文件的讀取和數(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ì)步驟如下:

  1. 創(chuàng)建一個(gè)不帶網(wǎng)絡(luò)連接的偽客戶端(fake client)。
  2. 讀取 AOF 所保存的文本,并根據(jù)內(nèi)容還原出命令、命令的參數(shù)以及命令的個(gè)數(shù)。
  3. 根據(jù)命令、命令的參數(shù)和命令的個(gè)數(shù),使用偽客戶端執(zhí)行該命令。
  4. 執(zhí)行 2 和 3 ,直到 AOF 文件中的所有命令執(zhí)行完畢。

完成第 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 valueRPUSH 1 2 3 4 命令,還原 keylist 兩個(gè)鍵的數(shù)據(jù)。

Note

為了避免對(duì)數(shù)據(jù)的完整性產(chǎn)生影響,在服務(wù)器載入數(shù)據(jù)的過(guò)程中,只有和數(shù)據(jù)庫(kù)無(wú)關(guān)的訂閱與發(fā)布功能可以正常使用,其他命令一律返回錯(cuò)誤。

AOF 重寫(xiě)

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)方式。

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()

AOF 后臺(tái)重寫(xiě)

上一節(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í)行,這樣處理的最大好處是:

  1. 子進(jìn)程進(jìn)行 AOF 重寫(xiě)期間,主進(jìn)程可以繼續(xù)處理命令請(qǐng)求。
  2. 子進(jìn)程帶有主進(jìn)程的數(shù)據(jù)副本,使用子進(jìn)程而不是線程,可以在避免鎖的情況下,保證數(shù)據(jù)的安全性。

不過(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è)緩存中:

digraph p {    node [style = filled];    edge [style =

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è)工作:

  1. 處理命令請(qǐng)求。
  2. 將寫(xiě)命令追加到現(xiàn)有的 AOF 文件中。
  3. 將寫(xiě)命令追加到 AOF 重寫(xiě)緩存中。

這樣一來(lái)可以保證:

  1. 現(xiàn)有的 AOF 功能會(huì)繼續(xù)執(zhí)行,即使在 AOF 重寫(xiě)期間發(fā)生停機(jī),也不會(huì)有任何數(shù)據(jù)丟失。
  2. 所有對(duì)數(shù)據(jù)庫(kù)進(jìn)行修改的命令都會(huì)被記錄到 AOF 重寫(xiě)緩存中。

當(dāng)子進(jìn)程完成 AOF 重寫(xiě)之后,它會(huì)向父進(jìn)程發(fā)送一個(gè)完成信號(hào),父進(jìn)程在接到完成信號(hào)之后,會(huì)調(diào)用一個(gè)信號(hào)處理函數(shù),并完成以下工作:

  1. 將 AOF 重寫(xiě)緩存中的內(nèi)容全部寫(xiě)入到新 AOF 文件中。
  2. 對(duì)新的 AOF 文件進(jìn)行改名,覆蓋原有的 AOF 文件。

當(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 后臺(tái)重寫(xiě)的觸發(fā)條件

AOF 重寫(xiě)可以由用戶通過(guò)調(diào)用 BGREWRITEAOF 手動(dòng)觸發(fā)。

另外,服務(wù)器在 AOF 功能開(kāi)啟的情況下,會(huì)維持以下三個(gè)變量:

  • 記錄當(dāng)前 AOF 文件大小的變量 aof_current_size 。
  • 記錄最后一次 AOF 重寫(xiě)之后, AOF 文件大小的變量 aof_rewrite_base_size 。
  • 增長(zhǎng)百分比變量 aof_rewrite_perc 。

每次當(dāng) serverCron 函數(shù)執(zhí)行時(shí),它都會(huì)檢查以下條件是否全部滿足,如果是的話,就會(huì)觸發(fā)自動(dòng)的 AOF 重寫(xiě):

  1. 沒(méi)有 BGSAVE 命令在進(jìn)行。
  2. 沒(méi)有 BGREWRITEAOF 在進(jìn)行。
  3. 當(dāng)前 AOF 文件大小大于 server.aof_rewrite_min_size (默認(rèn)值為 1 MB)。
  4. 當(dāng)前 AOF 文件大小和最后一次 AOF 重寫(xiě)后的大小之間的比率大于等于指定的增長(zhǎng)百分比。

默認(rèn)情況下,增長(zhǎng)百分比為 100% ,也即是說(shuō),如果前面三個(gè)條件都已經(jīng)滿足,并且當(dāng)前 AOF 文件大小比最后一次 AOF 重寫(xiě)時(shí)的大小要大一倍的話,那么觸發(fā)自動(dòng) AOF 重寫(xiě)。

小結(jié)

  • AOF 文件通過(guò)保存所有修改數(shù)據(jù)庫(kù)的命令來(lái)記錄數(shù)據(jù)庫(kù)的狀態(tài)。
  • AOF 文件中的所有命令都以 Redis 通訊協(xié)議的格式保存。
  • 不同的 AOF 保存模式對(duì)數(shù)據(jù)的安全性、以及 Redis 的性能有很大的影響。
  • AOF 重寫(xiě)的目的是用更小的體積來(lái)保存數(shù)據(jù)庫(kù)狀態(tài),整個(gè)重寫(xiě)過(guò)程基本上不影響 Redis 主進(jìn)程處理命令請(qǐng)求。
  • AOF 重寫(xiě)是一個(gè)有歧義的名字,實(shí)際的重寫(xiě)工作是針對(duì)數(shù)據(jù)庫(kù)的當(dāng)前值來(lái)進(jìn)行的,程序既不讀寫(xiě)、也不使用原有的 AOF 文件。
  • AOF 可以由用戶手動(dòng)觸發(fā),也可以由服務(wù)器自動(dòng)觸發(fā)。


以上內(nèi)容是否對(duì)您有幫助:
在線筆記
App下載
App下載

掃描二維碼

下載編程獅App

公眾號(hào)
微信公眾號(hào)

編程獅公眾號(hào)