Redis如何保證系統宕機資料不會丟失

2021-09-24 16:00:32 字數 3888 閱讀 3386

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

如圖1.1所示,redis 的持久化機制有兩種,第一種是快照,第二種是 aof 日誌。快照是一次全量備份,aof 日誌是連續的增量備份。快照是記憶體資料的二進位制序列化形式,在儲存上非常緊湊,而 aof 日誌記錄的是記憶體資料修改的指令記錄文字。aof 日誌在長期的執行過程中會變得無比龐大,資料庫重啟時需要載入 aof 日誌進行指令重放,這個時間就會無比漫長,所以需要定期進行 aof 重寫,給 aof 日誌進行**。

快照原理

我們知道 redis 是單執行緒程式,這個執行緒要同時負責多個客戶端套接字的併發讀寫操作和記憶體資料結構的邏輯讀寫。

在服務線上請求的同時,redis 還需要進行記憶體快照,記憶體快照要求 redis 必須進行檔案 io 操作,可檔案 io 操作是不能使用多路復用 api。

這意味著單執行緒在服務線上請求的同時,還要進行檔案 io 操作,而檔案 io 操作會嚴重拖累伺服器請求的效能。

還有個重要的問題,為了不阻塞線上的業務,redis 就需要一邊持久化,一邊響應客戶端的請求。持久化的同時,記憶體資料結構還在改變,比如乙個大型的 hash 字典正在持久化,結果乙個請求過來把它給刪掉了,可是還沒持久化完呢,這該怎麼辦呢?

redis 使用作業系統的多程序 cow(copy on write)機制來實現快照持久化,這個機制很有意思,也很少人知道。多程序 cow 也是鑑定程式設計師知識廣度的乙個重要指標。

fork(多程序)

redis 在持久化時會呼叫 glibc 的函式 fork 產生乙個子程序,快照持久化完全交給子程序來處理,父程序繼續處理客戶端請求。子程序剛剛產生時,它和父程序共享記憶體裡面的**段和資料段。這時你可以把父子程序想象成乙個連體嬰兒,它們在共享身體。這是 linux 作業系統的機制,為了節約記憶體資源,所以盡可能讓它們共享起來。在程序分離的一瞬間,記憶體的增長幾乎沒有明顯變化。

用 python 語言描述程序分離的邏輯如下。fork 函式會在父子程序同時返回,在父程序裡返回子程序的 pid,在子程序裡返回零。如果作業系統的記憶體資源不足,pid 就會是負數,表示 fork 失敗。

pid = os.fork()

if pid > 0:

handle_client_requests() # 父程序繼續處理客戶端請求

if pid == 0:

handle_snapshot_write() # 子程序處理快照寫磁碟

if pid < 0:

# fork error

子程序做資料持久化,不會修改現有的記憶體資料結構,它只是對資料結構進行遍歷讀取,然後序列化寫到磁碟中。但是父程序不一樣,它必須持續服務客戶端請求,然後對記憶體資料結構進行不間斷的修改。

這個時候就會使用作業系統的 cow 機制來進行資料段頁面的分離。如果 1.2 所示,資料段是由很多作業系統的頁面組合而成,當父程序對其中乙個頁面的資料進行修改時,會將被共享的頁面複製乙份分離出來,然後對這個複製的頁面進行修改。這時子程序相應的頁面是沒有變化的,還是程序產生時那一瞬間的資料。

隨著父程序修改操作的持續進行,越來越多的共享頁面被分離出來,記憶體就會持續增長。但是也不會超過原有資料記憶體的 2 倍大小。另外乙個 redis 例項裡冷資料佔的比例往往是比較高的,所以很少會出現所有的頁面都會被分離,被分離的往往只有其中一部分頁面。每個頁面的大小只有 4kb,乙個 redis 例項裡面一般都會有成千上萬個頁面。

子程序因為資料沒有變化,它能看到的記憶體裡的資料在程序產生的一瞬間就凝固了,再也不會改變,這也是為什麼 redis 的持久化叫「快照」的原因。接下來子程序就可以非常安心地遍歷資料,進行序列化寫磁碟了。

aof 原理

aof 日誌儲存的是 redis 伺服器的順序指令序列,aof 日誌只記錄對記憶體進行修改的指令記錄。

假設 aof 日誌記錄了自 redis 例項建立以來所有的修改性指令序列,那麼就可以通過對乙個空的 redis 例項順序執行所有的指令——也就是「重放」,來恢復 redis 當前例項的記憶體資料結構的狀態。

