深入剖析Redis高可用系列:持久化 AOF和RDB

歡迎關注公眾號:「碼農富哥」,致力于分享后端技術 (高并發架構,分布式集群系統,消息隊列中間件,網絡,微服務,Linux, TCP/IP, HTTP, MySQL, Redis), Python 等 原創干貨 和 面試指南!

免費視頻福利推薦:

2T學習視頻教程+電子書 免費送:BAT面試精講視頻,億級流量秒殺系統,分布式系統架構,中間件消息隊列,Python Go入門到精通,Java實戰項目,Linux, 網絡,MySQL高性能,Redis集群架構,大數據,架構師速成,微服務,容器化Docker K8s, ELK Stack日志系統等免費視頻教程!

Redis高可用概述

在介紹Redis高可用之前,先說明一下在Redis的語境中高可用的含義。

我們知道,在web服務器中,高可用是指服務器可以正常訪問的時間,衡量的標準是在多長時間內可以提供正常服務(99.9%、99.99%、99.999% 等等)。但是在Redis語境中,高可用的含義似乎要寬泛一些,除了保證提供正常服務(如主從分離、快速容災技術),還需要考慮數據容量的擴展、數據安全不會丟失等。

在Redis中,實現高可用的技術主要包括持久化、復制、哨兵和集群,下面分別說明它們的作用,以及解決了什么樣的問題。

  • 持久化:持久化是最簡單的高可用方法(有時甚至不被歸為高可用的手段),主要作用是數據備份,即將數據存儲在硬盤,保證數據不會因進程退出而丟失。
  • 復制:復制是高可用Redis的基礎,哨兵和集群都是在復制基礎上實現高可用的。復制主要實現了數據的多機備份,以及對于讀操作的負載均衡和簡單的故障恢復。缺陷:故障恢復無法自動化;寫操作無法負載均衡;存儲能力受到單機的限制。
  • 哨兵:在復制的基礎上,哨兵實現了自動化的故障恢復。缺陷:寫操作無法負載均衡;存儲能力受到單機的限制。
  • 集群:通過集群,Redis解決了寫操作無法負載均衡,以及存儲能力受到單機限制的問題,實現了較為完善的高可用方案。

Redis持久化概述

Redis 的數據全部在內存里,如果突然宕機,數據就會全部丟失,因此必須有一種機制來保證 Redis 的數據不會因為故障而丟失,這種機制就是 Redis 的持久化機制。

Redis為持久化提供了兩種方式:

  • RDB:在指定的時間間隔能對你的數據進行快照存儲。
  • AOF:記錄每次對服務器寫的操作,當服務器重啟的時候會重新執行這些命令來恢復原始的數據。

由于AOF持久化的實時性更好,即當進程意外退出時丟失的數據更少,因此AOF是目前主流的持久化方式,不過RDB持久化仍然有其用武之地。

下面依次介紹RDB持久化和AOF持久化;

RDB持久化

RDB是默認的持久化方式,按照一定的策略周期性的將內存中的數據生成快照保存到磁盤。

每次快照持久化都是將內存數據完整寫入到磁盤一次,并不是增量的只同步臟數據。如果數據量大的話,而且寫操作比較多,必然會引起大量的磁盤io操作,可能會嚴重影響性能。

1. 工作原理:

  • Redis調用fork(),產生一個子進程。
  • 子進程把數據寫到一個臨時的RDB文件。
  • 當子進程寫完新的RDB文件后,把舊的RDB文件替換掉。

2. 觸發機制

RDB觸發持久化分為手動觸發和自動觸發

  1. save 命令(手動觸發)

當客戶端向Redis server發送save命令請求進行持久化時,由于Redis是用一個主線程來處理所有,save命令會阻塞Redis server處理其他客戶端的請求,直到數據同步完成。save命令會阻塞Redis服務器進程,直到RDB文件創建完畢為止,在Redis服務器阻塞期間,服務器不能處理任何命令請求,因此線上環境不推薦使用

  1. bgsave命令(手動觸發)

與save命令不同,bgsave是異步執行的,當執行bgsave命令之后,Redis主進程會fork 一個子進程將數據保存到rdb文件中,同步完數據之后,對原有文件進行替換,然后通知主進程表示同步完成。

  1. 自動觸發

除了手動觸發RDB持久化,Redis內部還存在自動觸發機制,

在配置中集中配置 save m n 的方式,表示 m秒內數據集存在n次修改時,系統自動觸發bgsave 操作。

