一起看懂Redis持久化原理

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

RDB:在指定的時間間隔能對你的數據進行快照存儲(它是備份當前瞬間 Redis 在內存中的數據記錄,通過快照(snapshotting)實現的)。

AOF:記錄每次對服務器寫的操作,當服務器重啓的時候會重新執行這些命令來恢復原始的數據(只追加文件(Append-Only File,AOF),其作用就是當 Redis 執行寫命令後,在一定的條件下將執行過的寫命令依次保存在 Redis 的文件中,將來就可以依次執行那些保存的命令恢復 Redis 的數據了)。

 

本文將通過下面內容的介紹,希望能夠讓大家更全面、清晰的認識這兩種持久化方式,同時理解這種保存數據的思路,應用於自己的系統設計中。

 

持久化的配置

RDB與AOF持久化的工作原理

如何從持久化中恢復數據

關於性能與實踐建議

 

持久化的配置


爲了使用持久化的功能,我們需要先知道該如何開啓持久化的功能。

 

RDB的持久化配置


# 時間策略save 900 1
save 300 10
save 60 10000
# 文件名稱
dbfilename dump.rdb
# 文件保存路徑
dir /home/work/app/redis/data/
# 如果持久化出錯,主進程是否停止寫入
stop-writes-on-bgsave-error yes
# 是否壓縮
rdbcompression yes
# 導入時是否檢查
rdbchecksum yes

配置其實非常簡單,這裏說一下持久化的時間策略具體是什麼意思。

save 900 1 表示900s內如果有1條是寫入命令,就觸發產生一次快照,可以理解爲就進行一次備份

save 300 10 表示300s內有10條寫入,就產生快照

 

下面的類似,那麼爲什麼需要配置這麼多條規則呢?因爲Redis每個時段的讀寫請求肯定不是均衡的,爲了平衡性能與數據安全,我們可以自由定製什麼情況下觸發備份。所以這裏就是根據自身Redis寫入情況來進行合理配置。

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

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

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

 

 

RDB的原理


在Redis中RDB持久化的觸發分爲兩種:自己手動觸發與Redis定時觸發。

 

針對RDB方式的持久化,手動觸發可以使用:

save:會阻塞當前 Redis 服務器響應其他命令,直到 RDB 快照生成完成爲止,對於內存比較大的實例會造成長時間阻塞,所以線上環境不建議使用。

bgsave:Redis 主進程會 fork 一個子進程,RDB 快照生成有子進程來負責,完成之後,子進程自動結束,bgsave 只會在 fork 子進程的時候短暫的阻塞,這個過程是非常短的,所以推薦使用該命令來手動觸發。

 

而自動觸發的場景主要是有以下幾點:

根據我們的 save m n 配置規則自動觸發

從節點全量複製時,主節點發送rdb文件給從節點完成複製操作,主節點會觸發 bgsave

執行 debug reload 時

執行 shutdown時,如果沒有開啓aof,也會觸發

 

由於 save 基本不會被使用到,我們重點看看 bgsave 這個命令是如何完成RDB的持久化的

bgsave 命令大概有以下幾個步驟:

1、執行 bgsave 命令,Redis 主進程判斷當前是否存在正在執行的 RDB/AOF 子進程,如果存在, bgsave 命令直接返回不在往下執行。

2、父進程執行 fork 操作創建子進程,fork 操作過程中父進程會阻塞,fork 完成後父進程將不在阻塞可以接受其他命令。

3、子進程創建新的 RDB 文件,基於父進程當前內存數據生成臨時快照文件,完成後用新的 RDB 文件替換原有的 RDB 文件,並且給父進程發送 RDB 快照生成完畢通知。

image1

image1

RDB 快照是某一時刻 Redis 節點內存數據,非常適合做備份,上傳到遠程服務器或者文件系統中,用於容災備份。數據恢復時 RDB 要遠遠快於 AOF。

有優點同樣存在缺點,RDB 的缺點有:

RDB 持久化方式數據沒辦法做到實時持久化/秒級持久化。我們已經知道了 bgsave 命令每次運行都要執行 fork 操作創建子進程,屬於重量級操作,頻繁執行成本過高

RDB 文件使用特定二進制格式保存,Redis 版本演進過程中有多個格式 的 RDB 版本,存在老版本 Redis 服務無法兼容新版 RDB 格式的問題。

如果我們對數據要求比較高,每一秒的數據都不能丟,RDB 持久化方式肯定是不能夠滿足要求的,那 Redis 有沒有辦法滿足呢,答案是有的,那就是接下來的 AOF 持久化方式。

不管是RDB還是AOF都是先寫入一個臨時文件,然後通過 rename 完成文件的替換工作。

 

AOF的配置


# 是否開啓aof
appendonly yes

# 文件名稱
appendfilename "appendonly.aof"

# 同步方式
appendfsync everysec

