徹底搞懂Redis持久化之RDB原理

一、爲什麼需要持久化

redis裏有10gb數據,突然停電或者意外宕機了,再啓動的時候10gb都沒了?!所以需要持久化,宕機後再通過持久化文件將數據恢復。

二、優缺點

1、rdb文件

rdb文件都是二進制,很小。比如內存數據有10gb,rdb文件可能就1gb,只是舉例。

2、優點

  • 由於rdb文件都是二進制文件,所以很小,在災難恢復的時候會快些。
  • 他的效率(主進程處理命令的效率,而不是持久化的效率)相對於aof要高(bgsave而不是save),因爲每來個請求他都不會處理任何事,只是bgsave的時候他會fork()子進程且可能copyonwrite,但copyonwrite只是一個尋址的過程,納秒級別的。而aof每次都是寫盤操作,毫米級別。沒法比。

3、缺點

數據可靠性比aof低,也就是會丟失的多。因爲aof可以配置每秒都持久化或者每個命令處理完就持久化一次這種高頻率的操作,而rdb的話雖然也是靠配置進行bgsave,但是沒有aof配置那麼靈活,也沒aof持久化快,因爲rdb每次全量,aof每次只追加。

三、RDB持久化的兩種方法

配置文件也可以配置觸發rdb的規則。配置文件配置的規則採取的是bgsave的原理。

1、save

1.1、描述

同步、阻塞

1.2、缺點

致命的問題,持久化的時候redis服務阻塞(準確的說會阻塞當前執行save命令的線程,但是redis是單線程的,所以整個服務會阻塞),不能繼對外提供請求,GG!數據量小的話肯定影響不大,數據量大呢?每次複製需要1小時,那就相當於停機一小時。

2、bgsave

2.1、描述

異步、非阻塞

2.2、原理

fork() + copyonwrite

2.3、優點

他可以一邊進行持久化,一邊對外提供讀寫服務,互不影響,新寫的數據對我持久化不會造成數據影響,你持久化的過程中報錯或者耗時太久都對我當前對外提供請求的服務不會產生任何影響。持久化完會將新的rdb文件覆蓋之前的。
四 三、fork()

bgsave原理是fork() + copyonwrite,那麼現在來聊一下fork()

1、fork()是什麼

fork()是unix和linux這種操作系統的一個api,而不是Redis的api。

2、fork()有什麼用

fork()用於創建一個子進程,注意是子進程,不是子線程。fork()出來的進程共享其父類的內存數據。僅僅是共享fork()出子進程的那一刻的內存數據,後期主進程修改數據對子進程不可見,同理,子進程修改的數據對主進程也不可見。比如:A進程fork()了一個子進程B,那麼A進程就稱之爲主進程,這時候主進程子進程所指向的內存空間是同一個,所以他們的數據一致。但是A修改了內存上的一條數據,這時候B是看不到的,A新增一條數據,刪除一條數據,B都是看不到的。而且子進程B出問題了,對我主進程A完全沒影響,我依然可以對外提供服務,但是主進程掛了,子進程也必須跟隨一起掛。這一點有點像守護線程的概念。Redis正是巧妙的運用了fork()這個牛逼的api來完成RDB的持久化操作。

五、Redis中的fork()

Redis巧妙的運用了fork()。當bgsave執行時,Redis主進程會判斷當前是否有fork()出來的子進程,若有則忽略,若沒有則會fork()出一個子進程來執行rdb文件持久化的工作,子進程與Redis主進程共享同一份內存空間,所以子進程可以搞他的rdb文件持久化工作,主進程又能繼續他的對外提供服務,二者互不影響。我們說了他們之後的修改內存數據對彼此不可見,但是明明指向的都是同一塊內存空間,這是咋搞得?肯定不可能是fork()出來子進程後順帶複製了一份數據出來,如果是這樣的話比如我有4g內存,那麼其實最大有限空間是2g,我要給rdb留出一半空間來,扯淡一樣!那他咋做的?採取了copyonwrite技術。

六、copyonwrite

很簡單,現在不就是主進程和子進程共享了一塊內存空間,怎麼做到的彼此更改互不影響嗎?