3. RDB自動持久化配置

# 時間策略
save 900 1
save 300 10
save 60 10000

# 文件名稱
dbfilename dump.rdb

# 文件保存路徑
dir /etc/redis/data/

# 如果持久化出錯,主進程是否停止寫入
stop-writes-on-bgsave-error yes

# 是否壓縮
rdbcompression yes

# 導入時是否檢查
rdbchecksum yes
rdb持久化策略比較簡單,下面解釋一下:

save 900 1表示900s內如果有1條是寫入命令,就觸發產生一次快照,可以理解為就進行一次備份
save 300 10表示300s內有10條寫入,就產生快照
下面的類似,那么為什么需要配置這么多條規則呢?因為Redis每個時段的讀寫請求肯定不是均衡的,為了平衡性能與數據安全,我們可以自由定制什么情況下觸發備份。所以這里就是根據自身Redis寫入情況來進行合理配置。

stop-writes-on-bgsave-error yes這個配置也是非常重要的一項配置,這是當備份進程出錯時,主進程就停止接受新的寫入操作,是為了保護持久化的數據一致性問題。如果自己的業務有完善的監控系統,可以禁止此項配置, 否則請開啟。

rdbcompression yes 用于配置是否壓縮RDB文件,建議沒有必要開啟,畢竟Redis本身就屬于CPU密集型服務器,再開啟壓縮會帶來更多的CPU消耗,相比硬盤成本,CPU更值錢。

rdbchecksum yes 是否開啟RDB文件的校驗,在寫入文件和讀取文件時都起作用;關閉checksum在寫入文件和啟動文件時大約能帶來10%的性能提升,但是數據損壞時無法發現

dbfilename dump.rdb RDB文件名

dir ./ RDB文件和AOF文件所在目錄

當然如果你想要禁用RDB配置,也是非常容易的,只需要在save的最后一行寫上:save ""

4. 執行流程圖

RDB執行流程圖
  1. Redis父進程首先判斷:當前是否在執行save,或bgsave/bgrewriteaof(后面會詳細介紹該命令)的子進程,如果在執行則bgsave命令直接返回。bgsave/bgrewriteaof 的子進程不能同時執行,主要是基于性能方面的考慮:兩個并發的子進程同時執行大量的磁盤寫操作,可能引起嚴重的性能問題。

  2. 父進程執行fork操作創建子進程,這個過程中父進程是阻塞的,Redis不能執行來自客戶端的任何命令

  3. 父進程fork后,bgsave命令返回”Background saving started”信息并不再阻塞父進程,并可以響應其他命令

  4. 子進程創建RDB文件,根據父進程內存快照生成臨時快照文件,完成后對原有文件進行原子替換

  5. 子進程發送信號給父進程表示完成,父進程更新統計信息

5. 數據恢復 & Redis啟動加載數據

RDB文件的載入工作是在服務器啟動時自動執行的,并沒有專門的命令。但是由于AOF的優先級更高,因此當AOF開啟時,Redis會優先載入AOF文件來恢復數據;

只有當AOF關閉時,才會在Redis服務器啟動時檢測RDB文件,并自動載入。服務器載入RDB文件期間處于阻塞狀態,直到載入完成為止。

所以Redis的內存數據如果很大,會導致數據恢復時間比較長,因此線上實踐更傾向于限制單個Redis的內存不能太大,同時結合Redis Cluster集群使用多節點部署

Redis啟動日志中可以看到自動載入的執行:


RDB載入過程

Redis載入RDB文件時,會對RDB文件進行校驗,如果文件損壞,則日志中會打印錯誤,Redis啟動失敗。

大家如果更系統了解Redis 高可用集群架構知識,可以關注公眾號【碼農富哥】后回復【Redis】獲取 Redis高可用集群架構視頻

AOF持久化

RDB快照并不是很可靠。如果你的電腦突然宕機了,或者電源斷了,又或者不小心殺掉了進程,那么最新的數據就會丟失。而AOF文件則提供了一種更為可靠的持久化方式。每當Redis接受到會修改數據集的命令時,就會把命令追加到AOF文件里,當你重啟Redis時,AOF里的命令會被重新執行一次,重建數據。

1.工作原理

由于需要記錄Redis的每條寫命令,因此AOF不需要觸發, AOF的執行流程包括:

  • 命令追加(append):將Redis的寫命令追加到緩沖區aof_buf;
  • 文件寫入(write)和文件同步(sync):根據不同的同步策略將aof_buf中的內容同步到硬盤;
  • 文件重寫(rewrite):定期重寫AOF文件,達到壓縮的目的。


    AOF執行流程

