你管這破玩意叫 RDB

**我是個 redis 服務,**我馬上就要掛了

我得想個辦法,時不時把數據複製到硬盤上保存起來。

我把這個偉大的計劃,稱爲持久化計劃

停下手頭的工作


我首先想到的最簡單的辦法,就是先拒絕新來的命令,開始將內存中的數據複製到硬盤。

等拷貝完成後,再開始接受新命令。

這樣可以保證我拷貝的時候,沒有新來的命令修改內存,也即保證了時點性

簡單說就是,我保存的是某一時刻的 Redis 內存狀態。

但這樣每次持久化都要阻塞客戶端命令,肯定要被罵。

不停止手頭的工作


這好辦,那我就不停止手頭的工作唄,一邊接受命令,一邊做持久化,如下。

這樣效率提高了不少,持久化不再阻塞客戶端執行命令了。

但是,你有沒有注意到,內存中,某一時刻的數據,只有三種情況:

閃客 18 低併發編程

低兄 18 低併發編程

低兄 18 求三連

而此時硬盤中持久化的數據是:

閃客 18 求三連

它無法表示任何一個時刻的內存數據。

那這樣的快照就失去了意義,也即沒有保證時點性。

這顯然也是不行的。

先複製一份內存


這可咋辦呢?

停止手頭的工作可以保證時點性,但阻塞了客戶端。

不停止手頭的工作,雖然不阻塞客戶端,但又無法保證時點性。

真令人頭大啊。

抓了一會頭皮後,我冷靜了下來,開始分析。

時點性是必須保證的,否則快照就沒有了意義,那就只能嘗試將阻塞客戶端的時間變短一點了

之前的阻塞客戶端時間,是消耗在持久化,也就是內存拷貝到硬盤這個過程。

優化一下,先從內存中拷貝一份到另一塊內存空間,然後再對這塊新的內存空間進行持久化。

這樣,持久化的過程不耽誤客戶端命令,同時不受客戶端命令影響,保證了時點性。

而阻塞客戶端的時間,僅僅是內存與內存之間拷貝一份數據的時間,相比於整個持久化過程,可以忽略不計。

完美!

帶着這套完美的方案,我去找我的主人邀功了。

寫時複製


我:主人,我做好持久化方案啦!

主人:嗯我看看... 哎呀,把內存複製一份,這個想法很好,但是差了點火候呀,你對操作系統瞭解的還不夠深入。

我:啊,爲啥呢?

主人:你想想看,你現在的目的,就是爲了讓持久化和處理客戶端命令的這兩個過程所用到的內存空間隔離開,是不是?

我:嗯嗯是的。

主人:對呀,那其實你只需要新建一個進程去做持久化的過程即可,不同進程之間的內存是隔離的,也就是新建一個進程,會將原有進程的內存空間完全拷貝一份新的。

我:啊,那這不是和我自己複製一份內存一樣嘛,耗時差不多吧?

主人:我剛剛的圖只是給用戶的感覺是這樣的,實際上,linux 採用了寫時複製技術,在 fork 出子進程時並沒有立刻將內存進行拷貝,僅僅是拷貝了一份映射關係,讓它們暫時指向同一個內存空間。

主人:而當父子進程對這塊內存空間進行寫操作時,纔會真正複製內存,而且是以頁爲單位。

我:原來如此,也就是說,我可以利用操作系統的進程的寫時複製內存的原理,來代替我自己複製全部內存這個方案,因爲持久化過程,對內存的寫操作想來也不會特別多,大多數值都是不變的,所以這樣就提高了效率。

主人:是的,正是如此。

我:妙呀!

我趕緊把方案修改了,要持久化時我就 fork 一個子進程去做這件事,由操作系統的進程內存隔離的特徵替我保證時點性,寫時複製原理替我保證效率,也就是減少客戶端阻塞時間,僞代碼大概是這個樣子。

void rdbSaveBackground() {
    // 子進程處理(利用了操作系統的寫時複製技術)
    if ((childpid = fork()) == 0) {
        // 落盤主方法
        rdbSave();
    }
}

完美!

還沒定結構呢


剛剛光顧着想持久化的過程了,還沒定寫到磁盤中的數據格式呢。

那就定一個唄。

假如我的 Redis 內存只有一條數據,是通過下面的命令寫入的:

set dibingfa niubi

那持久化後落到磁盤中的 rdb 文件將會是這個樣子。

好了,大功告成,我再也不用擔心自己掛了,會有人幫我從持久化文件中恢復我的內存數據的。

但沒來得及持久化的,我就不管了。

具體什麼時候進行一次持久化,我給主人留了一個配置

save m n

表示 m 秒內數據集存在 n 次修改時,自動觸發一次持久化。

主人也可以配置多個這樣的配置項。

而我也好心給主人配了個默認的配置項,並寫了段註釋。

# Save the DB on disk:
#   In the example below the behaviour will be to save:
#   after 900 sec (15 min) if at least 1 key changed
#   after 300 sec (5 min) if at least 10 keys changed
#   after 60 sec if at least 10000 keys changed
save 900 1
save 300 10
save 60 10000

我想以主人的英文水平,應該可以讀得懂。

好啦,這回真的是大功告成了!

這個破玩意,我給起個名字,就叫 RDB

沒什麼特別的含義,其實就是用我的名字作爲開頭,Redis DB 而已。

後記

rdb 持久化過程,也可以手動觸發,即直接輸入 bgsave,同自動觸發完全一樣。

其在 redis 的源碼中,叫做 bgsaveCommand 方法。

整個源碼非常簡單易讀,只是干擾項很多罷了。

我將干擾項全部去掉,整理了一份 RDB 源碼簡潔版,並配上了我魔性的註釋,製作了一份精緻的小 pdf。

本來想着讓大家分享到朋友圈獲取,但想想看算了,加我好友(公衆號低****併發編程 - 菜單欄 - 關於),即可獲取這個精緻的小 PDF。

本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源https://mp.weixin.qq.com/s/GXnNSLqiAN0HDQMYE0yhug