1. 程式人生 > 實用技巧 >Day2 Redis資料持久化--redis 學習第二章

Day2 Redis資料持久化--redis 學習第二章

Redis 資料持久化?-----意外宕機如何避免資料丟失

我們在實際應用生產中,大部分公司會把 Redis 當做快取使用,用它來把後端資料庫中的資料儲存在記憶體中,然後直接從記憶體中直接讀取資料,這樣會使這個程式響應速度變得非常快。但是一旦伺服器宕機,那麼記憶體中的資料將全部丟失?

如何解決上述問題呢?

我們第一時間肯定想到從後端資料庫中讀取資料,但是這樣會頻繁訪問資料庫,會給後端資料庫帶來巨大的壓力;還有就是資料從資料庫中讀取出來,效能肯定比不上 Redis 中讀取,導致使用這些資料的應用程式響應變慢,所以,對 Redis 來說,實現資料的持久化,避免從後端資料庫中進行恢復,是至關重要的。

目前 Redis 的持久化機制有兩種用 AOF(Append Only File)日誌和 RDB 快照。我們下面來介紹下這兩種機制

AOF 日誌

AOF 日誌是如何實現的?

AOF 是寫後日志,他是當 Redis 執行命令成功之後把相對應的操作日誌(命令記錄)寫道 AOF 日誌中。

AOF 為什麼要先執行命令在記日誌呢?

因為 AOF 日誌裡面記錄的是 Redis 執行命令的操作記錄,我們以 Redis 收到“set testkey testvalue”命令後記錄的日誌為例,看看 AOF 日誌的內容。其中,“*3”表示當前命令有三個部分,每部分都是由“$+數字”開頭,後面緊跟著具體的命令、鍵或值。這裡,“數字”表示這部分中的命令、鍵或值一共有多少位元組。例如,“$3 set”表示這部分有 3 個位元組,也就是“set”命令。

但是,為了避免額外的檢查開銷,Redis 在向 AOF 裡面記錄日誌的時候,並不會先去對這些命令進行語法檢查。所以,如果先記日誌再執行命令的話,日誌中就有可能記錄了錯誤的命令,Redis 在使用日誌恢復資料時,就可能會出錯。而寫後日志這種方式,就是先讓系統執行命令,只有命令能執行成功,才會被記錄到日誌中,否則,系統就會直接向客戶端報錯。所以,Redis 使用寫後日志這一方式的一大好處是,可以避免出現記錄錯誤命令的情況。

除此之外,AOF 還有一個好處:它是在命令執行後才記錄日誌,所以**不會阻塞當前的寫操作。****

AOF存在兩個潛在的風險?

1, 如果剛執行完一個命令,還沒有來得及記錄日誌就宕機了,那麼這個命令和相對應的資料就會有丟失的風險,如果此時 Redis 是作用快取,還可以從後端資料庫重新讀入資料進行恢復,但是,如果 Redis 是直接用作資料庫的話,此時,因為命令沒有記入日誌,所以就無法用日誌進行恢復了。

2,AOF 雖然避免了對當前命令的阻塞,但可能會給下一個操作帶來阻塞風險。這是因為,AOF 日誌也是在主執行緒中執行的,如果在把日誌檔案寫入磁碟時,磁碟寫壓力大,就會導致寫盤很慢,進而導致後續的操作也無法執行了。

上面這兩個風險不難看出都是和 AOF 記錄日誌的實際相關,所以我們可以控制 AOF 寫日誌的時機來進行規避的

AOF 的三種寫回策略

Always 同步寫回

每個寫命令執行完,立馬同步的將日誌寫回磁碟;可以做到基本上不丟資料,但是它在每一個寫命令後都有一個慢速的落盤操作,不可避免地會影響主執行緒效能;

Everysec 每秒寫回

每個寫命令執行完,只是先把日誌寫道 AOF 檔案的記憶體緩衝區,每隔一秒把緩衝區中的內容寫入磁碟。這種避免了同步寫回的效能開銷,雖然減少了對系統性能的影響,但是如果發生宕機,上一秒內未落盤的命令操作仍然會丟失。所以,只能算是在避免影響主執行緒效能和避免資料丟失兩者之間取了個折中。

No 作業系統控制寫回

每個命令執行完成後,只是先把日誌寫到 AOF 檔案的記憶體緩衝區,由作業系統決定何時將緩衝區內容寫回到磁碟。雖然“作業系統控制的寫回”在寫完緩衝區後,就可以繼續執行後續的命令,但是落盤的時機已經不在 Redis 手中了,只要 AOF 記錄沒有寫回磁碟,一旦宕機對應的資料就丟失了;