2. AOF 持久化配置

# 是否開啟aof
appendonly yes

# 文件名稱
appendfilename "appendonly.aof"

# 同步方式
appendfsync everysec

# aof重寫期間是否同步
no-appendfsync-on-rewrite no

# 重寫觸發配置
auto-aof-rewrite-percentage 100
auto-aof-rewrite-min-size 64mb

# 加載aof時如果有錯如何處理
aof-load-truncated yes

# 文件重寫策略
aof-rewrite-incremental-fsync yes

3. AOF同步策略

同步步驟分為兩步:

  • Redis收到寫命令后首先會追加到AOF緩沖區aof_buf,而不是直接寫入文件系統,因為AOF緩沖區是內存提存的,寫入速度極高,可以避免每次寫入命令到硬盤,導致硬盤IO成為Redis的負載瓶頸
  • 通過調用系統函數 fsync() 把AOF緩沖區的數據真正寫到磁盤里面持久化。由于數據是先存儲在緩沖區內存里面,如果碰到斷電,宕機那么緩沖區里面的數據沒來得急落盤就會丟失,因此我們必須有一個相對可靠的機制保證數據落盤。

Redis寫命令寫入磁盤的命令是通過appendfsync來配置的。
appendfsync 三個取值代表三種落盤策略:

  • always:命令寫入aof緩沖區后立即調用系統fsync操作同步到AOF文件,fsync完成后線程返回。這種情況下,每次有寫命令都要同步到AOF文件,硬盤IO成為性能瓶頸。
  • no:命令寫入aof緩沖區后調用系統write操作,不對AOF文件做fsync同步;同步由操作系統負責,通常同步周期為30秒。這種情況下,文件同步的時間不可控,且緩沖區中堆積的數據會很多,數據安全性無法保證。
  • everysec:命令寫入aof緩沖區后調用系統write操作,write完成后線程返回;fsync同步文件操作由專門的線程每秒調用一次。everysec是前述兩種策略的折中,是性能和數據安全性的平衡,因此是Redis的默認配置,也是我們推薦的配置。

4. AOF文件重寫(rewrite)

隨著寫操作的不斷增加,AOF文件會越來越大。例如你遞增一個計數器100次,那么最終結果就是數據集里的計數器的值為最終的遞增結果,但是AOF文件里卻會把這100次操作完整的記錄下來。而事實上要恢復這個記錄,只需要1個命令就行了,也就是說AOF文件里那100條命令其實可以精簡為1條。所以Redis支持這樣一個功能:在不中斷服務的情況下在后臺重建AOF文件。

AOF重寫流程:

AOF重寫流程

關于文件重寫的流程,有兩點需要特別注意:

(1)重寫由父進程fork子進程進行;

(2)重寫期間Redis執行的寫命令,需要追加到新的AOF文件中,為此Redis引入了aof_rewrite_buf緩存。

對照上圖,文件重寫的流程如下:

  1. Redis父進程首先判斷當前是否存在正在執行 bgsave/bgrewriteaof的子進程,如果存在則bgrewriteaof命令直接返回,如果存在bgsave命令則等bgsave執行完成后再執行。前面曾介紹過,這個主要是基于性能方面的考慮。

  2. 父進程執行fork操作創建子進程,這個過程中父進程是阻塞的。

3.1) 父進程fork后,bgrewriteaof命令返回”Background append only file rewrite started”信息并不再阻塞父進程,并可以響應其他命令。Redis的所有寫命令依然寫入AOF緩沖區,并根據appendfsync策略同步到硬盤,保證原有AOF機制的正確。

3.2) 由于fork操作使用寫時復制技術,子進程只能共享fork操作時的內存數據。由于父進程依然在響應命令,因此Redis使用AOF重寫緩沖區(圖中的aof_rewrite_buf)保存這部分數據,防止新AOF文件生成期間丟失這部分數據。也就是說,bgrewriteaof執行期間,Redis的寫命令同時追加到aof_buf和aof_rewirte_buf兩個緩沖區。

  1. 子進程根據內存快照,按照命令合并規則寫入到新的AOF文件。

5.1) 子進程寫完新的AOF文件后,向父進程發信號,父進程更新統計信息,具體可以通過info persistence查看。

