redis 持久化與備份策略

持久化(persistence)

本文是 Redis 持久化文檔 的中文翻譯。

這篇文章提供了 Redis 持久化的技術性描述,推薦所有 Redis 用戶閱讀。

要更廣泛地瞭解 Redis 持久化,以及這種持久化所保證的耐久性(durability),請參考文章 Redis persistence demystified (中文)。

Redis 持久化

Redis 提供了多種不同級別的持久化方式:

  • RDB 持久化可以在指定的時間間隔內生成數據集的時間點快照(point-in-time snapshot)。
  • AOF 持久化記錄服務器執行的所有寫操作命令,並在服務器啓動時,通過重新執行這些命令來還原數據集。AOF 文件中的命令全部以 Redis 協議的格式來保存,新命令會被追加到文件的末尾。Redis 還可以在後臺對 AOF 文件進行重寫(rewrite),使得 AOF 文件的體積不會超出保存數據集狀態所需的實際大小。
  • Redis 還可以同時使用 AOF 持久化和 RDB 持久化。在這種情況下,當 Redis 重啓時,它會優先使用 AOF 文件來還原數據集,因爲 AOF 文件保存的數據集通常比 RDB 文件所保存的數據集更完整。
  • 你甚至可以關閉持久化功能,讓數據只在服務器運行時存在。

瞭解 RDB 持久化和 AOF 持久化之間的異同是非常重要的,以下幾個小節將詳細地介紹這這兩種持久化功能,並對它們的相同和不同之處進行說明。

RDB 的優點

  • RDB 是一個非常緊湊(compact)的文件,它保存了 Redis 在某個時間點上的數據集。這種文件非常適合用於進行備份:比如說,你可以在最近的 24 小時內,每小時備份一次 RDB 文件,並且在每個月的每一天,也備份一個 RDB 文件。這樣的話,即使遇上問題,也可以隨時將數據集還原到不同的版本。
  • RDB 非常適用於災難恢復(disaster recovery):它只有一個文件,並且內容都非常緊湊,可以(在加密後)將它傳送到別的數據中心,或者亞馬遜 S3 中。
  • RDB 可以最大化 Redis 的性能:父進程在保存 RDB 文件時唯一要做的就是 fork 出一個子進程,然後這個子進程就會處理接下來的所有保存工作,父進程無須執行任何磁盤 I/O 操作。
  • RDB 在恢復大數據集時的速度比 AOF 的恢復速度要快。

RDB 的缺點

  • 如果你需要儘量避免在服務器故障時丟失數據,那麼 RDB 不適合你。雖然 Redis 允許你設置不同的保存點(save point)來控制保存 RDB 文件的頻率,但是,因爲RDB 文件需要保存整個數據集的狀態,所以它並不是一個輕鬆的操作。因此你可能會至少 5 分鐘才保存一次 RDB 文件。在這種情況下,一旦發生故障停機,你就可能會丟失好幾分鐘的數據。
  • 每次保存 RDB 的時候,Redis 都要 fork() 出一個子進程,並由子進程來進行實際的持久化工作。在數據集比較龐大時, fork() 可能會非常耗時,造成服務器在某某毫秒內停止處理客戶端;如果數據集非常巨大,並且 CPU 時間非常緊張的話,那麼這種停止時間甚至可能會長達整整一秒。雖然 AOF 重寫也需要進行 fork() ,但無論 AOF 重寫的執行間隔有多長,數據的耐久性都不會有任何損失。

AOF 的優點

  • 使用 AOF 持久化會讓 Redis 變得非常耐久(much more durable):你可以設置不同的 fsync 策略,比如無 fsync ,每秒鐘一次 fsync ,或者每次執行寫入命令時 fsync 。AOF 的默認策略爲每秒鐘 fsync 一次,在這種配置下,Redis 仍然可以保持良好的性能,並且就算髮生故障停機,也最多隻會丟失一秒鐘的數據( fsync 會在後臺線程執行,所以主線程可以繼續努力地處理命令請求)。
  • AOF 文件是一個只進行追加操作的日誌文件(append only log),因此對 AOF 文件的寫入不需要進行seek ,即使日誌因爲某些原因而包含了未寫入完整的命令(比如寫入時磁盤已滿,寫入中途停機,等等),redis-check-aof 工具也可以輕易地修復這種問題。
  • Redis 可以在 AOF 文件體積變得過大時,自動地在後臺對 AOF 進行重寫:重寫後的新 AOF 文件包含了恢復當前數據集所需的最小命令集合。整個重寫操作是絕對安全的,因爲 Redis 在創建新 AOF 文件的過程中,會繼續將命令追加到現有的 AOF 文件裏面,即使重寫過程中發生停機,現有的 AOF 文件也不會丟失。而一旦新 AOF 文件創建完畢,Redis 就會從舊 AOF 文件切換到新 AOF 文件,並開始對新 AOF 文件進行追加操作。
  • AOF 文件有序地保存了對數據庫執行的所有寫入操作,這些寫入操作以 Redis 協議的格式保存,因此 AOF 文件的內容非常容易被人讀懂,對文件進行分析(parse)也很輕鬆。導出(export) AOF 文件也非常簡單:舉個例子,如果你不小心執行了 FLUSHALL 命令,但只要 AOF 文件未被重寫,那麼只要停止服務器,移除 AOF 文件末尾的 FLUSHALL 命令,並重啓 Redis ,就可以將數據集恢復到 FLUSHALL 執行之前的狀態。