redis 會在收到客戶端修改指令後,進行引數校驗、邏輯處理,如果沒問題,就立即將該指令文字儲存到 aof 日誌中,也就是說,先執行指令才將日誌存檔。這點不同於 leveldb、hbase 等儲存引擎,它們都是先儲存日誌再做邏輯處理。

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

1.1 aof 重寫

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

1.2fsync

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

這就意味著如果機器突然宕機,aof 日誌內容可能還沒有來得及完全刷到磁碟中,這個時候就會出現日誌丟失。那該怎麼辦?

linux 的 glibc 提供了 fsync(int fd) 函式可以將指定檔案的內容強制從核心快取刷到磁碟。只要 redis 程序實時呼叫 fsync 函式就可以保證 aof 日誌不丟失。但是 fsync 是乙個磁碟 io 操作,它很慢!如果 redis 執行一條指令就要 fsync 一次,那麼 redis 高效能的地位就不保了。

所以在生產環境的伺服器中,redis 通常是每隔 1s 左右執行一次 fsync 操作,這個 1s 的週期是可以配置的。這是在資料安全性和效能之間做的乙個折中,在保持高效能的同時,盡可能使得資料少丟失。

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

1.3 運維

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

1.遍歷整個記憶體,大塊寫磁碟會加重系統負載。

2.aof 的 fsync 是乙個耗時的 io 操作,它會降低 redis 效能,同時也會增加系統 io 負擔。

所以通常 redis 的主節點是不會進行持久化操作,持久化操作主要在從節點進行。從節點是備份節點,沒有來自客戶端請求的壓力,它的作業系統資源往往比較充沛。

但是如果出現網路分割槽,從節點長期連不上主節點,就會出現資料不一致的問題,特別是在網路分割槽出現的情況下,主節點一旦不小心宕機了,那麼資料就會丟失,所以在生產環境要做好實時監控工作,保證網路暢通或者能快速修復。另外還應該再增加乙個從節點以降低網路分割槽的概率,只要有乙個從節點資料同步正常,資料也就不會輕易丟失。

1.4redis 4.0 混合持久化

重啟 redis 時,我們很少使用 rdb 來恢復記憶體狀態,因為會丟失大量資料。我們通常使用 aof 日誌重放,但是重放 aof 日誌的效能相對 rdb 來說要慢很多,這樣在 redis 例項很大的情況下,啟動需要花費很長的時間。

redis 4.0 為了解決這個問題,帶來了乙個新的持久化選項——混合持久化。如圖 1-3 所示,將 rdb 檔案的內容和增量的 aof 日誌檔案存在一起。這裡的 aof 日誌不再是全量的日誌,而是自持久化開始到持久化結束的這段時間發生的增量 aof 日誌,通常這部分 aof 日誌很小。

於是在 redis 重啟的時候,可以先載入 rdb 的內容,然後再重放增量 aof 日誌就可以完全替代之前的 aof 全量檔案重放,重啟效率因此得到大幅提公升。

突發宕機,Kafka寫入的資料如何保證不丟失?

我們暫且不考慮寫磁碟的具體過程,先大致看看下面的圖,這代表了 kafka 的核心架構原理。kafka 分布式儲存架構 那麼現在問題來了,如果每天產生幾十 tb 的資料,難道都寫一台機器的磁碟上嗎?這明顯是不靠譜的啊!所以說,這裡就得考慮資料的分布式儲存了,我們結合 kafka 的具體情況來說說。在 ...

redis如何保證資料都是熱點資料

背景 眾所周知,redis是純記憶體的操作。所以速度極快。然而記憶體的大小是有限的。如 mysql中有2000w的資料,redis中只存20w的資料,那麼如何保證redis中的資料都是熱點資料呢?答案 redis記憶體資料集達到一定大小的時候,就會實行資料淘汰策略,記憶體的淘汰機制的初衷是為了更好地...

Redis如何避免宕機時資料丟失

redis 用於避免資料丟失的 aof 方法。這個方法通過逐一記錄操作命令,在恢復時再逐一執行命令的方式,保證了資料的可靠性。這個方法看似 簡單 但也是充分考慮了對 redis 效能的影響。總結來說,它提供了 aof 日誌的三種寫回策略,分別是 always everysec 和 no,這三種策略在...