#在rdb寫磁盤過程中,是否需要停止aof,默認是不停,如果負載較高,建議停止。
# 這個時候數據也不會丟失,在暫時存在內存隊列,rdb數據寫完後,aof繼續。
no-appendfsync-on-rewrite no

# 重寫觸發配置
auto-aof-rewrite-percentage 100 --aof文件大小相比上次文件大小,增長100%時,重寫
auto-aof-rewrite-min-size 64mb  --aof文件至少超過64M時,重寫

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

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

還是重點解釋一些關鍵的配置:

appendfsync everysec 它其實有三種模式:

  • always:把每個寫命令都立即同步到aof,很慢,但是很安全
  • everysec:每秒同步一次,是折中方案
  • no:redis不處理交給OS來處理,非常快,但是也最不安全

一般情況下都採用 everysec 配置,這樣可以兼顧速度與安全,最多損失1s的數據。

aof-load-truncated yes 如果該配置啓用,在加載時發現aof尾部不正確是,會向客戶端寫入一個log,但是會繼續執行,如果設置爲 no ,發現錯誤就會停止,必須修復後才能重新加載。

 

 

AOF的原理


AOF的整個流程大體來看可以分爲兩步,一步是命令的實時寫入(如果是 appendfsync everysec 配置,會有1s損耗),第二步是對aof文件的重寫。

在 AOF 持久化過程中有兩個非常重要的操作:一個是將操作命令追加到 AOF_BUF 緩存區,另一個是 AOF_buf 緩存區數據同步到 AOF 文件,接下來我們詳細聊一聊這兩個操作:

1、爲什麼要將命令寫入到 aof_buf 緩存區而不是直接寫入到 aof 文件?

我們知道 Redis 是單線程響應,如果每次寫入 AOF 命令都直接追加到磁盤上的 AOF 文件中,這樣頻繁的 IO 開銷,Redis 的性能就完成取決於你的機器硬件了,爲了提升 Redis 的響應效率就添加了一層 aof_buf 緩存層, 利用的是操作系統的 cache 技術,這樣就提升了 Redis 的性能,雖然這樣性能是解決了,但是同時也引入了一個問題,aof_buf 緩存區數據如何同步到 AOF 文件呢?由誰同步呢?這就是我們接下來要聊的一個操作:fsync 操作。

 

2、aof_buf 緩存區數據如何同步到 aof 文件中?

aof_buf 緩存區數據寫入到 aof 文件是有 linux 系統去完成的,由於 Linux 系統調度機制週期比較長,如果系統故障宕機了,意味着一個週期內的數據將全部丟失,這不是我們想要的,所以 Linux 提供了一個 fsync 命令,fsync 是針對單個文件操作(比如這裏的 AOF 文件),做強制硬盤同步,fsync 將阻塞直到寫入硬盤完成後返回,保證了數據持久化,正是由於有這個命令,所以 redis 提供了配置項讓我們自行決定何時進行磁盤同步,redis 在 redis.conf 中提供了appendfsync 配置項,有如下三個選項:

# appendfsync always 
appendfsync everysec 
# appendfsync no 

always:每次有寫入命令都進行緩存區與磁盤數據同步,這樣保證不會有數據丟失,但是這樣會導致 redis 的吞吐量大大下降,下降到每秒只能支持幾百的 TPS ,這違背了 redis 的設計,所以不推薦使用這種方式

everysec:這是 redis 默認的同步機制,雖然每秒同步一次數據,看上去時間也很快的,但是它對 redis 的吞吐量沒有任何影響,每秒同步一次的話意味着最壞的情況下我們只會丟失 1 秒的數據, 推薦使用這種同步機制,兼顧性能和數據安全

no:不做任何處理,緩存區與 aof 文件同步交給系統去調度,操作系統同步調度的週期不固定,最長會有 30 秒的間隔,這樣出故障了就會丟失比較多的數據。 這就是三種磁盤同步策略,但是你有沒有注意到一個問題,AOF 文件都是追加的,隨着服務器的運行 AOF 文件會越來越大體積過大的 AOF 文件對 redis 服務器甚至是主機都會有影響,而且在 Redis 重啓時加載過大的 AOF 文件需要過多的時間,這些都是不友好的,那 Redis 是如何解決這個問題的呢?Redis 引入了重寫機制來解決 AOF 文件過大的問題。

 

3、Redis 是如何進行 AOF 文件重寫的?(隨着命令不斷寫入AOF,文件會越來越大,爲了解決這個問題,redis引入了AOF重寫機制壓縮文件)

因爲AOF持久化是通過保存被執行的寫命令來記錄數據庫狀態的,所以隨着服務器運行時間的流逝,AOF文件中的內容越來越多,文件體積越來越大,如果不加以控制,會對redis服務器甚至宿主計算器造成影響。

所謂的“重寫”其實是一個有歧義的詞語, 實際上, AOF 重寫並不需要對原有的 AOF 文件進行任何寫入和讀取, 它針對的是數據庫中鍵的當前值。