AOF 的缺點

  • 對於相同的數據集來說,AOF 文件的體積通常要大於 RDB 文件的體積。
  • 根據所使用的 fsync 策略,AOF 的速度可能會慢於 RDB 。在一般情況下,每秒 fsync 的性能依然非常高,而關閉 fsync 可以讓 AOF 的速度和 RDB 一樣快,即使在高負荷之下也是如此。不過在處理巨大的寫入載入時,RDB 可以提供更有保證的最大延遲時間(latency)。
  • AOF 在過去曾經發生過這樣的 bug :因爲個別命令的原因,導致 AOF 文件在重新載入時,無法將數據集恢復成保存時的原樣。(舉個例子,阻塞命令 BRPOPLPUSH 就曾經引起過這樣的 bug 。)測試套件裏爲這種情況添加了測試:它們會自動生成隨機的、複雜的數據集,並通過重新載入這些數據來確保一切正常。雖然這種 bug 在 AOF 文件中並不常見,但是對比來說,RDB 幾乎是不可能出現這種 bug 的。

RDB 和 AOF ,我應該用哪一個?

一般來說,如果想達到足以媲美 PostgreSQL 的數據安全性,你應該同時使用兩種持久化功能。

如果你非常關心你的數據,但仍然可以承受數分鐘以內的數據丟失,那麼你可以只使用 RDB 持久化。

有很多用戶都只使用 AOF 持久化,但我們並不推薦這種方式:因爲定時生成 RDB 快照(snapshot)非常便於進行數據庫備份,並且 RDB 恢復數據集的速度也要比 AOF 恢復的速度要快,除此之外,使用 RDB 還可以避免之前提到的 AOF 程序的 bug 。

因爲以上提到的種種原因,未來我們可能會將 AOF 和 RDB 整合成單個持久化模型。(這是一個長期計劃。)

接下來的幾個小節將介紹 RDB 和 AOF 的更多細節。

RDB 快照

在默認情況下,Redis 將數據庫快照保存在名字爲 dump.rdb 的二進制文件中。

你可以對 Redis 進行設置,讓它在“ N 秒內數據集至少有 M 個改動”這一條件被滿足時,自動保存一次數據集。

你也可以通過調用 SAVE 或者 BGSAVE ,手動讓 Redis 進行數據集保存操作。

比如說,以下設置會讓 Redis 在滿足“ 60 秒內有至少有 1000 個鍵被改動”這一條件時,自動保存一次數據集:

save 60 1000

這種持久化方式被稱爲快照(snapshot)。

快照的運作方式

當 Redis 需要保存 dump.rdb 文件時,服務器執行以下操作:

  1. Redis 調用 fork() ,同時擁有父進程和子進程。
  2. 子進程將數據集寫入到一個臨時 RDB 文件中。
  3. 當子進程完成對新 RDB 文件的寫入時,Redis 用新 RDB 文件替換原來的 RDB 文件,並刪除舊的 RDB 文件。

這種工作方式使得 Redis 可以從寫時複製(copy-on-write)機制中獲益。

只進行追加操作的文件(append-only file,AOF)

快照功能並不是非常耐久(durable):如果 Redis 因爲某些原因而造成故障停機,那麼服務器將丟失最近寫入、且仍未保存到快照中的那些數據。

儘管對於某些程序來說,數據的耐久性並不是最重要的考慮因素,但是對於那些追求完全耐久能力(full durability)的程序來說,快照功能就不太適用了。

從 1.1 版本開始,Redis 增加了一種完全耐久的持久化方式:AOF 持久化。

你可以通過修改配置文件來打開 AOF 功能:

appendonly yes

從現在開始,每當 Redis 執行一個改變數據集的命令時(比如 SET),這個命令就會被追加到 AOF 文件的末尾。

