數據持久化通俗講就是把數據保存到磁盤上,保證不會因為斷電等因素丟失數據。
redis需要經常將內存中的數據同步到磁盤來保證持久化。redis支持兩種持久化方式,一種是 Snapshotting(快照)也是默認方式,另一種是Append-only file(縮寫aof)的方式。先介紹下這兩種dump方式再講講自己遇到的一些現象和想法,前面的內容是從網上整理出來的。
Snapshotting
快照是默認的持久化方式。這種方式是就是將內存中數據以快照的方式寫入到二進制文件中,默認的文件名為dump.rdb。可以通過配置設置自動做快照持久 化的方式。我們可以配置redis在n秒內如果超過m個key被修改就自動做快照,下面是默認的快照保存配置
save 900 1 #900秒內如果超過1個key被修改,則發起快照保存
save 300 10 #300秒內容如超過10個key被修改,則發起快照保存
save 60 10000
下面介紹詳細的快照保存過程
1.redis調用fork,現在有了子進程和父進程。
2. 父進程繼續處理client請求,子進程負責將內存內容寫入到臨時文件。由于os的寫時復制機制(copy on write)父子進程會共享相同的物理頁面,當父進程處理寫請求時os會為父進程要修改的頁面創建副本,而不是寫共享的頁面。所以子進程的地址空間內的數 據是fork時刻整個數據庫的一個快照。
3.當子進程將快照寫入臨時文件完畢后,用臨時文件替換原來的快照文件,然后子進程退出。
client 也可以使用save或者bgsave命令通知redis做一次快照持久化。save操作是在主線程中保存快照的,由于redis是用一個主線程來處理所有 client的請求,這種方式會阻塞所有client請求。所以不推薦使用。另一點需要注意的是,每次快照持久化都是將內存數據完整寫入到磁盤一次,并不 是增量的只同步臟數據。如果數據量大的話,而且寫操作比較多,必然會引起大量的磁盤io操作,可能會嚴重影響性能。
另外由于快照方式是在一定間隔時間做一次的,所以如果redis意外down掉的話,就會丟失最后一次快照后的所有修改。如果應用要求不能丟失任何修改的話,可以采用aof持久化方式。下面介紹
Append-only file
aof 比快照方式有更好的持久化性,是由于在使用aof持久化方式時,redis會將每一個收到的寫命令都通過write函數追加到文件中(默認是 appendonly.aof)。當redis重啟時會通過重新執行文件中保存的寫命令來在內存中重建整個數據庫的內容。當然由于os會在內核中緩存 write做的修改,所以可能不是立即寫到磁盤上。這樣aof方式的持久化也還是有可能會丟失部分修改。不過我們可以通過配置文件告訴redis我們想要 通過fsync函數強制os寫入到磁盤的時機。有三種方式如下(默認是:每秒fsync一次)
appendonly yes //啟用aof持久化方式
# appendfsync always //每次收到寫命令就立即強制寫入磁盤,最慢的,但是保證完全的持久化,不推薦使用
appendfsync everysec //每秒鐘強制寫入磁盤一次,在性能和持久化方面做了很好的折中,推薦
# appendfsync no //完全依賴os,性能最好,持久化沒保證
aof 的方式也同時帶來了另一個問題。持久化文件會變的越來越大。例如我們調用incr test命令100次,文件中必須保存全部的100條命令,其實有99條都是多余的。因為要恢復數據庫的狀態其實文件中保存一條set test 100就夠了。為了壓縮aof的持久化文件。redis提供了bgrewriteaof命令。收到此命令redis將使用與快照類似的方式將內存中的數據 以命令的方式保存到臨時文件中,最后替換原來的文件。具體過程如下
1. redis調用fork ,現在有父子兩個進程
2. 子進程根據內存中的數據庫快照,往臨時文件中寫入重建數據庫狀態的命令
3.父進程繼續處理client請求,除了把寫命令寫入到原來的aof文件中。同時把收到的寫命令緩存起來。這樣就能保證如果子進程重寫失敗的話并不會出問題。
4.當子進程把快照內容寫入已命令方式寫到臨時文件中后,子進程發信號通知父進程。然后父進程把緩存的寫命令也寫入到臨時文件。
5.現在父進程可以使用臨時文件替換老的aof文件,并重命名,后面收到的寫命令也開始往新的aof文件中追加。
需要注意到是重寫aof文件的操作,并沒有讀取舊的aof文件,而是將整個內存中的數據庫內容用命令的方式重寫了一個新的aof文件,這點和快照有點類似。
運維上的想法
其實快照和aof一樣,都使用了Copy-on-write技術。多次試驗發現每次做數據dump的時候,內存都會擴大一倍(關于這個問題可以參考我去年寫的 redis的內存陷阱 ,很多人用redis做為緩存,數據量小,dump耗時非常短暫,所以不太容易發現),這個時候會有三種情況:
一:物理內存足以滿足,這個時候dump非常快,性能最好
二:物理內存+虛擬內存可以滿足,這個時候dump速度會比較慢,磁盤swap繁忙,服務性能也會下降。所幸的是經過一段比較長的時候數據dump完成了,然后內存恢復正常。這個情況系統穩定性差。
三: 物理內存+虛擬內存不能滿足,這個時候dump一直死著,時間久了機器掛掉。這個情況就是災難!
如果數據要做持久化又想保證穩定性,建議留空一半的物理內存。如果覺得無法接受還是有辦法,下面講:
快照和aof雖然都使用Copy-on-write,但有個不同點,快照你無法預測redis什么時候做dump,aof可以通過bgrewriteaof命令控制dump的時機。
根據這點我可以在一個服務器上開啟多個redis節點(利用多CPU),使用aof的持久化方式。
例如在24G內存的服務器上開啟3個節點,每天用bgrewriteaof定期重新整理數據,每個節點dump的時間都不一樣,這樣理論上每個節點可以消耗6G內存,一共使用18G內存,另外6G內存在單個節點dump時用到,內存一下多利用了6G! 當然節點開的越多內存的利用率也越高。如果帶寬不是問題,節點數建議 = CPU數。
我的應用里為了保證高性能,數據沒有做dump,也沒有用aof。因為不做dump發生的故障遠遠低于做dump的時候,即使數據丟失了,自動修復腳本可以馬上數據恢復。畢竟對海量數據redis只能做數據分片,那么落到每個節點上的數據量也不會很多。
redis的虛擬內存建議也不要用,用redis本來就是為了達到變態的性能,虛擬內存、aof看起來都有些雞肋。
現在還離不開redis,因為它的mget是現在所有db里性能最好的,以前也考慮過用tokyocabinet hash方式做mget,性能不給力。直接用redis,基本上單個redis節點mget可以達到10W/s
糾錯
之前說過redis做數據dump的時候內容會擴大一倍,后來我又做了些測試,發現有些地方說的不對。
top命令并不是反映真實的內存占用情況,在top里盡管fork出來的子進程占了和父進程一樣的內存,但是當做dump的時候沒有寫操作,實際使用的是同一份內存的數據。當有寫操作的時候內存才會真實的擴大(具體是不是真實的擴大一倍不確定,可能數據是按照頁分片的),這才是真正的Copy-on-write。
基于這點在做數據持久化會更加靈活。
更多文章、技術交流、商務合作、聯系博主
微信掃碼或搜索:z360901061

微信掃一掃加我為好友
QQ號聯系: 360901061
您的支持是博主寫作最大的動力,如果您喜歡我的文章,感覺我的文章對您有幫助,請用微信掃描下面二維碼支持博主2元、5元、10元、20元等您想捐的金額吧,狠狠點擊下面給點支持吧,站長非常感激您!手機微信長按不能支付解決辦法:請將微信支付二維碼保存到相冊,切換到微信,然后點擊微信右上角掃一掃功能,選擇支付二維碼完成支付。
【本文對您有幫助就好】元