1、原理

主進程fork()子進程之後,內核把主進程中所有的內存頁的權限都設爲read-only,然後子進程的地址空間指向主進程。這也就是共享了主進程的內存,當其中某個進程寫內存時(這裏肯定是主進程寫,因爲子進程只負責rdb文件持久化工作,不參與客戶端的請求),CPU硬件檢測到內存頁是read-only的,於是觸發頁異常中斷(page-fault),陷入內核的一箇中斷例程。中斷例程中,內核就會把觸發的異常的頁複製一份(這裏僅僅複製異常頁,也就是所修改的那個數據頁,而不是內存中的全部數據),於是主子進程各自持有獨立的一份。
數據修改之前的樣子
在這裏插入圖片描述
數據修改之後的樣子
在這裏插入圖片描述

2、回到原問題

其實就是更改數據的之前進行copy一份更改數據的數據頁出來,比如主進程收到了set k 1請求(之前k的值是2),然後這同時又有子進程在rdb持久化,那麼主進程就會把k這個key的數據頁拷貝一份,並且主進程中k這個指針指向新拷貝出來的數據頁地址上,然後進行更改值爲1的操作,這個主進程k元素地址引用的新拷貝出來的地址,而子進程引用的內存數據k還是修改之前的。

3、一段話總結

copyonwritefork()出來的子進程共享主進程的物理空間,當主子進程有內存寫入操作時,read-only內存頁發生中斷,將觸發的異常的內存頁複製一份(其餘的頁還是共享主進程的)。

4、額外補充

在 Redis 服務中,子進程只會讀取共享內存中的數據,它並不會執行任何寫操作,只有主進程會在寫入時纔會觸發這一機制,而對於大多數的 Redis 服務或者數據庫,寫請求往往都是遠小於讀請求的,所以使用fork()加上寫時拷貝這一機制能夠帶來非常好的性能,也讓BGSAVE這一操作的實現變得很簡單。

七、疑問

0、調用fork()也會阻塞啊

我只能說沒毛病,但是這個阻塞真的可以忽略不計。尤其是相對於阻塞主線程的save。

1、會同時存在多個子進程嗎?

不會,主進程每次收到bgsave命令需要fork()子進程之前都會判斷是否存在子進程了,若存在也會忽略掉這次bgsave請求。若不存在我會fork()出子進程進行工作。
爲什麼這麼搞?
我猜測原因如下:
1.如果支持並行存在多個子進程,那麼不僅會拉低服務器性能,還會造成數據問題,比如八點的bgsave在工作,九點又來個bgsave命令。這時候九點的先執行完了,八點的後執行完了,那九點的不白執行了嗎?這是我所謂的數據問題。再比如,都沒執行完,十點又開一個bgsave,越積越多,服務器性能被拉低。
2.那爲什麼不阻塞?判斷有子進程在工作,就等待,等他執行完我在上場,那一樣,越積越多,文件過大,只會造成堆積。

2、如果沒有copyonwrite這種技術是什麼效果?

1.假設是全量複製,那麼內存空間直接減半,浪費資源不說,數據量10g,全量複製這10g的時間也夠長的。這誰頂得住?2.如果不全量複製,會是怎樣?相當於我一邊複製,你一邊寫數據,看着貌似問題不大,其實不然。比如現在Redis裏有k1的值是1,k2的值是
2,比如bgsave了,這時候rdb寫入了k1的值,在寫k2的值之前時,有個客戶端請求

set k1 11 
set k2 22

那麼持久化進去的是k2 22,但是k1的值還是1,而不是最新的11,所以會造成數據問題,所以採取了copyonwrite技術來保證觸發bgsave請求的時候無論你怎麼更改,都對我rdb文件的數據持久化不會造成任何影響。

八、總結

此篇都是重點,廢話很少。沒啥可總結的。Redis作者對底層操作系統瞭解的很多,先是epoll,又是現在的fork()和copyonwrite。佩服三連!!!

九、個人公衆號

微信公衆號【Java碼農社區】
在這裏插入圖片描述

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章