這樣的話,當 Redis 重新啓時,程序就可以通過重新執行 AOF 文件中的命令來達到重建數據集的目的。

AOF 重寫

因爲 AOF 的運作方式是不斷地將命令追加到文件的末尾,所以隨着寫入命令的不斷增加,AOF 文件的體積也會變得越來越大。

舉個例子,如果你對一個計數器調用了 100 次 INCR ,那麼僅僅是爲了保存這個計數器的當前值,AOF 文件就需要使用 100 條記錄(entry)。

然而在實際上,只使用一條 SET 命令已經足以保存計數器的當前值了,其餘 99 條記錄實際上都是多餘的。

爲了處理這種情況,Redis 支持一種有趣的特性:可以在不打斷服務客戶端的情況下,對 AOF 文件進行重建(rebuild)。

執行 BGREWRITEAOF 命令,Redis 將生成一個新的 AOF 文件,這個文件包含重建當前數據集所需的最少命令。

Redis 2.2 需要自己手動執行 BGREWRITEAOF 命令;Redis 2.4 則可以自動觸發 AOF 重寫,具體信息請查看 2.4 的示例配置文件。

AOF 有多耐久?

你可以配置 Redis 多久纔將數據 fsync 到磁盤一次。

有三個選項:

  • 每次有新命令追加到 AOF 文件時就執行一次 fsync :非常慢,也非常安全。
  • 每秒 fsync 一次:足夠快(和使用 RDB 持久化差不多),並且在故障時只會丟失 1 秒鐘的數據。
  • 從不 fsync :將數據交給操作系統來處理。更快,也更不安全的選擇。

推薦(並且也是默認)的措施爲每秒 fsync 一次,這種 fsync 策略可以兼顧速度和安全性。

總是 fsync 的策略在實際使用中非常慢,即使在 Redis 2.0 對相關的程序進行了改進之後仍是如此 ——頻繁調用fsync 註定了這種策略不可能快得起來。

如果 AOF 文件出錯了,怎麼辦?

服務器可能在程序正在對 AOF 文件進行寫入時停機,如果停機造成了 AOF 文件出錯(corrupt),那麼 Redis 在重啓時會拒絕載入這個 AOF 文件,從而確保數據的一致性不會被破壞。

當發生這種情況時,可以用以下方法來修復出錯的 AOF 文件:

  1. 爲現有的 AOF 文件創建一個備份。
  2. 使用 Redis 附帶的 redis-check-aof 程序,對原來的 AOF 文件進行修復。
$ redis-check-aof --fix
  1. (可選)使用 diff -u 對比修復後的 AOF 文件和原始 AOF 文件的備份,查看兩個文件之間的不同之處。
  2. 重啓 Redis 服務器,等待服務器載入修復後的 AOF 文件,並進行數據恢復。

AOF 的運作方式

AOF 重寫和 RDB 創建快照一樣,都巧妙地利用了寫時複製機制。

以下是 AOF 重寫的執行步驟:

  1. Redis 執行 fork() ,現在同時擁有父進程和子進程。
  2. 子進程開始將新 AOF 文件的內容寫入到臨時文件。
  3. 對於所有新執行的寫入命令,父進程一邊將它們累積到一個內存緩存中,一邊將這些改動追加到現有 AOF 文件的末尾:這樣即使在重寫的中途發生停機,現有的 AOF 文件也還是安全的。
  4. 當子進程完成重寫工作時,它給父進程發送一個信號,父進程在接收到信號之後,將內存緩存中的所有數據追加到新 AOF 文件的末尾。
  5. 搞定!現在 Redis 原子地用新文件替換舊文件,之後所有命令都會直接追加到新 AOF 文件的末尾。

怎麼從 RDB 持久化切換到 AOF 持久化

在 Redis 2.2 或以上版本,可以在不重啓的情況下,從 RDB 切換到 AOF :

  1. 爲最新的 dump.rdb 文件創建一個備份。
  2. 將備份放到一個安全的地方。
  3. 執行以下兩條命令:
redis-cli> CONFIG SET appendonly yes

redis-cli> CONFIG SET save ""
  1. 確保命令執行之後,數據庫的鍵的數量沒有改變。
  2. 確保寫命令會被正確地追加到 AOF 文件的末尾。

步驟 3 執行的第一條命令開啓了 AOF 功能:Redis 會阻塞直到初始 AOF 文件創建完成爲止,之後 Redis 會繼續處理命令請求,並開始將寫入命令追加到 AOF 文件末尾。