選擇哪種策略比較好?

其實這個問題沒有完全正確的答案,總結一下就是:想要獲得高效能,就選擇 No 策略;如果想要得到高可靠性保證,就選擇 Always 策略;如果允許資料有一點丟失,又希望效能別受太大影響的話,那麼就選擇 Everysec 策略。

如何設定策略
# appendfsync 由此配置項進行配置,預設為 Everysec 

AOF 日誌檔案太大了怎麼辦?

當我們 一直在記錄 AOF 檔案,AOF 檔案越來越大,因為 AOF 日誌檔案是追加的。就會造成下面的 效能問題:

1,檔案系統本身對檔案大小有限制,無法儲存過大的檔案;

2,如果檔案太大,之後再往裡面追加命令記錄的話,效率也會變低;

3,如果發生宕機,AOF 中記錄的命令要一個個被重新執行,用於故障恢復,如果日誌檔案太大,整個恢復過程就會非常緩慢,這就會影響到 Redis 的正常使用。

針對上面的問題,我們就需要不讓 AOF 檔案一直增長,Redis 中採用 AOF 重寫機制來規避上面這個問題

AOF 重寫機制是什麼?

簡單來說,AOF 重寫機制就是在重寫時,Redis 根據資料庫的現狀建立一個新的 AOF 檔案,也就是說,讀取資料庫中的所有鍵值對,然後對每一個鍵值對用一條命令記錄它的寫入。比如說,當讀取了鍵值對“testkey”: “testvalue”之後,重寫機制會記錄 set testkey testvalue 這條命令。這樣,當需要恢復時,可以重新執行該命令,實現“testkey”: “testvalue”的寫入。

AOF 重寫機制具有合併功能,所謂合併功能就是多條 AOF 日誌合併成一條。我們知道,AOF 檔案是以追加的方式,逐一記錄接收到的寫命令的。當一個鍵值對被多條寫命令反覆修改時,AOF 檔案會記錄相應的多條命令。但是,在重寫的時候,是根據這個鍵值對當前的最新狀態,為它生成對應的寫入命令。這樣一來,一個鍵值對在重寫日誌中只用一條命令就行了,而且,在日誌恢復時,只用執行這條命令,就可以直接完成這個鍵值對的寫入了。

當我們對一個列表先後做了 6 次修改操作後,列表的最後狀態是[“D”, “C”, “N”],此時,只用 LPUSH u:list “N”, “C”, "D"這一條命令就能實現該資料的恢復,這就節省了五條命令的空間。對於被修改過成百上千次的鍵值對來說,重寫能節省的空間當然就更大了。

不過,雖然 AOF 重寫後,日誌檔案會縮小,但是,要把整個資料庫的最新資料的操作日誌都寫回磁碟,仍然是一個非常耗時的過程。這時,我們就要繼續關注另一個問題了:重寫會不會阻塞主執行緒?

AOF 重寫會不會阻塞主執行緒?

和 AOF 日誌由主執行緒寫回不同,AOF 重寫過程是由後臺子程序 bgrewriteaof來完成的,也正是因為這個子程序,才避免了阻塞主執行緒,導致 Redis 效能下降。

把重寫的過程總結為“一個拷貝,兩處日誌

“一個拷貝”就是指,每次執行重寫時,主執行緒 fork 出後臺的 bgrewriteaof 子程序。此時,fork 會把主執行緒的記憶體拷貝一份給 bgrewriteaof 子程序,這裡面就包含了資料庫的最新資料。然後,bgrewriteaof 子程序就可以在不影響主執行緒的情況下,逐一把拷貝的資料寫成操作,記入重寫日誌。

“兩處日誌”又是什麼呢?

因為主執行緒未阻塞,仍然可以處理新來的操作。此時,如果有寫操作,第一處日誌就是指正在使用的 AOF 日誌,Redis 會把這個操作寫到它的緩衝區。這樣一來,即使宕機了,這個 AOF 日誌的操作仍然是齊全的,可以用於恢復。

而第二處日誌,就是指新的 AOF 重寫日誌。這個操作也會被寫到重寫日誌的緩衝區。這樣,重寫日誌也不會丟失最新的操作。等到拷貝資料的所有操作記錄重寫完成後,重寫日誌記錄的這些最新操作也會寫入新的 AOF 檔案,以保證資料庫最新狀態的記錄。此時,我們就可以用新的 AOF 檔案替代舊檔案了。

總結來說,每次 AOF 重寫時,Redis 會先執行一個記憶體拷貝,用於重寫;然後,使用兩個日誌保證在重寫過程中,新寫入的資料不會丟失。而且,因為 Redis 採用額外的執行緒進行資料重寫,所以,這個過程並不會阻塞主執行緒。

何時觸發 AOF 重寫

有兩個配置項在控制AOF重寫的觸發時機:

1, auto-aof-rewrite-min-size: 表示執行AOF重寫時檔案的最小大小,預設為64MB

2, auto-aof-rewrite-percentage: 這個值的計算方法是:當前AOF檔案大小和上一次重寫後AOF檔案大小的差值,再除以上一次重寫後AOF檔案大小。也就是當前AOF檔案比上一次重寫後AOF檔案的增量大小,和上一次重寫後AOF檔案大小的比值。

AOF檔案大小同時超出上面這兩個配置項時,會觸發AOF重寫。

3, bgrewriteaof: 也可以通過在命令列中執行這個命令,來手動觸發 AOF 重寫。

RDB 快照

我們思考一個問題? 當 redis 資料量大的時候(AOF重寫之後資料量還是很大)的情況下,如果我們 redis 宕機了之後,通過 AOF 檔案一條條在 redis 中執行恢復命令的時候,效率是那麼的高嗎?能很快的 AOF 裡面的資料恢復到 redis 中嗎?

答案是顯而易見的,因為 AOF 恢復資料需要一條一條的到 redis 裡面執行命令,他的效率必然沒有 那麼高?所以我們這裡面引用了 redis 的 RDB 快照功能,下面我們就來介紹下 RDB 快照。

什麼是 RDB 快照

就是把某一時刻的狀態以檔案的形式寫到磁碟上,也就是快照。這樣一來,即使宕機,快照檔案也不會丟失,資料的可靠性也就得到了保證。這個快照檔案就稱為 RDB 檔案,其中,RDB 就是 Redis DataBase 的縮寫。

和 AOF 相比,RDB 記錄的是某一時刻的資料,並不是操作,所以,在做資料恢復時,我們可以直接把 RDB 檔案讀入記憶體,很快地完成恢復。聽起來好像很不錯,但記憶體快照也並不是最優選項。為什麼這麼說呢?

我們還要考慮兩個關鍵問題:

  • 對哪些資料做快照?這關係到快照的執行效率問題;
  • 做快照時,資料還能被增刪改嗎?這關係到 Redis 是否被阻塞,能否同時正常處理請求。(快照儲存那些資料,儲存資料的時候還能被改變嗎)

給哪些記憶體資料做快照?

Redis 的資料都在記憶體中,為了提供所有資料的可靠性保證,它執行的是全量快照,也就是說,把記憶體中的所有資料都記錄到磁碟中,這就類似於給 100 個人拍合影,把每一個人都拍進照片裡。這樣做的好處是,一次性記錄了所有資料,一個都不少。

當你給一個人拍照時,只用協調一個人就夠了,但是,拍 100 人的大合影,卻需要協調 100 個人的位置、狀態,等等,這當然會更費時費力。同樣,給記憶體的全量資料做快照,把它們全部寫入磁碟也會花費很多時間。而且,全量資料越多,RDB 檔案就越大,往磁碟上寫資料的時間開銷就越大。

對於 Redis 而言,它的單執行緒模型就決定了,我們要儘量避免所有會阻塞主執行緒的操作,所以,針對任何操作,我們都會提一個靈魂之問:“它會阻塞主執行緒嗎?”RDB 檔案的生成是否會阻塞主執行緒,這就關係到是否會降低 Redis 的效能。

Redis 提供了兩個命令來生成 RDB 檔案,分別是 save 和 bgsave。

save:在主執行緒中執行,會導致阻塞;

bgsave:建立一個子程序,專門用於寫入 RDB 檔案,避免了主執行緒的阻塞,這也是 Redis RDB 檔案生成的預設配置。

好了,這個時候,我們就可以通過 bgsave 命令來執行全量快照,這既提供了資料的可靠性保證,也避免了對 Redis 的效能影響。

快照時資料能修改嗎?

在給別人拍照時,一旦對方動了,那麼這張照片就拍糊了,我們就需要重拍,所以我們當然希望對方保持不動。對於記憶體快照而言,我們也不希望資料“動”。

