[Redis]持久化

Duancf發表於2024-07-06

持久化

Redis的資料全部在記憶體裡,如果突然當機,資料就會全部丟失,因此必須有一種機制來保證Redis的資料不會因為故障而丟失,這種機制就是Redis的持久化機制。

Redis的持久化機制有兩種,第一種是快照,第二種是AOF日誌

快照是一次全量備份,AOF日誌是連續的增量備份。
快照是記憶體資料的二進位制序列化形式,在儲存上非常緊湊,而AOF日誌記錄的是記憶體資料修改的指令記錄文字。

RDB持久化透過建立記憶體中所有資料的快照並將其儲存到磁碟上來實現。具體工作原理如下:

觸發條件:
RDB持久化可以透過配置來自動觸發,比如在一定時間內有一定數量的寫操作時。
也可以手動觸發,如透過執行 SAVE 或 BGSAVE 命令。

建立快照:
當觸發RDB持久化時,Redis會生成一個資料快照檔案。
如果是執行 SAVE 命令,Redis會阻塞當前所有客戶端連線,直到快照生成完畢。這種方式通常不推薦在生產環境中使用,因為它會導致服務中斷。
如果是執行 BGSAVE 命令,Redis會建立一個子程序,由子程序負責生成快照檔案,這樣可以避免阻塞主程序,保證服務的持續執行。

儲存到磁碟:
子程序將資料快照寫入到一個臨時檔案中。只有當快照完成並且資料完全寫入後,臨時檔案才會替換舊的快照檔案。
這種方式確保了快照檔案的一致性,即使生成快照時發生崩潰,也不會損壞已有的快照檔案。

快照原理

我們知道Redis是單執行緒程式,這個執行緒要同時負責多個客戶端套接字的併發讀寫操作和記憶體資料結構的邏輯讀寫。
在服務線上請求的同時,Redis還需要進行記憶體快照,記憶體快照要求Redis必須進行檔案IO操作,可檔案IO操作不能使用多路複用API這意昧著單執行緒在服務線上請求的同時,還要進行檔案IO操作,而檔案IO作會嚴重拖累伺服器的效能。還有個重要的問題,為了不阻塞線上的業務,Redis就需要一邊持久化,一邊響應客戶端的請求。持久化的同時,記憶體資料結構還在改變,比如一個大型的hash典正在持久化,結果一個請求過來把它給刪掉了,可是還沒持久化完呢,這該怎麼
辦呢?Redis使用作業系統的多程序COW(Copy On Write)機制來實現快照持久化,這個機制很有意思,也很少人知道。多程序cow也是鑑定程式設計師知識廣度的一個重要指標。

fork(多程序)

Redis在持久化時會呼叫glibc的函式fork產生一個子程序,快照持久化完全交給子程序來處理,父程序繼續處理客戶端請求。子程序剛剛產生時,它和父程序共享記憶體裡面的程式碼段和資料段。這時你可以把父子程序想象成一個連體嬰兒,它們在共享身體。這是Linux作業系統的機制,為了節約記憶體資源,所以儘可能讓它們共享起來。在程序分離的瞬間,記憶體的增長几乎沒有明顯變化。
fork函式會在父子程序同時返回,在父程序裡返回子程序的pid,在子程序裡返回零。如果作業系統的記憶體資源不足pid就會是負數,表示fork失敗。
子程序做資料持久化,不會修改現有的記憶體資料結構,它只是對資料結構進行遍歷讀取,然後序列化寫到碰盤中。但是父程序不一樣,它必須持續服務客戶端請求,然後對記憶體資料結構進行不間斷的修改。
image

這個時候就會使用作業系統的cow機制來進行資料段頁面的分離。資料段是由很多作業系統的頁面組合而成,當父程序對其中一個頁面的資料進行修改時,會將被共享的頁面複製一份分離出來,然後對這個複製的頁面進行修改。這時子程序相應的頁面是沒有變化的,還是程序產生時那一瞬間的資料。
隨著父程序修改操作的持續進行,越來越多的共享頁面被分離出來,記憶體就會持續增長,但是也不會超過原有資料記憶體的2倍大小。另外,Redis例項裡冷資料佔的比例往往是比較高的,所以很少會出現所有的頁面都被分離的情況,被分離的往往只有其中一部分頁面。每個頁面的大小隻有4KB,一個Redis例項裡面一般都會有成千上萬個頁面子程序因為資料沒有變化,它能看到的記憶體裡的資料在程序產生的瞬間就凝固了,再也不會改變,這也是為什Redis的持久化叫"快照"的原因。接下來子程序就可以非常安心地遍歷資料,進行序列化寫磁碟了。

