宕機後,Redis如何實現快速恢復?

對 Redis 來講,它實現相似照片記錄效果的方式,就是把某一時刻的狀態以文件的形式寫到磁盤上,也就是快照。這樣一來,即便宕機,快照文件也不會丟失,數據的可靠性也就獲得了保證。這個快照文件就稱爲 RDB 文件,其中,RDB 就是 Redis DataBase 的縮寫。ide


和 AOF 相比,RDB 記錄的是某一時刻的數據,並非操做,因此,在作數據恢復時,咱們能夠直接把 RDB 文件讀入內存,很快地完成恢復。聽起來好像很不錯,但內存快照也並非最優選項。爲何這麼說呢?性能


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

  • 對哪些數據作快照?這關係到快照的執行效率問題;操作系統

  • 作快照時,數據還能被增刪改嗎?這關係到 Redis 是否被阻塞,可否同時正常處理請求。線程

這麼說可能你還不太好理解,我仍是拿拍照片來舉例子。咱們在拍照時,一般要關注兩個問題:日誌

  • 如何取景?也就是說,咱們打算把哪些人、哪些物拍到照片中;blog

  • 在按快門前,要記着提醒朋友不要亂動,不然拍出來的照片就模糊了進程


給哪些內存數據作快照?圖片

Redis 的數據都在內存中,爲了提供全部數據的可靠性保證,它執行的是全量快照,也就是說,把內存中的全部數據都記錄到磁盤中,這就相似於給 100 我的拍合影,把每個人都拍進照片裏。這樣作的好處是,一次性記錄了全部數據,一個都很多。內存


當你給一我的拍照時,只用協調一我的就夠了,可是,拍 100 人的大合影,卻須要協調 100 我的的位置、狀態,等等,這固然會更費時費力。一樣,給內存的全量數據作快照,把它們所有寫入磁盤也會花費不少時間。並且,全量數據越多,RDB 文件就越大,往磁盤上寫數據的時間開銷就越大。


對於 Redis 而言,它的單線程模型就決定了,咱們要儘可能避免全部會阻塞主線程的操做,因此,針對任何操做,咱們都會提一個靈魂之問:「它會阻塞主線程嗎?」RDB 文件的生成是否會阻塞主線程,這就關係到是否會下降 Redis 的性能。


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

  • save:在主線程中執行,會致使阻塞;

  • bgsave:建立一個子進程,專門用於寫入 RDB 文件,避免了主線程的阻塞,這也是 Redis RDB 文件生成的默認配置。

好了,這個時候,咱們就能夠經過 bgsave 命令來執行全量快照,這既提供了數據的可靠性保證,也避免了對 Redis 的性能影響。


接下來,咱們要關注的問題就是,在對內存數據作快照時,這些數據還能「動」嗎? 也就是說,這些數據還能被修改嗎?這個問題很是重要,這是由於,若是數據能被修改,那就意味着 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 執行時不阻塞主線程,可是,若是頻繁地執行全量快照,也會帶來兩方面的開銷。


一方面,頻繁將全量數據寫入磁盤,會給磁盤帶來很大壓力,多個快照競爭有限的磁盤帶寬,前一個快照尚未作完,後一個又開始作了,容易形成惡性循環。


另外一方面,bgsave 子進程須要經過 fork 操做從主線程建立出來。雖然,子進程在建立後不會再阻塞主線程,可是,fork 這個建立過程自己會阻塞主線程,並且主線程的內存越大,阻塞時間越長。若是頻繁 fork 出 bgsave 子進程,這就會頻繁阻塞主線程了。那麼,有什麼其餘好方法嗎?


若是咱們對每個鍵值對的修改,都作個記錄,那麼,若是有 1 萬個被修改的鍵值對,咱們就須要有 1 萬條額外的記錄。並且,有的時候,鍵值對很是小,好比只有 32 字節,而記錄它被修改的元數據信息,可能就須要 8 字節,這樣的畫,爲了「記住」修改,引入的額外空間開銷比較大。這對於內存資源寶貴的 Redis 來講,有些得不償失。


到這裏,你能夠發現,雖然跟 AOF 相比,快照的恢復速度快,可是,快照的頻率很差把握,若是頻率過低,兩次快照間一旦宕機,就可能有比較多的數據丟失。若是頻率過高,又會產生額外開銷,那麼,還有什麼方法既能利用 RDB 的快速恢復,又能以較小的開銷作到儘可能少丟數據呢?Redis 4.0 中提出了一個混合使用 AOF 日誌和內存快照的方法。


簡單來講,內存快照以必定的頻率執行,在兩次快照之間,使用 AOF 日誌記錄這期間的全部命令操做。這樣一來,快照不用很頻繁地執行,這就避免了頻繁 fork 對主線程的影響。並且,AOF 日誌也只用記錄兩次快照間的操做,也就是說,不須要記錄全部操做了,所以,就不會出現文件過大的狀況了,也能夠避免重寫開銷。以下圖所示,T1 和 T2 時刻的修改,用 AOF 日誌記錄,等到第二次作全量快照時,就能夠清空 AOF 日誌,由於此時的修改都已經記錄到快照中了,恢復時就再也不用日誌了。

圖片

這個方法既能享受到 RDB 文件快速恢復的好處,又能享受到 AOF 只記錄操做命令的簡單優點,很有點「魚和熊掌能夠兼得」的感受,建議你在實踐中用起來。

相關文章
相關標籤/搜索