Redis 的持久化


原文鏈接: https://www.changxuan.top/?p=1386


 

Redis 是一個非關系型的內存數據庫,使用內存存儲數據是它能夠進行快速存取數據的原因之一。

在實際應用中,常有人提倡把 Redis 只作為一種能夠提高用戶體驗的組件來使用, 也就是說即使 Redis 服務掛掉之后也要保證系統正常使用。不過,在很多系統中還是希望既能發揮 Redis 基於內存快速存取的特性,又希望機器斷電或 Redis服務停止后數據不丟失。所以,才引出了 Redis 的持久化功能。

在許多技術文章中,提到 Redis 的持久化時往往都會直接拋出兩個名詞 RDB 和 AOF。然后接下來就是分別介紹這兩個名詞。當然,如果要談 Redis 的持久化肯定避免不了講 RDB 和 AOF,但這是介紹持久化最恰當的方式嗎?這樣的文章是不是顯得有些生硬呢?所以,在嘗試弄明白一個事物的原理時一定要從頭到尾的思考它存在的意義?為了解決什么問題?采用了什么方式?達到了什么目的?自己有沒有其它的方案?這樣從問題的源頭切入,才能對這個事物理解的更加深刻,從而能夠更好的幫助自己進行舉一反三。而不是人雲亦雲,對於一些知識僅僅是背誦下來,這種死記硬背下來的知識在腦海里的保質期也是短的可憐。

在前面,我們已經提到為什么需要引入持久化?簡單的來說持久化就是把內存中的數據存儲到外存上,這樣服務停止后,當再啟動的時候就可以把外存的數據讀取到內存中從而達到了不丟失數據的目的。

RDB

如果讓你設計一個持久化的方案,你會怎么做呢?(假裝絞盡腦汁… …)首先,我們可以使用一種簡單的策略,將 Redis 中所有的數據按照一定格式全部寫到磁盤上,即創建數據的快照文件。然后,你為了盡量保證不丟數據需要考慮使用實時寫還是定時寫,又或者用其它策略。其實,現在的你已經在嘗試着去實現 RDB (Redis Database)持久化的機制了。所以,你看它其實並不難。萬丈高樓從地起,先從一個簡單的 idea 開始,逐漸去完善它,豐富它的過程便是解決問題的過程。例如用這種思路去學習計算機網絡也是同樣適用的,你可以給自己出一個問題“如何讓兩台電腦進行通信?”,自己想辦法解決這個問題的過程肯定會比在計算機網絡課堂上收獲的知識更多,也更牢固。

盡管不需要我們寫代碼來實現 RDB 持久化,但是並不妨礙我們來思考一下假如讓我們來實現的話大概會遇到哪些問題?例如:什么時候生成數據快照?文件數據格式的定義?如果在主進程中進行持久化,阻塞客戶端的請求后會不會有影響?接下來,我們就看一下 RDB 是如何做的吧。

基本命令

在 Redis 中,提供了兩個 RDB 持久化的命令: SAVE 和 BGSAVE 。執行 SAVE 時,Redis 服務會停止處理任何客戶端的命令請求;執行 BGSAVE 時,Redis 服務則會創建一個子進程,由子進程來負責數據的持久化,而此時 Redis 服務就可以正常處理客戶端的請求。

BGSAVE 解決了我們對於持久化時是否會影響 Redis 服務處理客戶端的請求的擔心。

自動間隔性保存

自動間隔性保存,則解決了“什么時候生成數據快照?”的問題。在 Redis 的配置文件中我們可以寫入以下配置:

save 600 1
save 300 10
save 60 100
save 30 1000

上面的配置表示,如果在 600 秒內對數據庫進行了 1 次修改,就執行執行一次 BGSAVE 命令;如果在 300 秒內對數據庫進行了 10 次修改,就執行一次 BGSAVE 命令;以此類推。你可以根據你的業務場景,配置 save 的參數,也不僅僅局限於 4 條配置。

實現原理

在 Redis 啟動時,會把上述配置存儲到 Redis 服務器的狀態中,具體的結構體則是 redisServer,存儲 save 參數的結構體為 saveparam。

 1 // Redis 服務器狀態信息結構體
 2 struct redisServer {
 3     // ... ...
 4  
 5     // 記錄多個 save 配置參數
 6     struct saveparam *saveparams;
 7     // 修改次數計數器
 8     long long dirty;
 9     // 上次執行保存的時間
10     time_t lastsave;
11  
12     // ... ...
13 }
14 // Save 參數結構體 saveparam
15 struct saveparam {
16     // 秒數
17     time_t seconds;
18     // 修改數
19     int changes;
20 }

 

看到上面 redisServer 結構體的屬性信息,你心里應該有答案了吧?dirty 表示的是自從上次執行 SAVE 或者 BGSAVE 命令完成之后對數據庫進行修改的次數;lastsave 表示的是上次成功執行SAVE 或者 BGSAVE 命令的時間。這個時候,如果再有個機制能夠定時檢查是否有滿足條件的配置參數就可以了。

Redis 提供了一個周期性操作函數 serverCron,每 100 ms 會執行一次。它其中的一項工作就是來檢查是否有符合條件的 save 參數,如果存在符合條件的參數則執行 BGSAVE 命令,執行完畢之后將 dirty 和 lastsave 的值重置。相信只要有基礎的編程知識,根據這些變量就能實現這個檢查的過程吧。

文件結構

RDB 文件結構示意圖

