網上雖然已經有不少相似的介紹了,但我仍是本身總結概括了一下,自認爲內容和細節都是比較齊全的。html
文章篇幅有 4k 多字,貨有點幹,斷斷續續寫了好幾天,但願對你們有幫助。不出意外地話,從此會陸續更新 Redis 相關的文章,和你們一塊兒學習。redis
好了,下面開始迴歸正文:數據庫
Redis 一共有 2 種持久化方式,分別是 RDB 和 AOF,下面我來詳細介紹兩種方式在各個過程所作的事情,特色等等。編程
RDB 持久化是 Redis 默認的持久化方式。設計模式
它所生成的 RDB 文件是一個壓縮的二進制文件,經過該文件能夠還原生成 RDB 文件時的數據庫狀態緩存
PS:數據庫狀態是指 Redis 服務器的非空數據庫以及他們鍵值對的統稱安全
有兩個命令能夠生成 RDB 文件,一個是 SAVE、另外一個是 BGSAVE。服務器
二者的區別在於:前者會阻塞 Redis 服務器進程,直到 RDB 文件建立完畢爲止。 微信
而在服務器進程阻塞期間,服務器是不能處理任何命令請求的。數據結構
後者則不會阻塞服務器進程,由於是經過 fork 一個子進程,並讓其去建立 RDB 文件,而服務器進程(父進程)繼續則繼續處理命令請求。
當寫完數據庫狀態後,新 RDB 文件就會原子地替換舊的 RDB 文件。
此處小提問:若是在執行 BGSAVE 期間,客戶端發送 SAVE、BGSAVE 或 BGREWRITEAOF 命令給服務端,服務端會如何處理呢?答案:在執行 BGSAVE 期間,上述三個命令都不會被執行。
詳細緣由:前兩個會被直接拒絕,緣由是爲了不父子進程同時執行兩個 rdbSave 調用,防止產生競爭條件。
而 BGREWRITEAOF 命令則是會被延遲到 BGSAVE 命令執行以後再執行。
但若是是 BGREWRITEAOF 命令正在執行,此時客戶端發送 BGSAVE 命令則會被拒絕。
由於 BGREWRITEAOF 和 BGSAVE 都是由子進程執行的,因此在操做方面沒有衝突的地方,不能同時執行的緣由是性能上的考慮——併發出兩個子進程,而且這兩個子進程都會同時執行大量 io(磁盤寫入)操做
RDB 文件的載入是在服務器啓動時自動執行的,因此沒有用於載入的命令,期間阻塞主進程。
只要沒有開啓 AOF 持久化功能,在啓動時檢測到有 RDB 文件,就會自動載入。
當服務器有開啓 AOF 持久化功能時,服務器將會優先使用 AOF 文件來還原數據庫狀態。緣由是 AOF 文件的更新頻率一般比 RDB 文件的更新頻率高。
對於 RDB 持久化而言,咱們通常都會使用 BGSAVE 來持久化,畢竟它不會阻塞服務器進程。
在 Redis 的配置文件,有提供設置服務器每隔多久時間來執行 BGSAVE 命令。
Redis 默認是以下配置:
save 900 1 // 900 秒內,對數據庫至少修改 1 次。下面同理
save 300 10
save 60 10000
只要知足其中一種狀況,服務器就會執行 BGSAVE 命令。
咱們從上面的介紹知道,RDB 持久化經過保存數據庫狀態來持久化。而 AOF 與之不一樣,它是經過保存對數據庫的寫命令來記錄數據庫狀態。
好比執行了 set key 123
,Redis 就會將這條寫命令保存到 AOF 文件中。
在服務器下次啓動時,就能夠經過載入和執行 AOF 文件中保存的命令,來還原服務器關閉前的數據庫狀態了。
整體流程和 RDB 持久化同樣 —— 都是建立一個 xxx 文件、在服務器下次啓動時就載入這個文件來還原數據
那麼,AOF 持久化具體是怎麼實現的呢?
AOF 持久化功能的實現能夠分爲 3 個步驟:命令追加、文件寫入、文件同步
其中命令追加很好理解,就是將寫命令追加到 AOF 緩衝區的末尾。
那文件寫入和文件同步怎麼理解呢?剛開始我也一臉懵逼,終於在網上找到了答案,參考見文末,有興趣的讀者能夠去看看。
先不賣關子了,簡單一句話解釋就是:前者是緩衝區內容寫到 AOF 文件,後者是將 AOF 文件保存到磁盤。
ok,明白什麼意思以後,咱們稍微詳細看下這兩個東西是什麼鬼。
在《Redis設計與實現》中提到,Redis 服務器進程就是一個事件循環,這個循環中的文件事件(socket 的可讀可寫事件)負責接收客戶端的命令請求,以及向客戶端發送命令結果。
由於服務器在處理文件事件時,可能會發生寫操做,使得一些內容會被追加到 AOF 緩衝區末尾。因此,在服務器每次結束一個事件循環以前 ,都會調用 flushAppendOnlyFile 方法。
這個方法執行如下兩個工做:
兩個步驟都須要根據必定的條件來執行,而這些條件由 Redis 配置文件中的 appendfsync 選項來決定的,一共有三個選擇:
下面說下三個的區別:
appendfsync no:每次執行完一個命令以後, WRITE 會執行,SAVE 都會被忽略,只會在如下任意一種狀況中被執行:
而對於操做特性來分析的話,則是以下狀況:
模式 | WRITE 是否阻塞主進程 | SAVE 是否阻塞主進程 | 停機時丟失的數據量 |
---|---|---|---|
appendfsync always | 阻塞 | 阻塞 | 最多隻丟失一個命令的數據 |
appendfsync everysec | 阻塞 | 不阻塞 | 通常狀況下不超過 2 秒鐘的數據 |
appendfsync no | 阻塞 | 阻塞 | 操做系統最後一次對 AOF 文件觸發 SAVE 操做以後的數據 |
既然 AOF 持久化是經過保存寫命令到文件的,那隨着時間的推移,這個 AOF 文件記錄的內容就愈來愈多,文件體積也就愈來愈大,對其進行數據還原的時間也就愈來愈久。
針對這個問題,Redis 提供了 AOF 文件重寫功能。
經過該功能來建立一個新的 AOF 文件來代替舊文件。而且兩個文件所保存的數據庫狀態同樣,但新文件不會包含任何冗餘命令,因此新文件要比舊文件小得多。
而爲何新文件不會包含任何冗餘命令呢?
那是由於這個重寫功能是經過讀取服務器當前的數據庫狀態來實現的。雖然叫作「重寫」,但實際上並無對舊文件進行任何讀取修改。
好比舊文件保存了對某個 key 有 4 個 set 命令,通過重寫以後,新文件只會記錄最後一次對該 key 的 set 命令。所以說新文件不會包含任何冗餘命令
由於重寫涉及到大量 IO 操做,因此 Redis 是用子進程來實現這個功能的,不然將會阻塞主進程。該子進程擁有父進程的數據副本,能夠避免在使用鎖的狀況下,保證數據的安全性。
那麼這裏又會存在一個問題,子進程在重寫過程當中,服務器還在繼續處理命令請求,新命令可能會對數據庫進行修改,這會致使當前數據庫狀態和重寫後的 AOF 文件,所保存的數據庫狀態不一致。
爲了解決這個問題,Redis 設置了一個 AOF 重寫緩衝區。在子進程執行 AOF 重寫期間,主進程須要執行如下三個步驟:
當子進程結束重寫後,會向主進程發送一個信號,主進程接收到以後會調用信號處理函數執行如下步驟:
當函數執行完成後,主進程就繼續處理客戶端命令。
所以,在整個 AOF 重寫過程當中,只有在執行信號處理函數時纔會阻塞主進程,其餘時候都不會阻塞。
到目前爲止,Redis 的兩種持久化方式就介紹得差很少了。可能你會有疑惑,在實際項目中,我到底要選擇哪一種持久化方案呢?下面,我貼下官方建議:
一般,若是你要想提供很高的數據保障性,那麼建議你同時使用兩種持久化方式。若是你能夠接受災難帶來的幾分鐘的數據丟失,那麼你能夠僅使用 RDB。
不少用戶僅使用了 AOF,可是咱們建議,既然 RDB 能夠時不時的給數據作個完整的快照,而且提供更快的重啓,因此最好仍是也使用 RDB。
在數據恢復方面:
RDB 的啓動時間會更短,緣由有兩個:
注意:
上面說了 RDB 快照的持久化,須要注意:在進行快照的時候(save),fork 出來進行 dump 操做的子進程會佔用與父進程同樣的內存,真正的 copy-on-write,對性能的影響和內存的耗用都是比較大的。好比機器 8G 內存,Redis 已經使用了 6G 內存,這時 save 的話會再生成 6G,變成 12G,大於系統的 8G。這時候會發生交換;要是虛擬內存不夠則會崩潰,致使數據丟失。因此在用 redis 的時候必定對系統內存作好容量規劃。
目前,一般的設計思路是利用複製(Replication)機制來彌補 aof、snapshot 性能上的不足,達到了數據可持久化。即 Master 上 Snapshot 和 AOF 都不作,來保證 Master 的讀寫性能,而 Slave 上則同時開啓 Snapshot 和 AOF 來進行持久化,保證數據的安全性。
文章知識點有點多和雜,我總結一下,幫助他們回顧內容:
參考:
《redis設計與實現》
https://www.cnblogs.com/zhouj...
https://redisbook.readthedocs...
PS:本文原創發佈於微信公衆號「不僅Java」,堅持原創!後臺回覆如下關鍵字獲取經典必讀書籍:
Java、MySQL、Redis、Linux、mq、數據結構、設計模式、編程思想、架構