1. 程式人生 > >Redis設計與實現讀書筆記-AOF,RDB,復制

Redis設計與實現讀書筆記-AOF,RDB,復制

atomic 人工 only rpo script 發送 讀書筆記 競爭條件 產生

Redis

Redis特性:

1.Redis是一個鍵值對數據庫服務器, 服務器中通常包含著任意個非空數據庫, 而每個非空數據庫中又可以包含任意個鍵值對,為了方便起見, 我們將服務器中的非空數據庫以及它們的鍵值對統稱為數據庫狀態。

2.Redis是內存數據庫,它將自己的數據庫狀態儲存在內存裏面,一旦服務器進程退出, 服務器中的數據庫狀態也會消失不見。所以redis有兩種機制可以將數據庫狀態保存到磁盤裏面:RDB,AOF。

RDB

1. RDB兩種執行方式:

a) RDB持久化既可以手動執行

b) RDB持久化可以根據服務器配置選項定期執行,該功能可以將某個時間點上的數據庫狀態保存到一個ROB文件中

2.RDB持久化功能:

a)所生成的ROB文件是一個經過壓縮的二進制文件,

b)通過該文件可以還原生成ROB文件時的數據庫狀態。

ROB文件的創建與載入

創建

一、手動創建

有兩個Redis命令可以用於生成ROB文件, 一個是SAVE, 另一個是BGSAVE。

SAVE:命令會阻塞Redis服務器進程,直到ROB文件創建完畢為止,在服務器進程阻塞期間,服務器不能處理任何命令請求。

BGSAVE :和SAVE命令直接阻塞服務器進程的做法不同,命令會派生出一個子進程, 然後由子進程負責創建RDB文件,服務器進程(父進程)繼續處理命令請求。

在BGSAVE命令執行期間, 服務器處理SAVE、 BGSAVE、 BGREWRITEAOF三個命令的方式會和平時有所不同。

1. SAVE命令會被服務器拒絕,服務器禁止SAVE命令和BGSAVE命令同時執行是為了避免父進程(服務器進程)和子進程同時執行兩個rdbSave調用,防止產生競爭條件。(進程競爭)

2.BGSAVE命令會被服務器拒絕,因為同時執行兩個BGSAVE命令也會產生競爭條件。(進程競爭)

3. BGREWRITEAOF:BGREWRITEAOF和BGSAVE兩個命令的實際工作都由子進程執行,所以這兩個命令在操作方面並沒有什麽沖突的地方,不能同時執行它們只是一個性能方面的考慮一並發出兩個子進程, 並且這兩個子進程都同時執行大量的磁盤寫入操作, 這怎麽想都不會是一個好主意。(無進程競爭問題,但兩個進程工作量都很大)

二、自動創建

因為BGSAVE命令可以在不阻塞服務器進程的情況下執行,所以Redis允許用戶通過設置服務器配置的save選項,讓服務器每隔一段時間自動執行一次BGSAVE命令。

用戶可以通過save選項設置多個保存條件,但只要其中任意一個條件被滿足,服務器就會執行BGSAVE命令。

dirty 計數器和 lastsave 屬性

dirty計數器:記錄距離上一次成功執行SAVE命令或者BGSAVE命令之後,服務器 對 數據庫狀態(服務器中的所有數據庫)進行了多少次修改(包括寫入、 刪除、 更新等操作)。

lastsave屬性:是一個UNIX時間戳, 記錄了服務器上一次成功執行SAVE命令或者BGSAVE命令的時間。

載入

服務器在載入RDB文件期間,會一直處於阻塞狀態,直到載入工作完成為止。

和使用SAVE命令或者BGSAVE命令創建RDB文件不同,RDB文件的載人工作是在服務器啟動時自動執行的,所以Redis並沒有專門用於載入RDB文件的命令,只要Redis服務器在啟動時檢測到RDB文件存在,它就會自動載入RDB文件

另外值得一提的是,因為AOF文件的更新頻率通常比ROB文件的更新頻率高,所以:

1.如果服務器開啟了AOF持久化功能,那麽服務器會優先使用 AOF文件來還原數據 庫狀態。

2.只有在AOF持久化功能處於關閉狀態時, 服務器才會使用RDB文件來還原數據庫狀態。 服務器判斷該用哪個文件來還原數據庫狀態的流程如下圖所示。

載入ROB文件的實際工作由rdb.c/rdbLoad函數完成, 這個函數和rdbSave函數之間的關系可以下圖表示

技術分享圖片

AOF

AOF持久化是通過保存 Redis服務器所執行的寫命令來記錄數據庫狀態的, 如下圖所示

技術分享圖片