AOF原理

AOF日誌儲存的是Redis伺服器的順序指令序列,AOF日誌只記錄對記憶體進行修改的指令記錄。
假設AOF日誌記錄了自Redis例項建立以來所有的修改性指令序列,那可以透過對一個空的Redis例項順序執行所有的指令一一也就是重放,來恢復Redis當前例項的記憶體資料結構的狀態。Redis會在收到客戶端修改指令後,進行引數校驗、邏輯處理,如果沒問題,就將該指令文字儲存到AOF日誌中,也就是說,先執指令才將日誌存檔,這點不同於leveldb,hbase等儲存引擎,它們都是先儲存日誌做邏輯處理。

Redis在長期執行的過程中,AOF的日誌會越來越長,如果例項當機重啟,重放整個AOF日誌會非常耗時,導致Redis長時間無法對外提供服務,所以需要對AOF日誌瘦身。

AOF重寫

Redis提供了bgrewriteaof指令用於對AOF日誌進行瘦身,其原理就是開闢一個子程序對記憶體進行遍歷,轉換成一系列Redis的操作指令,序列化到一個新的AOF日誌檔案中。序列化完畢後再將操作期間發生的增量AOF日誌追加到這個新的AOF日誌檔案中,追加完畢後就立即替代舊的AOF日誌檔案了,瘦身工作就完成了。

fsync

AOF日誌是以檔案的形式存在的,當程式對AOF日誌檔案進行寫操作時,實際上是將內容寫到了核心為檔案描述符分配的一個記憶體快取中,然後核心會非同步將髒資料刷回到磁碟的。

這就意昧著如果機器突然當機,AOF日誌內容可能還沒有來得及完全刷到磁碟中,這個時候就會出現日誌丟失。那該怎麼辦?
Linuxglibc提供了fsync(intfd)函式可以將指定檔案的內容強制從核心快取刷到磁碟。只要Redis程序實時呼叫fsync函式就可以保證AOF日誌不丟失。但是fsync是一個磁碟IO操作,它很慢!如果Redis執行一條指令就要fsync一次,那麼Redis高效能的地位就不保了,所以在生產環境的伺服器中,Redis通常是每隔ls左右執行一次fsync操作,這ls的週期是可以配置的。這是在資料安全性和效能之間做的一個折中,在保持高效能的同時,儘可能使資料少丟失。

Redis同樣也提供了另外兩種策略,一個是永不呼叫fsync一一讓作業系統來決定何時同步碰盤,這樣做很不安全,
另一個是來一個指令就呼叫fsync一次一一結果導致非常慢。這兩種策略在生產環境中基本不會使用,瞭解一下即可。

運維

快照是透過開啟子程序的方式進行的,它是一個比較耗資源的操作。

  1. 遍歷整個記憶體,大塊寫磁碟會加重系統負載。
  2. AOF fsync是一個耗時的IO操作,它會降低Redis效能,同時也會增加系IO負擔。

所以通常Redis的主節點不會進行持久化操作,持久化操作主要在從節點進行。從節點是備份節點,沒有來自客戶端請求的壓力,它的作業系統資源往往比較充沛。但是如果出現網路分割槽,從節點長期連不上主節點,就會出現資料不一致的問題,特別是在網路分割槽出現的情況下,主節點一旦不小心看機了,那麼資料就會丟失,所以在生產環境下要做好實時監控工作,保證網路暢通或者能快速修復。另外還應該再增加一個從節點以降低網路分割槽的機率,只要有一個從節點資料同步正常,資料也就不會輕易丟失。

Redis4.0混合持久化|

重啟Redis時,我們很少使用rdb來恢復記憶體狀態,因為會丟失大量資料。我們通常使用AOF日誌重放,但是重放AOF日誌相對於使用rdb來說要慢很多,這樣在Redis例項很大的時候,啟動需要花費很長的時間。
Redis4.0為了解決這個問題,帶來了一個新的持久化選項一一混合持久化。如所示,將rdb檔案的內容和增量的AOF日誌檔案存在一起。這裡的AOF日誌不再是全量的曰志,而是自持久化開始到持久化結束的這段時間發生的增量AOF志,通常這部分AOF日誌很小。
image

相關文章