舉個例子。我們在時刻 t 給記憶體做快照,假設記憶體資料量是 4GB,磁碟的寫入頻寬是 0.2GB/s,簡單來說,至少需要 20s(4/0.2 = 20)才能做完。如果在時刻 t+5s 時,一個還沒有被寫入磁碟的記憶體資料 A,被修改成了 A’,那麼就會破壞快照的完整性,因為 A’不是時刻 t 時的狀態。因此,和拍照類似,我們在做快照時也不希望資料“動”,也就是不能被修改。

但是,如果快照執行期間資料不能被修改,是會有潛在問題的。對於剛剛的例子來說,在做快照的 20s 時間裡,如果這 4GB 的資料都不能被修改,Redis 就不能處理對這些資料的寫操作,那無疑就會給業務服務造成巨大的影響。

你可能會想到,可以用 bgsave 避免阻塞啊。這裡我就要說到一個常見的誤區了,避免阻塞和正常處理寫操作並不是一回事。此時,主執行緒的確沒有阻塞,可以正常接收請求,但是,為了保證快照完整性,它只能處理讀操作,因為不能修改正在執行快照的資料。

為了快照而暫停寫操作,肯定是不能接受的。所以這個時候,Redis 就會藉助作業系統提供的寫時複製技術(Copy-On-Write, COW),在執行快照的同時,正常處理寫操作。
簡單來說,bgsave 子程序是由主執行緒 fork 生成的,可以共享主執行緒的所有記憶體資料。bgsave 子程序執行後,開始讀取主執行緒的記憶體資料,並把它們寫入 RDB 檔案。

此時,如果主執行緒對這些資料也都是讀操作(例如圖中的鍵值對 A),那麼,主執行緒和 bgsave 子程序相互不影響。但是,如果主執行緒要修改一塊資料(例如圖中的鍵值對 C),那麼,這塊資料就會被複制一份,生成該資料的副本。然後,bgsave 子程序會把這個副本資料寫入 RDB 檔案,而在這個過程中,主執行緒仍然可以直接修改原來的資料。

這既保證了快照的完整性,也允許主執行緒同時對資料進行修改,避免了對正常業務的影響。到這裡,我們就解決了對“哪些資料做快照”以及“做快照時資料能否修改”這兩大問題:Redis 會使用 bgsave 對當前記憶體中的所有資料做快照,這個操作是子程序在後臺完成的,這就允許主執行緒同時可以修改資料。現在,我們再來看另一個問題:多久做一次快照?我們在拍照的時候,還有項技術叫“連拍”,可以記錄人或物連續多個瞬間的狀態。那麼,快照也適合“連拍”嗎?

可以每秒做一次快照嗎?

對於快照來說,所謂“連拍”就是指連續地做快照。這樣一來,快照的間隔時間變得很短,即使某一時刻發生宕機了,因為上一時刻快照剛執行,丟失的資料也不會太多。但是,這其中的快照間隔時間就很關鍵了。

如下圖所示,我們先在 T0 時刻做了一次快照,然後又在 T0+t 時刻做了一次快照,在這期間,資料塊 5 和 9 被修改了。如果在 t 這段時間內,機器宕機了,那麼,只能按照 T0 時刻的快照進行恢復。此時,資料塊 5 和 9 的修改值因為沒有快照記錄,就無法恢復了。

所以,要想盡可能恢復資料,t 值就要儘可能小,t 越小,就越像“連拍”。那麼,t 值可以小到什麼程度呢,比如說是不是可以每秒做一次快照?畢竟,每次快照都是由 bgsave 子程序在後臺執行,也不會阻塞主執行緒。

這種想法其實是錯誤的。雖然 bgsave 執行時不阻塞主執行緒,但是,如果頻繁地執行全量快照,也會帶來兩方面的開銷

1,頻繁將全量資料寫入磁碟,會給磁碟帶來很大壓力,多個快照競爭有限的磁碟頻寬,前一個快照還沒有做完,後一個又開始做了,容易造成惡性迴圈。

2,bgsave 子程序需要通過 fork 操作從主執行緒創建出來。雖然,子程序在建立後不會再阻塞主執行緒,但是,fork 這個建立過程本身會阻塞主執行緒,而且主執行緒的記憶體越大,阻塞時間越長。如果頻繁 fork 出 bgsave 子程序,這就會頻繁阻塞主執行緒了。

此時,我們可以做增量快照,所謂增量快照,就是指,做了一次全量快照後,後續的快照只對修改的資料進行快照記錄,這樣可以避免每次全量快照的開銷。

