《Redis開發(fā)與運(yùn)維》學(xué)習(xí)筆記--Redis持久化

本文是《Redis開發(fā)與運(yùn)維》的學(xué)習(xí)筆記。內(nèi)容大部分摘自此書。

眾所周知,redis是內(nèi)存數(shù)據(jù)庫,它把數(shù)據(jù)存儲在內(nèi)存中,這樣在加快讀取速度的同時也對數(shù)據(jù)安全性產(chǎn)生了新的問題,即當(dāng)redis所在服務(wù)器發(fā)生宕機(jī)后,redis數(shù)據(jù)庫里的所有數(shù)據(jù)將會全部丟失。為了解決這個問題,redis提供了持久化功能——RDBAOF。通俗的講就是將內(nèi)存中的數(shù)據(jù)寫入硬盤中。當(dāng)下次重啟時利用之前持久化的文件即可實(shí)現(xiàn)數(shù)據(jù)恢復(fù)。

一、RDB

RDB持久化是把當(dāng)前進(jìn)程數(shù)據(jù)生成快照保存到硬盤的過程。觸發(fā)RBD持久化過程分為手動觸發(fā)和自動觸發(fā)。

1.1觸發(fā)機(jī)制

對于手動觸發(fā),分別對應(yīng)save和bgsave命令:
save:阻塞當(dāng)前Redis服務(wù)器,知道RDB過程完成。
bgsave:)Redis進(jìn)程執(zhí)行fork操作創(chuàng)建子進(jìn)程,RDB持久化過程由子進(jìn)程負(fù)責(zé),完成后自動結(jié)束。阻塞只發(fā)生在fork階段,一般時間很短。
自動觸發(fā)的場景:
①save m n 表示m秒內(nèi)數(shù)據(jù)集存在n次修改時,自動觸發(fā)bgsave。
②如果從節(jié)點(diǎn)執(zhí)行全量復(fù)制操作,主節(jié)點(diǎn)自動執(zhí)行bgsave生成RDB文件并發(fā)送給從節(jié)點(diǎn)。
③執(zhí)行debug reload命令重新加載redis時,也會自動觸發(fā)save操作。
④默認(rèn)情況下執(zhí)行shutdown命令時,如果沒有開啟AOF,則自動執(zhí)行bgsave。

1.2流程說明

圖片中的 5 個步驟所進(jìn)行的操作如下:

  • Redis 父進(jìn)程首先判斷:當(dāng)前是否在執(zhí)行 save 或 bgsave/bgrewriteaof(后面會詳細(xì)介紹該命令)的子進(jìn)程,如果在執(zhí)行則 bgsave 命令直接返回。
    bgsave/bgrewriteaof 的子進(jìn)程不能同時執(zhí)行,主要是基于性能方面的考慮:兩個并發(fā)的子進(jìn)程同時執(zhí)行大量的磁盤寫操作,可能引起嚴(yán)重的性能問題。
  • 父進(jìn)程執(zhí)行 fork 操作創(chuàng)建子進(jìn)程,這個過程中父進(jìn)程是阻塞的,Redis 不能執(zhí)行來自客戶端的任何命令。
  • 父進(jìn)程 fork 后,bgsave 命令返回”Background saving started”信息并不再阻塞父進(jìn)程,并可以響應(yīng)其他命令。
  • 子進(jìn)程創(chuàng)建 RDB 文件,根據(jù)父進(jìn)程內(nèi)存快照生成臨時快照文件,完成后對原有文件進(jìn)行原子替換。
  • 子進(jìn)程發(fā)送信號給父進(jìn)程表示完成,父進(jìn)程更新統(tǒng)計信息。

1.3 RDB文件

RDB 文件是經(jīng)過壓縮的二進(jìn)制文件,Redis默認(rèn)采用LZF算法對生成的RDB文件做壓縮處理,壓縮后的文件遠(yuǎn)遠(yuǎn)小于內(nèi)存大小。

RDB 文件格式如下圖所示:


