一、為什么需要持久化
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()出子行程進行作業,
為什么這么搞?
我猜測原因如下:
-
如果支持并行存在多個子行程,那么不僅會拉低服務器性能,還會造成資料問題,比如八點的bgsave在作業,九點又來個bgsave命令,這時候九點的先執行完了,八點的后執行完了,那九點的不白執行了嗎?這是我所謂的資料問題,再比如,都沒執行完,十點又開一個bgsave,越積越多,服務器性能被拉低,
-
那為什么不阻塞?判斷有子行程在作業,就等待,等他執行完我在上場,那一樣,越積越多,檔案過大,只會造成堆積,
2、如果沒有copyonwrite這種技術是什么效果?
-
假設是全量復制,那么記憶體空間直接減半,浪費資源不說,資料量10g,全量復制這10g的時間也夠長的,這誰頂得住?
-
如果不全量復制,會是怎樣?相當于我一邊復制,你一邊寫資料,看著貌似問題不大,其實不然,比如現在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,佩服三連!!!
轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/178810.html
標籤:Java
下一篇:Java 給PDF檔案添加頁碼