命令追加

當AOF持久化功能處於打開狀態時, 服務器在執行完一個寫命令之後, 會以協議格式將被執行的寫命令追加到服務器狀態的aof_buf緩沖區的末尾。

文件寫入與同步

Redis的服務器進程就是一個事件循環(loop), 這個循環中的文件事件負責接收客戶端 的命令請求, 以及向客戶端發送命令回復。

服務器每次結束一個事件循環之前, 它都會調用flushAppendOnlyFile函 數, 考慮是否需要將aof_buf緩沖區中的內容寫人和保存到AOF文件裏面

flushAppendOnlyFile函數的行為由服務器配置的appendfsync選項的值(默認everysec)來決定,各個不同值產生的行為如下表所示。

技術分享圖片

文件載入與還原

因為AOF文件裏面包含了重建數據庫狀態所需的所有寫命令,所以服務器只要讀人並重新執行一遍AOF文件裏面保存的寫命令, 就可以還原服務器關閉之前的數據庫狀態。

Redis讀取AOF文件並還原數據庫狀態的詳細步驟如下:

1) 創建一個不帶網絡連接的偽客戶端(fake client)。

2) 從AOF文件中分析並讀取出一條寫命令。

3) 使用偽客戶端執行被讀出的寫命令。

4) 一直執行步驟2和步驟3, 直到AOF文件中的所有寫命令都被處理完畢為止。

載入過程如下圖:

技術分享圖片

AOF重寫

aof_rewrite(單進程):讀取服務器當前數據庫狀態,將命令寫入AOF文件中,使用新的AOF文件替換舊的AOF文件(缺點,單進程,重寫執行了大量寫入操作,會使函數的線程被長時間阻塞,無法處理客戶端發來的命令請求)

BGREWRITEAOF(後臺重寫):服務器fork出子進程執行重寫工作,但在重寫期間,新的寫命令會被丟失,為解決數據不一致的問題,Redis服務器設置了一個AOF重寫緩沖區,這個緩沖區在服務器創建子進程之後開始使用。

在子進程執 行AOF重寫期間,服務器進程需要執行以下三個工作:

1)執行客戶端發來的命令。

2)將執行後的寫命令追加到AOF緩沖區。

3)將執行後的寫命令追加到AOF重寫緩沖區。

技術分享圖片

當子進程完成AOF重寫工作之後,它會向父進程發送一個信號,父進程在接到該信號之後,會調用一個信號處理函數,並執行以下工作:

1)將AOF重寫緩沖區中的所有內容寫人到新AOF文件中,這時新AOF文件所保存的數據庫狀態將和服務器當前的數據庫狀態一致。

2)對新的AOF文件進行改名,原子地(atomic)覆蓋現有的AOF文件,完成新舊兩 個AOF文件的替換。

這個信號處理函數執行完畢之後,父進程就可以繼續像往常一樣接受命令請求了。

在整個AOF後臺重寫過程中,只有信號處理函數執行時會對服務器進程(父進程)造成阻塞(在同步重寫緩沖區的內容到aof文件中時),在其他時候,AOF後臺重寫都不會阻塞父進程,這將AOF重寫對服務器性能造成的影響降到了最低。

復制

在Redis中, 用戶可以通過執行SLAVEOF命令或者設置slaveof選項,讓一個服務器去復制(replicate)另一個服務器。

主服務器(master):被復制的服務器為

從服務器(slave):對主服務器進行復制的服務器

數據庫狀態一致:進行復制中的主從服務器雙方的數據庫將保存相同的數據

舊版復制功能的實現

技術分享圖片

Redis的復制功能分為同步(sync)和命令傳播(command propagate)兩個操作:

同步操作:用於將從服務器的數據庫狀態更新至主服務器當前所處的數據庫狀態。

命令傳播操作:用於在主服務器的數據庫狀態被修改,導致主從服務器的數據庫狀

態出現不一致時,讓主從服務器的數據庫重新回到一致狀態。

同步

從服務器對主服務器的同步操作需要通過向主服務器發送SYNC命令來完成, 以下是SYNC命令的執行步驟:

1)從服務器向主服務器發送SYNC命令。

2)收到SYNC命令的主服務器執行BGSAVE命令,在後臺生成一個RDB文件,並使用一個緩沖區記錄從現在開始執行的所有寫命令。

3)當主服務器的BGSAVE命令執行完畢時,主服務器會將BGSAVE命令生成的RDB 文件發送給從服務器,從服務器接收並載人這個RDB文件,將自己的數據庫狀態更新至主服務器執行BGSAVE命令時的數據庫狀態。