其中各個字段的含義說明如下:

  • REDIS:常量,保存著“REDIS”5 個字符。
  • db_version:RDB 文件的版本號,注意不是 Redis 的版本號。
  • SELECTDB 0 pairs:表示一個完整的數(shù)據(jù)庫(0 號數(shù)據(jù)庫),同理 SELECTDB 3 pairs 表示完整的 3 號數(shù)據(jù)庫。

只有當(dāng)數(shù)據(jù)庫中有鍵值對時,RDB 文件中才會有該數(shù)據(jù)庫的信息(上圖所示的 Redis 中只有 0 號和 3 號數(shù)據(jù)庫有鍵值對);如果 Redis 中所有的數(shù)據(jù)庫都沒有鍵值對,則這一部分直接省略。

其中:SELECTDB 是一個常量,代表后面跟著的是數(shù)據(jù)庫號碼;0 和 3 是數(shù)據(jù)庫號碼;pairs 則存儲了具體的鍵值對信息,包括 key、value 值,及其數(shù)據(jù)類型、內(nèi)部編碼、過期時間、壓縮信息等等。

  • EOF:常量,標(biāo)志 RDB 文件正文內(nèi)容結(jié)束。
  • check_sum:前面所有內(nèi)容的校驗(yàn)和;Redis 在載入 RBD 文件時,會計算前面的校驗(yàn)和并與 check_sum 值比較,判斷文件是否損壞。

二、AOF

RDB 持久化是將進(jìn)程數(shù)據(jù)寫入文件,而 AOF 持久化(即 Append Only File 持久化),則是將 Redis 執(zhí)行的每次寫命令記錄到單獨(dú)的日志文件中,當(dāng) Redis 重啟時再次執(zhí)行 AOF 文件中的命令來恢復(fù)數(shù)據(jù)。與 RDB 相比,AOF 的實(shí)時性更好,因此已成為主流的持久化方案。

2.1使用AOF

Redis 服務(wù)器默認(rèn)開啟 RDB,關(guān)閉 AOF;要開啟 AOF,需要在配置文件中配置:appendonly yes。

AOF的工作流程操作:命令寫入、文件同步、文件重寫、重啟加載。



(1)命令寫入(append)
Redis 先將寫命令追加到緩沖區(qū),而不是直接寫入文件,主要是為了避免每次有寫命令都直接寫入硬盤,導(dǎo)致硬盤 IO 成為 Redis 負(fù)載的瓶頸。

命令追加的格式是 Redis 命令請求的協(xié)議格式,它是一種純文本格式,具有兼容性好、可讀性強(qiáng)、容易處理、操作簡單避免二次開銷等優(yōu)點(diǎn),具體格式略。

在 AOF 文件中,除了用于指定數(shù)據(jù)庫的 select 命令(如 select 0 為選中 0 號數(shù)據(jù)庫)是由 Redis 添加的,其他都是客戶端發(fā)送來的寫命令。

(2)文件寫入(write)和文件同步(sync)
Redis 提供了多種 AOF 緩存區(qū)的同步文件策略,策略涉及到操作系統(tǒng)的 write 函數(shù)和 fsync 函數(shù),說明如下:

為了提高文件寫入效率,在現(xiàn)代操作系統(tǒng)中,當(dāng)用戶調(diào)用 write 函數(shù)將數(shù)據(jù)寫入文件時,操作系統(tǒng)通常會將數(shù)據(jù)暫存到一個內(nèi)存緩沖區(qū)里,當(dāng)緩沖區(qū)被填滿或超過了指定時限后,才真正將緩沖區(qū)的數(shù)據(jù)寫入到硬盤里。
這樣的操作雖然提高了效率,但也帶來了安全問題:如果計算機(jī)停機(jī),內(nèi)存緩沖區(qū)中的數(shù)據(jù)會丟失。

因此系統(tǒng)同時提供了 fsync、fdatasync 等同步函數(shù),可以強(qiáng)制操作系統(tǒng)立刻將緩沖區(qū)中的數(shù)據(jù)寫入到硬盤里,從而確保數(shù)據(jù)的安全性。

AOF 緩存區(qū)的同步文件策略由參數(shù) appendfsync 控制,各個值的含義如下:

