原文鏈接: 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 檔案開頭的“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 子行程的方式進行后臺重寫有兩個優點:
- 重寫期間服務器行程可以繼續處理請求,
- 子行程帶有服務器行程的資料副本,能充分利用作業系統提供的寫時復制機制從而提升效率,還可以在避免使用鎖的情況下保證資料的安全性,
天下沒有免費的午餐,這種方式還帶來一個問題,就是在使用子行程重寫期間,如果父行程還在處理著客戶端請求,如何保證重寫后 AOF 檔案資料的一致性呢?
對于這個問題,Redis 設定了一個 AOF 重寫緩沖區,在子行程被創建后,Redis 服務器就會啟用這個重寫緩沖區,在將命令以協議格式追加到 AOF 緩沖區之后,同時也會追加到 AOF 重寫緩沖區,
當子行程完成重寫作業后會向父行程發送一個信號,父行程接收到信后之后會進行呼叫相關函式,進行以下作業:
- 將 AOF 重寫緩沖區中的內容寫入到新的 AOF 檔案中,
- 對新的 AOF 檔案進行改名,原子地覆寫現有的 AOF 檔案,完成新舊檔案的替換,
這時,就完成了一次 AOF 后臺重寫,
總結
通過前文內容,我們可以大致清楚 Redis 所提供的 RDB 和 AOF 兩種持久化機制的程序以及基本原理,它們各有特點,也各有適合使用的場景所以并不能說誰一定比誰好,通過搭配使用,能夠確保線上環境資料的安全性就是最好的,
轉載請註明出處,本文鏈接:https://www.uj5u.com/shujuku/286068.html
標籤:NoSQL
上一篇:HTAP | MySQL 到 ClickHouse 的高速公路
下一篇:附近的人?你zao嗎?
