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

免費視頻福利推薦:

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

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

關注公衆號後回覆【資源】免費獲取 2T 編程視頻和電子書,回覆【Redis】獲取 Redis高可用集羣架構視頻

Redis高可用概述

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

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

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

  1. 持久化:持久化是最簡單的高可用方法(有時甚至不被歸爲高可用的手段),主要作用是數據備份,即將數據存儲在硬盤,保證數據不會因進程退出而丟失。
  2. 複製:複製是高可用Redis的基礎,哨兵和集羣都是在複製基礎上實現高可用的。複製主要實現了數據的多機備份,以及對於讀操作的負載均衡和簡單的故障恢復。缺陷:故障恢復無法自動化;寫操作無法負載均衡;存儲能力受到單機的限制。
  3. 哨兵:在複製的基礎上,哨兵實現了自動化的故障恢復。缺陷:寫操作無法負載均衡;存儲能力受到單機的限制。
  4. 集羣:通過集羣,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服務器阻塞期間,服務器不能處理任何命令請求,因此線上環境不推薦使用

2. bgsave命令(手動觸發)

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

3. 自動觸發

除了手動觸發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. 執行流程圖:

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啓動日誌中可以看到自動載入的執行:

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文件,達到壓縮的目的。

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文件。

重寫流程:

Redis 持久化與恢復

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

(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兩個緩衝區。

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

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

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

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

重寫觸發:

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

2. 自動觸發:通過配置 auto-aof-rewrite-percentage 和 auto-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啓動日誌:

 

持久化方案選擇

1. RDB和AOF的優缺點

RDB和AOF各有優缺點:

RDB持久化

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

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

AOF持久化

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

2. 性能與實踐

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

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

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

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

總結

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

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

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

關注公衆號後回覆【資源】免費獲取 2T 編程視頻和電子書,回覆【Redis】獲取 Redis高可用集羣架構視頻

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