always:命令寫入 aof_buf 后立即調(diào)用系統(tǒng) fsync 操作同步到 AOF 文件,fsync 完成后線程返回。
這種情況下,每次有寫命令都要同步到 AOF 文件,硬盤 IO 成為性能瓶頸,Redis 只能支持大約幾百 TPS 寫入,嚴(yán)重降低了 Redis 的性能。

即便是使用固態(tài)硬盤(SSD),每秒大約也只能處理幾萬個命令,而且會大大降低 SSD 的壽命。

no:命令寫入 aof_buf 后調(diào)用系統(tǒng) write 操作,不對 AOF 文件做 fsync 同步;同步由操作系統(tǒng)負(fù)責(zé),通常同步周期為 30 秒。
這種情況下,文件同步的時間不可控,且緩沖區(qū)中堆積的數(shù)據(jù)會很多,數(shù)據(jù)安全性無法保證。

everysec:命令寫入 aof_buf 后調(diào)用系統(tǒng) write 操作,write 完成后線程返回;fsync 同步文件操作由專門的線程每秒調(diào)用一次。
everysec 是前述兩種策略的折中,是性能和數(shù)據(jù)安全性的平衡,因此是 Redis 的默認(rèn)配置,也是我們推薦的配置

(3)文件重寫(rewrite)
隨著時間流逝,Redis 服務(wù)器執(zhí)行的寫命令越來越多,AOF 文件也會越來越大;過大的 AOF 文件不僅會影響服務(wù)器的正常運(yùn)行,也會導(dǎo)致數(shù)據(jù)恢復(fù)需要的時間過長。

文件重寫是指定期重寫 AOF 文件,減小 AOF 文件的體積。需要注意的是,AOF 重寫是把 Redis 進(jìn)程內(nèi)的數(shù)據(jù)轉(zhuǎn)化為寫命令,同步到新的 AOF 文件;不會對舊的 AOF 文件進(jìn)行任何讀取、寫入操作!

關(guān)于文件重寫需要注意的另一點(diǎn)是:對于 AOF 持久化來說,文件重寫雖然是強(qiáng)烈推薦的,但并不是必須的。即使沒有文件重寫,數(shù)據(jù)也可以被持久化并在 Redis 啟動的時候?qū)搿?/p>

因此在一些實(shí)現(xiàn)中,會關(guān)閉自動的文件重寫,然后通過定時任務(wù)在每天的某一時刻定時執(zhí)行。

文件重寫之所以能夠壓縮 AOF 文件,原因在于:

  • 過期的數(shù)據(jù)不再寫入文件。
  • 無效的命令不再寫入文件:如有些數(shù)據(jù)被重復(fù)設(shè)值(set mykey v1,set mykey v2)、有些數(shù)據(jù)被刪除了(sadd myset v1,del myset)等等。
  • 多條命令可以合并為一個:如 sadd myset v1,sadd myset v2,sadd myset v3 可以合并為 sadd myset v1 v2 v3。

不過為了防止單條命令過大造成客戶端緩沖區(qū)溢出,對于 list、set、hash、zset 類型的 key,并不一定只使用一條命令。

而是以某個常量為界將命令拆分為多條。這個常量在 redis.h/REDIS_AOF_REWRITE_ITEMS_PER_CMD 中定義,不可更改,3.0 版本中值是 64。

通過上述內(nèi)容可以看出,由于重寫后 AOF 執(zhí)行的命令減少了,文件重寫既可以減少文件占用的空間,也可以加快恢復(fù)速度。

文件重寫的觸發(fā),分為手動觸發(fā)和自動觸發(fā):

手動觸發(fā),直接調(diào)用 bgrewriteaof 命令,該命令的執(zhí)行與 bgsave 有些類似:都是 fork 子進(jìn)程進(jìn)行具體的工作,且都只有在 fork 時阻塞。

自動觸發(fā),根據(jù) auto-aof-rewrite-min-size 和 auto-aof-rewrite-percentage 參數(shù),以及 aof_current_size 和 aof_base_size 狀態(tài)確定觸發(fā)時機(jī):

  • auto-aof-rewrite-min-size:執(zhí)行 AOF 重寫時,文件的最小體積,默認(rèn)值為 64MB。
  • auto-aof-rewrite-percentage:執(zhí)行 AOF 重寫時,當(dāng)前 AOF 大小(即 aof_current_size)和上一次重寫時 AOF 大小(aof_base_size)的比值。