4)主服務器將記錄在緩沖區裏面的所有寫命令發送給從服務器,從服務器執行這些寫命令,將自己的數據庫狀態更新至主服務器數據庫當前所處的狀態。

命令傳播

在同步操作執行完畢之後, 主從服務器兩者的數據庫將達到一致狀態,但這種一致並不是一成不變的, 每當主服務器執行客戶端發送的寫命令時, 主服務器的數據庫就有可能會被修改, 並導致主從服務器狀態不再一致。

為了讓主從服務器再次回到一致狀態, 主服務器需要對從服務器執行命令傳播操作:

  1. 主服務器會將自己執行的寫命令, 也即是造成主從服務器不一致的那條寫命令,發送給從服務器執行
  2. 從服務器執行了相同的寫命令之後,主從服務器將再次回到一致狀態。

也就是說。命令傳播是在同步完成後,主服務器將每次執行的寫命令發送給從服務器執行,以此達到主從一致

舊版復制功能的缺陷

在Redis中,從服務器對主服務器的復制可以分為以下兩種情況:

初次復制:從服務器以前沒有復制過任何主服務器,或者從服務器當前要復制的主 服務器和上一次復制的主服務器不同。

斷線後重復制:處千命令傳播階段的主從服務器因為網絡原因而中斷了復制,但從服務器通過自動重連接重新連上了主服務器,並繼續復制主服務器。

缺陷:初次復制可很好地完成任務,但對於斷線後重復制來說,舊版復制功能雖然也能讓主從服務器重新回到一致狀態,但效率卻非常低-為了執行斷電期間少量的寫命令,讓主服務器重新執行一次sync命令,非常耗資源

新版復制功能的實現

為了解決舊版復制功能在處理斷線重復制情況時的低效問題,Redis從2.8版本開始,使用PSYNC命令代替SYNC命令來執行復制時的同步操作。

PSYNC命令具有完整重同步(full resynchronization)和部分重同步(partialresynchronization) 兩種模式。

完整重同步:用於處理初次復制情況,執行步驟和SYNC命令的執行步驟基本一樣,它們都是通過讓主服務器創建並發送RDB文件,以及向從服務器 發送保存在緩沖區裏面的寫命令來進行同步。

部分重同步:則用於處理斷線後重復制情況:當從服務器在斷線後重新連接主服務器時,如果條件允許,主服務器可以將主從服務器連接斷開期間執行的寫命令發送給從服務器,從服務器只要接收並執行這些寫命令,就可以將數據庫更新至主服務 器當前所處的狀態。

技術分享圖片

部分重同步的實現

部分重同步功能由以下三個部分構成:

1.主服務器的復制偏移量(replication offset) 和從服務器的復制偏移量。

2.主服務器的復制積壓緩沖區(replication backlog)。

3.服務器的運行ID (run ID)。

復制偏移量

執行復制的雙方一主服務器和從服務器會分別維護一個復制偏移量:

主服務器:每次向從服務器傳播N 個字節的數據時,就將自己的復制偏移量的值加上N。

從服務器:每次收到主服務器傳播來的N個字節的數據時, 就將自己的復制偏移量的值加上N。

通過對比主從服務器的復制偏移量,程序可以很容易地知道主從服務器是否處於一致狀態,如果主從服務器處於一致狀態,那麽主從服務器兩者的偏移量總是相同的。

相反,如果主從服務器兩者的偏移量並不相同, 那麽說明主從服務器並未處於一致狀態。

復制積壓緩沖區

復制積壓緩沖區是由主服務器維護的一個固定長度(fixed-size)先進先出(FIFO)隊列, 默認大小為1MB

當主服務器進行命令傳播時,它不僅會將寫命令發送給所有從服務器,還會將寫命令入隊到 復制積壓緩沖區裏面

技術分享圖片

因此,主服務器的復制積壓緩沖區裏面會保存著一部分最近傳播的寫命令,並且復制積

壓緩沖區會為隊列中的每個字節記錄相應的復制偏移量。

當從服務器重新連上主服務器時,從服務器會通過PSYNC命令將自己的復制偏移量 offset發送給主服務器,主服務器會根據這個復制偏移量來決定對從服務器執行何種同步操作:

1.如果offset偏移量之後的數據(也即是偏移量offset+1開始的數據)仍然存在於復制積壓緩沖區裏面,主服務器向從服務器發送+CONTINUE回復,那麽主服務器將對從服務器執行部分重同步操作。

2.相反,如果offset偏移量之後的數據已經不存在於復制積壓緩沖區, 那麽主服務器將對從服務器執行完整重同步操作。

3. 接著主服務器會將復制積壓緩沖區 10086 偏移址之後的所有數據(偏移量為 10087 至 10119) 都發送給從服務器。

復制積壓緩沖區的最小大小可以根據公式second * write_size_per_second 來估算:

其中second為從服務器斷線後重新連接上主服務器所需的平均時間(以秒計算)。

而write_size_per_second則是主服務器平均每秒產生的寫命令數據量(協議格式的寫命令的長度總和)。

服務器運行ID

每個Redis服務器, 不論主服務器還是從服務,都會有自己的運行1D。

運行1D在服務器啟動時自動生成,由40個隨機的十六進制字符組成,例如53b9b 28df8042fdc9ab5e3fcbbbabffld5dce2b3。

  1. 當從服務器對主服務器進行初次復制時,主服務器會將自己的運行1D傳送給從服務器, 而從服務器則會將這個運行1D保存起來。
  2. 當從服務器斷線並重新連上 個主服務器時,從服務器將向當前連接的主服務器發送之前保存的運行ID。
  3. 如果從服務器保存的運行ID和當前連接的主服務器的運行ID相同,那麽說明從服務器斷線之前復制的就是當前連接的這個主服務器,主服務器可以繼續嘗試執行部分重同步操作。相反地, 如果從服務器保存的運行1D和當前連接的主服務器的運行1D並不相同,那麽說明從服務器斷線之前復制的主服務器並不是當前連接的這個主服務器,主服務器將對從服務器執行完整重同步操作。

PSYNC命令的實現

PSYNC命令的調用方法有兩種:

1.如果從服務器以前沒有復制過任何主服務器, 或者之前執行過SLAVEOF no one命令, 那麽從服務器在開始一次新的復制時將向主服務器發送PSYNC ? -1命令, 主動請求主服務器進行完整重同步(因為這時不可能執行部分重同步)。

2.相反地,如果從服務器已經復制過某個主服務器,那麽從服務器在開始一次新的復制時將向主服務器發送PSYNC <runid> <offset>命令:其中runid是上一次復制的主服務器的運行ID,而offset則是從服務器當前的復制偏移量,接收到這個命令的主服務器會通過這兩個參數來判斷應該對從服務器執行哪種同步操作 。

根據情況,接收到PSYNC命令的主服務器會向從服務器返回以下三種回復的其中一種:

  1. 如果主服務器返回+FULLRESYNC <runid> <offset>回復,那麽表示主服務器將與從服務器執行完整重同步操作:其中runid是這個主服務器的運行ID, 從服務器會將這個ID保存起來,在下一次發送PSYNC命令時使用;而offse七則是主服務器當前的復制偏移量,從服務器會將這個值作為自己的初始化偏移量。
  2. 如果主服務器返回+CONTINUE 回復,那麽表示主服務器將與從服務器執行部分重同步操作,從服務器只要等著主服務器將自己缺少的那部分數據發送過來就可以了。
  3. 如果主服務器返回-ERR 回復,那麽表示主服務器的版本低千Redis2.8, 它識別不了PSYNC命令,從服務器將向主服務器發送SYNC命令,並與主服務器執行完整同步操作。

技術分享圖片

復制的實現

Step1:設置寧服務器的地址和端口

客戶端向從服務器發送SLAVEOF命令,從服務器首先要做的就是將客戶端給定的主服務器IP地址127.0.0.1 以及端口6379保存到服務器狀態的masterhost屬性和masterport屬性裏面。SLAVEOF命令是一個異步命令, 在完成masterhost屬性和masterport屬性的設

置工作之後,從服務器將向發送SLAVEOF命令的客戶端返回OK, 表示復制指令已經被接

收, 而實際的復制工作將在OK返回之後才真正開始執行。

Step2: 建立套接字連接

在SLAVEOF命令執行之後,從服務器將根據命令所設置的IP地址和端口,創建連向主服務器的套接字連接,如下圖所示。

技術分享圖片

如果從服務器創建的套接字能成功連接(connect)到主服務器,那麽

從服務器將為這個套接字關聯一個專門用於處理復制工作的文件事件處理器,這個處理器將負責執行後續的復制工作,比如接收RDB文件,以及接收主服務器傳播來的寫命令, 諸如此類。

主服務器在接受(accept)從服務器的套接字連接之後,將為該套接字創建相應的客戶端狀態,並將從服務器看作是一個連接到主服務器的客戶端來對待, 這時從服務器將同時具有服務器(server)和客戶端(client)兩個身份:從服務器可以向主服務器發送命令請求,而主服務器則會向從服務器返回命令回復。

Step3: 發送PING命令

從服務器成為主服務器的客戶端之後,做的第一件事就是向主服務器發送一個PING命令。

這個PING命令有兩個作用:

1.雖然主從服務器成功建立起了套接字連接,但雙方井未使用該套接字進行過任何通信,通過發送PING命令可以檢查套接字的讀寫狀態是否正常。

2.因為復制工作接下來的幾個步驟都必須在主服務器可以正常處理命令請求的狀態下 才能進行,通過發送PING命令可以檢查主服務器能否正常處理命令請求。

從服務器在發送PING命令之後將遇到以下三種情況的其中一種:

1.如果主服務器向從服務器返回了一個命令回復,但從服務器卻不能在規定的時限(timeout)內讀取出命令回復的內容,那麽表示主從服務器之間的網絡連接狀態不 佳,不能繼續執行復制工作的後續步驟。 當出現這種情況時,從服務器斷開並重新創建連向主服務器的套接字。

2.如果主服務器向從服務器返回一個錯誤,那麽表示主服務器暫時沒辦法處理從服務器的命令請求,不能繼續執行復制工作的後續步驟。 當出現這種情況時,從服務器斷開並重新創建連向主服務器的套接字。 比如說, 如果主服務器正在處理一個超時運行的腳本, 那麽當從服務器向主服務器發送PING命令時,從服務器將收到主服務器返回的BUSY Redisis busy running a scrip七. You can only call SCRIPT KILL or SHUTDOWN NOSAVE. 錯誤。

3.如果從服務器讀取到"PONG" 回復, 那麽表示主從服務器之間的網絡連接狀態正常, 並且主服務器可以正常處理從服務器(客戶端)發送的命令請求, 在這種情況下, 從服務器可以繼續執行復制工作的下個步驟。

Step4: 身份驗證

1.如果從服務器設置了masterauth選項,那麽進行身份驗證。

2.如果從服務器沒有設置masterauth選項,那麽不進行身份驗證。

在需要進行身份驗證的情況下,從服務器將向主服務器發送一條AUTH命令,命令的參數為從服務器masterau吐選項的值。

從服務器在身份驗證階段可能遇到的情況有以下幾種:

1.如果主服務器沒有設置requirepass選項,並且從服務器也沒有設置masterauth 選項, 那麽主服務器將繼續執行從服務器發送的命令, 復制工作可以繼續進行。

2.如果從服務器通過AUTH命令發送的密碼和主服務器requirepass選項所設置的密碼相同, 那麽主服務器將繼續執行從服務器發送的命令, 復制工作可以繼續進行。與此相反, 如果主從服務器設置的密碼不相同,那麽主服務器將返回一個 invalid password錯誤。

3.如果主服務器設置了requirepass選項,但從服務器卻沒有設置masterauth選項,name主服務器將返回一個NOAUTH錯誤。另一方面, 如果主服務器沒有設置requirepass選項, 但從服務器卻設置了masterauth選項, 那麽主服務器將返回一個no password is set錯誤。

Step5發送端口信息

在身份驗證步驟之後,從服務器將執行命令REPLCONF listening-por 七<portnumber>,

向主服務器發送從服務器的監聽端口號。

主服務器在接收到這個命令之後, 會將端口號記錄在從服務器所對應的客戶端狀態的

slave_listening_port屬性中。

Step6:同步

在這一步,從服務器將向主服務器發送PSYNC命令,執行同步操作,並將自己的數據庫更新至主服務器數據庫當前所處的狀態。

值得一提的是,在同步操作執行之前,只有從服務器是主服務器的客戶端,但是在執行同步操作之後, 主服務器也會成為從服務器的客戶端:

1.如果PSYNC命令執行的是完整重同步操作,那麽主服務器需要成為從服務器的客戶端,才能將保存在緩沖區裏面的寫命令發送給從服務器執行。

2.如果PSYNC命令執行的是部分重同步操作,那麽主服務器需要成為從服務器的客戶端,才能向從服務器發送保存在復制積壓緩沖區裏面的寫命令。

Step7:命令傳播

當完成了同步之後, 主從服務器就會進人命令傳播階段, 這時主服務器只要執行的寫命令發送給從服務器, 而從服務器只要一直接收主服務器發來的命令,就可以保證主從一致了

心跳檢測

在命令傳播階段, 從服務器默認會以每秒一次的頻率, 向主服務器發送命令: REPLCONF ACK <replication_offset>

其中 replication_offset 是從服務器當前的復制偏移量。

發送REPLCONFACK命令對於主從服務器有三個作用:

1.檢測主從服務器的網絡連接狀態。

2.輔助實現 min-slaves 選項。

3.檢測命令丟失。

Redis設計與實現讀書筆記-AOF,RDB,復制