考慮這樣一個情況, 如果服務器對鍵 list 執行了以下四條命令:

RPUSHlist1234// [1, 2, 3, 4]

RPOPlist// [1, 2, 3]

LPOPlist// [2, 3]

LPUSHlist1// [1, 2, 3]

那麼當前列表鍵 list 在數據庫中的值就爲 [1, 2, 3] 。

如果我們要保存這個列表的當前狀態, 並且儘量減少所使用的命令數, 那麼最簡單的方式不是去 AOF 文件上分析前面執行的四條命令, 而是直接讀取 list 鍵在數據庫的當前值, 然後用一條 RPUSH 1 2 3 命令來代替前面的四條命令。

再考慮這樣一個例子, 如果服務器對集合鍵 animal 執行了以下命令:

SADDanimalcat// {cat}

SADDanimaldogpandatiger// {cat, dog, panda, tiger}

SREManimalcat// {dog, panda, tiger}

SADDanimalcatlion// {cat, lion, dog, panda, tiger}

那麼使用一條 SADD animal cat lion dog panda tiger 命令, 就可以還原 animal 集合的狀態, 這比之前的四條命令調用要大大減少。

除了列表和集合之外, 字符串、有序集、哈希表等鍵也可以用類似的方法來保存狀態, 並且保存這些狀態所使用的命令數量, 比起之前建立這些鍵的狀態所使用命令的數量要大大減少。

 

Redis AOF 文件重寫是把 Redis 進程內的數據轉化爲寫命令同步到新 AOF 文件的過程,重寫之後的 AOF 文件會比舊的 AOF 文件佔更小的體積,這是由以下幾個原因導致的:

進程內已經超時的數據不再寫入文件

舊的 AOF 文件含有無效命令,如 del key1、hdel key2、srem keys、set a111、set a222等。重寫使用進程內數據直接生成,這樣新的AOF文件只保 留最終數據的寫入命令

多條寫命令可以合併爲一個,如:lpush list a、lpush list b、lpush list c可以轉化爲:lpush list a b c。爲了防止單條命令過大造成客戶端緩衝區溢 出,對於 list、set、hash、zset 等類型操作,以 64 個元素爲界拆分爲多條。

重寫之後的 AOF 文件體積更小了,不但能夠節約磁盤空間,更重要的是在 Redis 數據恢復時,更小體積的 AOF 文件加載時間更短。AOF 文件重寫跟 RDB 持久化一樣分爲手動觸發和自動觸發,手動觸發直接調用 bgrewriteaof 命令就好了,我們後面會詳細聊一聊這個命令,自動觸發就需要我們在 redis.conf 中修改以下幾個配置:

auto-aof-rewrite-percentage 100
auto-aof-rewrite-min-size 64mb
auto-aof-rewrite-percentage:代表當前 AOF文件空間 (aof_current_size)和上一次重寫後 AOF 文件空間(aof_base_size)的比值,默認是 100%,也就是一樣大的時候

auto-aof-rewrite-min-size:表示運行 AOF 重寫時 AOF 文件最小體積,默認爲 64MB,也就是說 AOF 文件最小爲 64MB 纔有可能觸發重寫。 滿足了這兩個條件,Redis 就會自動觸發 AOF 文件重寫,AOF 文件重寫的細節跟 RDB 持久化生成快照有點類似,下面是 AOF 文件重寫流程圖:

 

file

對於上圖有四個關鍵點補充一下:

1、在重寫期間,由於主進程依然在響應命令,爲了保證最終備份的完整性;因此它依然會寫入舊的AOF file中,如果重寫失敗,能夠保證數據不丟失。

2、爲了把重寫期間響應的寫入信息也寫入到新的文件中,因此也會爲子進程保留一個buf,防止新寫的file丟失數據。

3、重寫是直接把當前內存的數據生成對應命令,並不需要讀取老的AOF文件進行分析、命令合併。

4、AOF文件直接採用的文本協議,主要是兼容性好、追加方便、可讀性高可認爲修改修復。

無論是 RDB 還是 AOF 都是先寫入一個臨時文件,然後通過 rename 完成文件的替換工作。

 

從持久化中恢復數據


數據的備份、持久化做完了,我們如何從這些持久化文件中恢復數據呢?如果一臺服務器上有既有RDB文件,又有AOF文件,該加載誰呢?

其實想要從這些文件中恢復數據,只需要重新啓動Redis即可。我們還是通過圖來了解這個流程:
image2

啓動時會先檢查AOF文件是否存在,如果不存在就嘗試加載RDB。那麼爲什麼會優先加載AOF呢?因爲AOF保存的數據更完整,通過上面的分析我們知道AOF基本上最多損失1s的數據。

 

性能與實踐


通過上面的分析,我們都知道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持久化可以同時存在,配合使用。

 

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