其中,參數(shù)可以通過 config get 命令查看
config get auto-aof-rewrite-min-size

只有當(dāng) auto-aof-rewrite-min-size 和 auto-aof-rewrite-percentage 兩個參數(shù)同時滿足時,才會自動觸發(fā) AOF 重寫,即 bgrewriteaof 操作。

文件重寫流程如下圖所示:


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

  • 1):Redis 父進(jìn)程首先判斷當(dāng)前是否存在正在執(zhí)行 bgsave/bgrewriteaof 的子進(jìn)程,如果存在則 bgrewriteaof 命令直接返回;如果存在 bgsave 命令則等 bgsave 執(zhí)行完成后再執(zhí)行,這個主要是基于性能方面的考慮。
  • 2):父進(jìn)程執(zhí)行 fork 操作創(chuàng)建子進(jìn)程,這個過程中父進(jìn)程是阻塞的。
  • 3.1):父進(jìn)程 fork 后,bgrewriteaof 命令返回“Background append only file rewrite started”信息并不再阻塞父進(jìn)程,并可以響應(yīng)其他命令。
    Redis 的所有寫命令依然寫入 AOF 緩沖區(qū),并根據(jù) appendfsync 策略同步到硬盤,保證原有 AOF 機(jī)制的正確。
  • 3.2):由于 fork 操作使用寫時復(fù)制技術(shù),子進(jìn)程只能共享 fork 操作時的內(nèi)存數(shù)據(jù)。
    由于父進(jìn)程依然在響應(yīng)命令,因此 Redis 使用 AOF 重寫緩沖區(qū)(圖中的 aof_rewrite_buf)保存這部分?jǐn)?shù)據(jù),防止新 AOF 文件生成期間丟失這部分?jǐn)?shù)據(jù)。
    也就是說,bgrewriteaof 執(zhí)行期間,Redis 的寫命令同時追加到 aof_buf 和 aof_rewirte_buf 兩個緩沖區(qū)。
  • 4):子進(jìn)程根據(jù)內(nèi)存快照,按照命令合并規(guī)則寫入到新的 AOF 文件。
  • 5.1):子進(jìn)程寫完新的 AOF 文件后,向父進(jìn)程發(fā)信號,父進(jìn)程更新統(tǒng)計信息,具體可以通過 info persistence 查看。
  • 5.2):父進(jìn)程把 AOF 重寫緩沖區(qū)的數(shù)據(jù)寫入到新的 AOF 文件,這樣就保證了新 AOF 文件所保存的數(shù)據(jù)庫狀態(tài)和服務(wù)器當(dāng)前狀態(tài)一致。
  • 5.3):使用新的 AOF 文件替換老文件,完成 AOF 重寫。

三、重啟加載

前面提到過,當(dāng) AOF 開啟時,Redis 啟動時會優(yōu)先載入 AOF 文件來恢復(fù)數(shù)據(jù);只有當(dāng) AOF 關(guān)閉時,才會載入 RDB 文件恢復(fù)數(shù)據(jù)。

整個加載文件的過程如下圖:

與載入 RDB 文件類似,Redis 載入 AOF 文件時,會對 AOF 文件進(jìn)行校驗(yàn),如果文件損壞,則日志中會打印錯誤,Redis 啟動失敗。

但如果是 AOF 文件結(jié)尾不完整(機(jī)器突然宕機(jī)等容易導(dǎo)致文件尾部不完整),且 aof-load-truncated 參數(shù)開啟,則日志中會輸出警告,Redis 忽略掉 AOF 文件的尾部,啟動成功。

四、問題定位與優(yōu)化

4.1 fork操作

首先說明一下 fork 操作:父進(jìn)程通過 fork 操作可以創(chuàng)建子進(jìn)程;子進(jìn)程創(chuàng)建后,父子進(jìn)程共享代碼段,不共享進(jìn)程的數(shù)據(jù)空間,但是子進(jìn)程會獲得父進(jìn)程的數(shù)據(jù)空間的副本。