步驟 3 執行的第二條命令用於關閉 RDB 功能。這一步是可選的,如果你願意的話,也可以同時使用 RDB 和 AOF 這兩種持久化功能。

別忘了在 redis.conf 中打開 AOF 功能!否則的話,服務器重啓之後,之前通過 CONFIG SET 設置的配置就會被遺忘,程序會按原來的配置來啓動服務器。

譯註:原文這裏還有介紹 2.0 版本的切換方式,考慮到 2.0 已經很老舊了,這裏省略了對那部分文檔的翻譯,有需要的請參考原文。

RDB 和 AOF 之間的相互作用

在版本號大於等於 2.4 的 Redis 中,BGSAVE 執行的過程中,不可以執行 BGREWRITEAOF 。反過來說,在BGREWRITEAOF 執行的過程中,也不可以執行 BGSAVE 。

這可以防止兩個 Redis 後臺進程同時對磁盤進行大量的 I/O 操作。

如果 BGSAVE 正在執行,並且用戶顯示地調用 BGREWRITEAOF 命令,那麼服務器將向用戶回覆一個 OK 狀態,並告知用戶,BGREWRITEAOF 已經被預定執行:一旦 BGSAVE 執行完畢,BGREWRITEAOF 就會正式開始。

當 Redis 啓動時,如果 RDB 持久化和 AOF 持久化都被打開了,那麼程序會優先使用 AOF 文件來恢復數據集,因爲 AOF 文件所保存的數據通常是最完整的。

備份 Redis 數據

在閱讀這個小節前,先將下面這句話銘記於心:一定要備份你的數據庫!

磁盤故障,節點失效,諸如此類的問題都可能讓你的數據消失不見,不進行備份是非常危險的。

Redis 對於數據備份是非常友好的,因爲你可以在服務器運行的時候對 RDB 文件進行復制:RDB 文件一旦被創建,就不會進行任何修改。當服務器要創建一個新的 RDB 文件時,它先將文件的內容保存在一個臨時文件裏面,當臨時文件寫入完畢時,程序才使用 rename(2) 原子地用臨時文件替換原來的 RDB 文件。

這也就是說,無論何時,複製 RDB 文件都是絕對安全的。

以下是我們的建議:

  • 創建一個定期任務(cron job),每小時將一個 RDB 文件備份到一個文件夾,並且每天將一個 RDB 文件備份到另一個文件夾。
  • 確保快照的備份都帶有相應的日期和時間信息,每次執行定期任務腳本時,使用 find 命令來刪除過期的快照:比如說,你可以保留最近 48 小時內的每小時快照,還可以保留最近一兩個月的每日快照。
  • 至少每天一次,將 RDB 備份到你的數據中心之外,或者至少是備份到你運行 Redis 服務器的物理機器之外。

容災備份

Redis 的容災備份基本上就是對數據進行備份,並將這些備份傳送到多個不同的外部數據中心。

容災備份可以在 Redis 運行併產生快照的主數據中心發生嚴重的問題時,仍然讓數據處於安全狀態。

因爲很多 Redis 用戶都是創業者,他們沒有大把大把的錢可以浪費,所以下面介紹的都是一些實用又便宜的容債備份方法:

  • Amazon S3 ,以及其他類似 S3 的服務,是一個構建災難備份系統的好地方。最簡單的方法就是將你的每小時或者每日 RDB 備份加密並傳送到 S3 。對數據的加密可以通過 gpg -c 命令來完成(對稱加密模式)。記得把你的密碼放到幾個不同的、安全的地方去(比如你可以把密碼複製給你組織裏最重要的人物)。同時使用多個儲存服務來保存數據文件,可以提升數據的安全性。
  • 傳送快照可以使用 SCP 來完成(SSH 的組件)。以下是簡單並且安全的傳送方法:買一個離你的數據中心非常遠的 VPS ,裝上 SSH ,創建一個無口令的 SSH 客戶端 key ,並將這個 key 添加到 VPS 的 authorized_keys 文件中,這樣就可以向這個 VPS 傳送快照備份文件了。爲了達到最好的數據安全性,至少要從兩個不同的提供商那裏各購買一個 VPS 來進行數據容災備份。

需要注意的是,這類容災系統如果沒有小心地進行處理的話,是很容易失效的。

最低限度下,你應該在文件傳送完畢之後,檢查所傳送備份文件的體積和原始快照文件的體積是否相同。如果你使用的是 VPS ,那麼還可以通過比對文件的 SHA1 校驗和來確認文件是否傳送完整。

另外,你還需要一個獨立的警報系統,讓它在負責傳送備份文件的傳送器(transfer)失靈時通知你。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章