5.2) 父進程把AOF重寫緩沖區的數據寫入到新的AOF文件,這樣就保證了新AOF文件所保存的數據庫狀態和服務器當前狀態一致。

5.3) 使用新的AOF文件替換老文件,完成AOF重寫。

重寫觸發:

  1. 手動觸發:直接調用bgrewriteaof命令,該命令的執行與bgsave有些類似:都是fork子進程進行具體的工作,且都只有在fork時阻塞。

  2. 自動觸發:通過配置 auto-aof-rewrite-percentageauto-aof-rewrite-min-size來完成

auto-aof-rewrite-percentage 100 :Redis會記住自從上一次重寫后AOF文件的大小(如果自Redis啟動后還沒重寫過,則記住啟動時使用的AOF文件的大小)。如果當前的文件大小比起記住的那個大小超過指定的百分比,則會觸配置發重寫。

auto-aof-rewrite-min-size 64mb:同時需要設置一個文件大小最小值,只有大于這個值文件才會重寫,以防文件很小,但是已經達到百分比的情況。

要禁用自動的日志重寫功能,我們可以把百分比設置為0:

auto-aof-rewrite-percentage 0: 禁用日志重寫功能

5. 數據恢復 & Redis啟動加載數據

前面提到過,當AOF開啟時,Redis啟動時會優先載入AOF文件來恢復數據;

只有當AOF關閉時,才會載入RDB文件恢復數據。

當AOF開啟,且AOF文件存在時,Redis啟動日志:


image

持久化方案選擇

1. RDB和AOF的優缺點

RDB和AOF各有優缺點:

RDB持久化

  • 優點:RDB文件緊湊,體積小,網絡傳輸快,適合全量復制;恢復速度比AOF快很多。當然,與AOF相比,RDB最重要的優點之一是對性能的影響相對較小。

  • 缺點:RDB文件的致命缺點在于其數據快照的持久化方式決定了必然做不到實時持久化,而在數據越來越重要的今天,數據的大量丟失很多時候是無法接受的,因此AOF持久化成為主流。此外,RDB文件需要滿足特定格式,兼容性差(如老版本的Redis不兼容新版本的RDB文件)。

AOF持久化

  • 與RDB持久化相對應,AOF的優點在于支持秒級持久化、兼容性好,缺點是文件大、恢復速度慢、對性能影響大。

2. 性能與實踐

通過上面的分析,我們都知道RDB的快照、AOF的重寫都需要fork,這是一個重量級操作,會對Redis造成阻塞。因此為了不影響Redis主進程響應,我們需要盡可能降低阻塞。

  • 降低fork的頻率,比如可以手動來觸發RDB生成快照、與AOF重寫;
  • 控制Redis最大使用內存,防止fork耗時過長;
  • 使用更牛逼的硬件;
  • 合理配置Linux的內存分配策略,避免因為物理內存不足導致fork失敗。

在線上我們到底該怎么做?我提供一些自己的實踐經驗。

  • 如果Redis中的數據并不是特別敏感或者可以通過其它方式重寫生成數據,可以關閉持久化,如果丟失數據可以通過其它途徑補回;
  • 自己制定策略定期檢查Redis的情況,然后可以手動觸發備份、重寫數據;
  • 單機如果部署多個實例,要防止多個機器同時運行持久化、重寫操作,防止出現內存、CPU、IO資源競爭,讓持久化變為串行;
  • 可以加入主從機器,利用一臺從機器進行備份處理,其它機器正常響應客戶端的命令;
  • RDB持久化與AOF持久化可以同時存在,配合使用。

總結

Redis的高可用系列:持久化就已經講完了,持久化主要有RDB和AOF兩種技術,大家按照上面所介紹的原理和流程,根據線上具體需求選擇適合自己的持久化方案。

另外,寫原創技術文章不易,要花費好多時間和精力,希望大家看到文章也能有所收獲!你們的點贊和收藏就能成為我繼續堅持輸出原創文章的動力!大家也可以關注我的公眾號,訂閱更多我的文章!

歡迎關注公眾號:「碼農富哥」,致力于分享后端技術 (高并發架構,分布式集群系統,消息隊列中間件,網絡,微服務,Linux, TCP/IP, HTTP, MySQL, Redis), Python 等 原創干貨 和 面試指南!
關注公眾號后回復【資源】免費獲取 2T 編程視頻和電子書,回復【Redis】獲取 Redis高可用集群架構視頻

image
最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。