在上圖中,大寫字母的單詞表示的常量,小寫字母單詞則是變量和數據。RDB 文件開頭的“REDIS”是我們習慣稱為的魔數,類似於 class 文件的 COFFEE,用來識別文件類型;緊接着長度為四個字節的 db_version 記錄的是 RDB 文件的版本號;database 表示的是所存儲的數據;EOF 則表明數據內容結束了;check_sum 的值是整個文件的校驗和,用來檢查文件是否損壞。

AOF

其實持久化數據除了 RDB 這種方式,肯定會有同學能想到另一種方式,就是把服務端執行的所有客戶端請求增加、修改和刪除等會改變數據的命令全都存儲起來。通過存儲這些命令數據,在遇到機器宕機和服務進程異常中斷的情況下重啟服務時只要執行一遍這些持久化的命令即可恢復之前的數據了。(也是一個相當好的辦法呀!)

原理就是如此,那么問題來了,假如同樣讓你來實現這個過程,你會考慮到哪些問題呢?

一是性能問題,執行完命令之后是否直接將此命令持久化到磁盤上還是由操作系統控制文件同步?在這個問題上如何做取舍?二是文件大小問題,隨着 Redis 服務運行越來越久,數據文件勢必會越來越大?應該使用什么辦法解決?… …

我們來看下 Redis 的 AOF 的過程吧!

持久化過程

首先,通過在配置文件中增加一行配置 appendonly yes 來開啟 AOF 持久化。

像 RDB 機制所依賴 redisServer 結構體中的 saveparams、dirty、lastsave 參數一樣,AOF 的實現依賴 redisServer 結構體中的 aof_buf 參數。

1 struct redisServer{
2     // ... ...
3  
4     // AOF 緩沖區
5     sds aof_buf;
6  
7     // ... ...
8 }

 

aof_buf 參數用來以協議格式緩存會對數據進行變更的命令。

在 Redis 服務器執行完命令,並將命令以協議的格式追加到 aof_buf 緩沖區之后,在當前這個事件循環結束之前,Redis 還會調用一個函數 flushAppendOnlyFile,這個函數會根據配置文件中 appendfsync 的值來決定接下來的持久化行為。appendfsync 有三個可選值,分別是 always、everysec、no

  • always: 將 aof_buf 緩沖區中的內容寫入並同步到 AOF 文件。(性能最低,安全最高)
  • everysec: 將 aof_buf 緩沖區中的內容寫入到 AOF 文件,如果上次同步 AOF 文件的時間距離現在超過一秒鍾,那么再次對 AOF 文件同步,並且這個同步是由一個線程專門負責的。(同時兼顧性能與安全,推薦)
  • no: 將 aof_buf 緩沖區中的內容寫入到 AOF 文件,但並不負責對 AOF 文件的同步,把同步的控制權交由操作系統控制。(性能最高,安全最低)

以上就是 AOF 持久化的基本過程。

數據載入

由於命令數據是以協議格式存儲至文件中的,所以在啟動 Redis 服務時檢測到 AOF 文件的存在后會啟動載入程序。(如果 RDB 和 AOF 持久化的文件同時存在則會優先載入 AOF 文件數據)

啟動載入程序后,其載入過程如下圖所示:

AOF 重寫

在前面,我們提到 AOF 的這種機制會造成 AOF 數據文件越來越大,並且可能會存在許多無意義的命令。例如,先執行了一個命令 set chang xuan ,隨后又執行了命令 del chang 。其實這兩條語句都會被持久化到 AOF 文件中,但實際上除了能證明曾經執行過這兩條命令之外對於我們要持久化數據的目的而言並沒有什么作用。

對此,Redis 提供了 AOF 重寫的機制。

Redis 的 AOF 重寫其實是根據當前存儲的數據,生成命令的過程。並且會采用一些策略盡量減小 AOF 文件的大小,例如對於 List 中的數據會盡量使用較少的命令操作較多的數據。當然,如果在當前進程中進行重寫處理並且數據量特別大的情況下肯定會阻塞客戶端的請求,所以和 RDB 一樣,Redis 提供了 AOF 后台重寫的機制。

后台重寫(BGREWRITEAOF)

AOF 通過 fork 子進程的方式進行后台重寫有兩個優點:

  1. 重寫期間服務器進程可以繼續處理請求。
  2. 子進程帶有服務器進程的數據副本,能充分利用操作系統提供的寫時復制機制從而提升效率,還可以在避免使用鎖的情況下保證數據的安全性。

天下沒有免費的午餐,這種方式還帶來一個問題。就是在使用子進程重寫期間,如果父進程還在處理着客戶端請求,如何保證重寫后 AOF 文件數據的一致性呢?

對於這個問題,Redis 設置了一個 AOF 重寫緩沖區。在子進程被創建后,Redis 服務器就會啟用這個重寫緩沖區。在將命令以協議格式追加到 AOF 緩沖區之后,同時也會追加到 AOF 重寫緩沖區。

當子進程完成重寫工作后會向父進程發送一個信號。父進程接收到信后之后會進行調用相關函數,進行以下工作:

  1. 將 AOF 重寫緩沖區中的內容寫入到新的 AOF 文件中。
  2. 對新的 AOF 文件進行改名,原子地覆蓋現有的 AOF 文件,完成新舊文件的替換。

這時,就完成了一次 AOF 后台重寫。

總結

通過前文內容,我們可以大致清楚 Redis 所提供的 RDB 和 AOF 兩種持久化機制的過程以及基本原理。它們各有特點,也各有適合使用的場景所以並不能說誰一定比誰好。通過搭配使用,能夠確保線上環境數據的安全性就是最好的。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM