文章目錄
1.RDB的基本概念
2.RDB的觸發方式
2-1、配置文件
2-2、手工觸發
2-3、其他觸發方式
3.bgsave的工作流程
3-1、什么是cow
3-2、Redis面臨的問題
3-3、Redis的cow
因為有小伙伴問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
- Redis創建子進程以后,根本不進行數據的copy,主進程與子線程是共享數據的。主進程繼續對外提供讀寫服務。
- 雖然不copy數據,但是kernel會把主進程中的所有內存頁的權限都設為read-only,主進程和子進程訪問數據的指針都指向同一內存地址。
- 主進程發生寫操作時,因為權限已經設置為read-only了,所以會觸發頁異常中斷(page-fault)。在中斷處理中,需要被寫入的內存頁面會復制一份,復制出來的舊數據交給子進程使用,然后主進程該干啥就干啥。
也就是說,在進行IO操作寫盤的過程中(on write),對於沒有改變的數據,主進程和子進程資源共享;只有在出現了需要變更的數據時(寫臟的數據),才進行copy操作。
在最理想的情況下,也就是生成RDB文件的過程中,一直沒有寫操作的話,就根本不會發生內存的額外占用。
當然,仍然需要合理配置Linux的內存分配策略。避免在寫操作過於集中時,發生因為物理內存不足導致fork失敗的情況。