在操作系統(tǒng) fork 的實(shí)際實(shí)現(xiàn)中,基本都采用了寫時復(fù)制技術(shù),即在父/子進(jìn)程試圖修改數(shù)據(jù)空間之前,父子進(jìn)程實(shí)際上共享數(shù)據(jù)空間。

但是當(dāng)父/子進(jìn)程的任何一個試圖修改數(shù)據(jù)空間時,操作系統(tǒng)會為修改的那一部分(內(nèi)存的一頁)制作一個副本。

雖然 fork 時,子進(jìn)程不會復(fù)制父進(jìn)程的數(shù)據(jù)空間,但是會復(fù)制內(nèi)存頁表(頁表相當(dāng)于內(nèi)存的索引、目錄);父進(jìn)程的數(shù)據(jù)空間越大,內(nèi)存頁表越大,fork 時復(fù)制耗時也會越多。

在 Redis 中,無論是 RDB 持久化的 bgsave,還是 AOF 重寫的 bgrewriteaof,都需要 fork 出子進(jìn)程來進(jìn)行操作。

如果 Redis 內(nèi)存過大,會導(dǎo)致 fork 操作時復(fù)制內(nèi)存頁表耗時過多;而 Redis 主進(jìn)程在進(jìn)行 fork 時,是完全阻塞的,也就意味著無法響應(yīng)客戶端的請求,會造成請求延遲過大。

對于不同的硬件、不同的操作系統(tǒng),fork 操作的耗時會有所差別,一般來說,如果 Redis 單機(jī)內(nèi)存達(dá)到了 10GB,fork 時耗時可能會達(dá)到百毫秒級別(如果使用 Xen 虛擬機(jī),這個耗時可能達(dá)到秒級別)。

因此,一般來說 Redis 單機(jī)內(nèi)存一般要限制在 10GB 以內(nèi);不過這個數(shù)據(jù)并不是絕對的,可以通過觀察線上環(huán)境 fork 的耗時來進(jìn)行調(diào)整。

觀察的方法如下:執(zhí)行命令 info stats,查看 latest_fork_usec 的值,單位為微秒。

為了減輕 fork 操作帶來的阻塞問題,除了控制 Redis 單機(jī)內(nèi)存的大小以外,還可以適度放寬 AOF 重寫的觸發(fā)條件、選用物理機(jī)或高效支持 fork 操作的虛擬化技術(shù)等,例如使用 Vmware 或 KVM 虛擬機(jī),不要使用 Xen 虛擬機(jī)。

4.2子進(jìn)程開銷

  • CPU
  • 內(nèi)存
  • 硬盤

4.3硬盤追加阻塞

有關(guān)linux寫時復(fù)制:https://www.cnblogs.com/biyeymyhjob/archive/2012/07/20/2601655.html

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • 從這篇文章開始,將依次介紹Redis高可用相關(guān)的知識——持久化、復(fù)制(及讀寫分離)、哨兵、以及集群。 本文將先說明...
    不變甄心閱讀 736評論 0 4
  • 前言 在上一篇文章中,介紹了Redis內(nèi)存模型,從這篇文章開始,將依次介紹Redis高可用相關(guān)的知識——持久化、復(fù)...
    Java架構(gòu)閱讀 2,500評論 3 21
  • 企業(yè)級redis集群架構(gòu)的特點(diǎn) 海量數(shù)據(jù) 高并發(fā) 高可用 要達(dá)到高可用,持久化是不可減少的,持久化主要是做災(zāi)難恢復(fù)...
    lucode閱讀 2,277評論 0 7
  • O 又到周末,我決定讓自己完全放松下來,既不去想要不要參加今天的營銷活動,也打消了去看望老爸的念頭。因?yàn)槲业纳眢w又...
    果果親閱讀 205評論 3 3
  • 七點(diǎn)半。西北方向掛著一顆明亮的星,唯一的一顆耀眼而孤獨(dú)的星。沒有抬頭仰望的人,低著頭只顧自己玩手機(jī)走路的人,我覺得...
    文森林木閱讀 131評論 0 0

友情鏈接更多精彩內(nèi)容