在第一次做完全量快照後,T1 和 T2 時刻如果再做快照,我們只需要將被修改的資料寫入快照檔案就行。但是,這麼做的前提是,我們需要記住哪些資料被修改了。你可不要小瞧這個“記住”功能,它需要我們使用額外的元資料資訊去記錄哪些資料被修改了,這會帶來額外的空間開銷問題。如下圖所示:

如果我們對每一個鍵值對的修改,都做個記錄,那麼,如果有 1 萬個被修改的鍵值對,我們就需要有 1 萬條額外的記錄。而且,有的時候,鍵值對非常小,比如只有 32 位元組,而記錄它被修改的元資料資訊,可能就需要 8 位元組,這樣的畫,為了“記住”修改,引入的額外空間開銷比較大。這對於記憶體資源寶貴的 Redis 來說,有些得不償失。

到這裡,你可以發現,雖然跟 AOF 相比,快照的恢復速度快,但是,快照的頻率不好把握,如果頻率太低,兩次快照間一旦宕機,就可能有比較多的資料丟失。如果頻率太高,又會產生額外開銷,那麼,還有什麼方法既能利用 RDB 的快速恢復,又能以較小的開銷做到儘量少丟資料呢?

混合使用 AOF 日誌和記憶體快照

Redis 4.0 中提出了一個 混合使用 AOF 日誌和記憶體快照 的方法。簡單來說,記憶體快照以一定的頻率執行,在兩次快照之間,使用 AOF 日誌記錄這期間的所有命令操作。這樣一來,快照不用很頻繁地執行,這就避免了頻繁 fork 對主執行緒的影響。而且,AOF 日誌也只用記錄兩次快照間的操作,也就是說,不需要記錄所有操作了,因此,就不會出現檔案過大的情況了,也可以避免重寫開銷。如下圖所示,T1 和 T2 時刻的修改,用 AOF 日誌記錄,等到第二次做全量快照時,就可以清空 AOF 日誌,因為此時的修改都已經記錄到快照中了,恢復時就不再用日誌了。

開啟混合使用 AOF 日誌和記憶體快照

aof-use-rdb-preamble yes

開啟 RDB

save 60 1000 #Redis 在滿足“ 60 秒內有至少有 1000 個鍵被改動”這一條件時, 自動儲存一次資料集

思考:

1,我們使用一個 2 核 CPU、4GB 記憶體、500GB 磁碟的雲主機執行 Redis,Redis 資料庫的資料量大小差不多是 2GB,我們使用了 RDB 做持久化保證。當時 Redis 的執行負載以修改操作為主,寫讀比例差不多在 8:2 左右,也就是說,如果有 100 個請求,80 個請求執行的是修改操作。你覺得,在這個場景下,用 RDB 做持久化有什麼風險嗎?

2核CPU、4GB記憶體、500G磁碟,Redis例項佔用2GB,寫讀比例為8:2,此時做RDB持久化,產生的風險主要在於 CPU資源 和 記憶體資源 這2方面:

a、記憶體資源風險:Redis fork子程序做RDB持久化,由於寫的比例為80%,那麼在持久化過程中,“寫實複製”會重新分配整個例項80%的記憶體副本,大約需要重新分配1.6GB記憶體空間,這樣整個系統的記憶體使用接近飽和,如果此時父程序又有大量新key寫入,很快機器記憶體就會被吃光,如果機器開啟了Swap機制,那麼Redis會有一部分資料被換到磁碟上,當Redis訪問這部分在磁碟上的資料時,效能會急劇下降,已經達不到高效能的標準(可以理解為武功被廢)。如果機器沒有開啟Swap,會直接觸發OOM,父子程序會面臨被系統kill掉的風險。

b、CPU資源風險:雖然子程序在做RDB持久化,但生成RDB快照過程會消耗大量的CPU資源,雖然Redis處理處理請求是單執行緒的,但Redis Server還有其他執行緒在後臺工作,例如AOF每秒刷盤、非同步關閉檔案描述符這些操作。由於機器只有2核CPU,這也就意味著父程序佔用了超過一半的CPU資源,此時子程序做RDB持久化,可能會產生CPU競爭,導致的結果就是父程序處理請求延遲增大,子程序生成RDB快照的時間也會變長,整個Redis Server效能下降。

c、另外,可以再延伸一下,老師的問題沒有提到Redis程序是否綁定了CPU,如果綁定了CPU,那麼子程序會繼承父程序的CPU親和性屬性,子程序必然會與父程序爭奪同一個CPU資源,整個Redis Server的效能必然會受到影響!所以如果Redis需要開啟定時RDB和AOF重寫,程序一定不要繫結CPU。