Redis 中 bgsave 方式持久化的細節問題


因爲有小夥伴問Redis的bgsave命令裏面,cow(copy on write)到底是如何實現的,所以順便複習一下RDB相關的知識點。

1.RDB的基本概念

Redis有兩種數據持久化的方式:AOF和RDB。

簡單來說,AOF是記錄數據增量的方式,將每次對服務器寫的操作存入日誌(類似MySQL的binlog);而RDB是記錄全量數據,根據指定的時間間隔對數據進行快照存儲,以二進制格式文件(後綴RDB)保存在硬盤當中。

2.RDB的觸發方式

2-1、配置文件

最常見的使用RDB進行持久化的方式,是在配置文件中配置Redis進行快照保存的時機:

save [seconds] [changes]

意爲在[seconds]秒內如果發生了[changes]次數據修改,則進行一次RDB快照保存,例如

save 60 100

可以配置多條save指令,讓Redis執行多級的快照保存策略。

Redis默認開啓RDB快照,默認的RDB策略如下:

save 900 1
save 300 10
save 60 10000

2-2、手工觸發

也可以直接使用手工命令的方式觸發RDB生成快照文件。

一種是 save 命令

redis> save
OK

save 命令是同步方式生成快照,會造成Redis阻塞,所有後續到達的命令要等待save完成以後才能執行。

另一種是 bgsave 命令

redis> bgsave
Background saving started

bgsave 命令採用異步方式生成快照,Redis會fork出一個子進程進行RDB文件的生成。

Redis只有在fork子進程時被阻塞,子進程完成快照生成的同時,Redis可以正常工作。

2-3、其他觸發方式

  • 主從複製時,自動生成RDB文件
  • Redis中的debug reload提供debug級別的重啓(不清空內存),此時自動生成RDB文件
  • shutdown會自動生成RDB文件

3.bgsave的工作流程

重點說一下 bgsave 是如何使用異步方式生成快照的。

一般資料提到這裏的時候都是一句話帶過,說Redis創建子進程以後,利用cow方式完成快照文件的生成。這沒有錯,但是大多數都沒說清楚這個cow是如何工作的。

我甚至在一些博客上看到“fork消耗額外內存”、“fork時對內存的消耗比較大”這樣的說法。

這其實是沒有理解清楚Redis fork出來的子進程是如何工作的。

3-1、什麼是cow

cow = copy on write

這是一種簡單的讀寫分離思想,適用於讀多寫少的併發場景。比如黑白名單,熱點文章等等。

正常情況下我們說cow,指的是修改共享資源時,將共享資源copy一份,加鎖後修改,再將原容器的引用指向新的容器。

對於java來說,是有線程的cow容器的,比如CopyOnWriteArrayList。

另外就是cow保證的是最終一致性而不是強一致。

3-2、Redis面臨的問題

在Redis生成快照這個問題上,顯然不能直接使用標準的cow流程來操作。

很簡單,這會導致Redis的可用內存容量就直接減半。

cow的第一步是要將Redis在內存中的內容copy一份副本;然後主進程操作原數據,進行正常的讀寫操作,子進程利用副本專心寫盤,寫完以後銷燬子進程。

真的直接copy一份副本的話,多少內存夠用啊?這不是簡單的copy一個java容器那麼簡單。

3-3、Redis的cow

  1. Redis創建子進程以後,根本不進行數據的copy,主進程與子線程是共享數據的。主進程繼續對外提供讀寫服務。
  2. 雖然不copy數據,但是kernel會把主進程中的所有內存頁的權限都設爲read-only,主進程和子進程訪問數據的指針都指向同一內存地址。
  3. 主進程發生寫操作時,因爲權限已經設置爲read-only了,所以會觸發頁異常中斷(page-fault)。在中斷處理中,需要被寫入的內存頁面會複製一份,複製出來的舊數據交給子進程使用,然後主進程該幹啥就幹啥。

也就是說,在進行IO操作寫盤的過程中(on write),對於沒有改變的數據,主進程和子進程資源共享;只有在出現了需要變更的數據時(寫髒的數據),才進行copy操作。

在最理想的情況下,也就是生成RDB文件的過程中,一直沒有寫操作的話,就根本不會發生內存的額外佔用。

當然,仍然需要合理配置Linux的內存分配策略。避免在寫操作過於集中時,發生因爲物理內存不足導致fork失敗的情況。

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