redis 的兩種持久化方式及原理

Redis是一種高級key-value數據庫。它跟memcached相似,不過數據能夠持久化,並且支持的數據類型很豐富。有字符串,鏈表,集 合和有序集合。支持在服務器端計算集合的並,交和補集(difference)等,還支持多種排序功能。因此Redis也能夠被當作是一個數據結構服務 器。
Redis的全部數據都是保存在內存中,而後不按期的經過異步方式保存到磁盤上(這稱爲「半持久化模式」);也能夠把每一次數據變化都寫入到一個append only file(aof)裏面(這稱爲「全持久化模式」)。redis

 

第一種方法filesnapshotting:默認redis是會以快照的形式將數據持久化到磁盤的(一個二進 制文件,dump.rdb,這個文件名字能夠指定),在配置文件中的格式是:save N M表示在N秒以內,redis至少發生M次修改則redis抓快照到磁盤。固然咱們也能夠手動執行save或者bgsave(異步)作快照。數據庫

工做原理簡單介紹一下:當redis須要作持久化時,redis會fork一個子進程;子進程將數據寫到磁盤上一個臨時RDB文件中;當子進程完成寫臨時文件後,將原來的RDB替換掉,這樣的好處就是能夠copy-on-write緩存

 

還有一種持久化方法是Append-only:filesnapshotting方法在redis異常死掉時, 最近的數據會丟失(丟失數據的多少視你save策略的配置),因此這是它最大的缺點,當業務量很大時,丟失的數據是不少的。Append-only方法可 以作到所有數據不丟失,但redis的性能就要差些。AOF就能夠作到全程持久化,只須要在配置文件中開啓(默認是no),appendonly yes開啓AOF以後,redis每執行一個修改數據的命令,都會把它添加到aof文件中,當redis重啓時,將會讀取AOF文件進行「重放」以恢復到 redis關閉前的最後時刻。安全

LOG Rewriting隨着修改數據的執行AOF文件會愈來愈大,其中不少內容記錄某一個key的變化狀況。所以redis有了一種比較有意思的特性:在後臺重建AOF文件,而不會影響client端操做。在任什麼時候候執行BGREWRITEAOF命令,都會把當前內存中最短序列的命令寫到磁盤,這些命令能夠徹底構建當前的數據狀況,而不會存在多餘的變化狀況(好比狀態變化,計數器變化等),縮小的AOF文件的大小。因此當使用AOF時,redis推薦同時使用BGREWRITEAOF服務器

AOF文件刷新的方式,有三種,參考配置參數appendfsync :appendfsync always每提交一個修改命令都調用fsync刷新到AOF文件,很是很是慢,但也很是安全;appendfsync everysec每秒鐘都調用fsync刷新到AOF文件,很快,但可能會丟失一秒之內的數據;appendfsync no依靠OS進行刷新,redis不主動刷新AOF,這樣最快,但安全性就差。默認並推薦每秒刷新,這樣在速度和安全上都作到了兼顧。數據結構

可能因爲系統緣由致使了AOF損壞,redis沒法再加載這個AOF,能夠按照下面步驟來修復:首先作一個AOF文件的備份,複製到其餘地方;修復原始AOF文件,執行:$ redis-check-aof –fix ;能夠經過diff –u命令來查看修復先後文件不一致的地方;重啓redis服務。app

LOG Rewrite的工做原理:一樣用到了copy-on-write:首先redis會fork一個子進程;子進程將最新的AOF寫入一個臨時文件;父進程 增量的把內存中的最新執行的修改寫入(這時仍寫入舊的AOF,rewrite若是失敗也是安全的);當子進程完成rewrite臨時文件後,父進程會收到 一個信號,並把以前內存中增量的修改寫入臨時文件末尾;這時redis將舊AOF文件重命名,臨時文件重命名,開始向新的AOF中寫入。運維

最後,爲以防萬一(機器壞掉或磁盤壞掉),記得按期把使用 filesnapshotting 或 Append-only 生成的*rdb *.aof文件備份到遠程機器上。我是用crontab每半小時SCP一次。我沒有使用redis的主從功能 ,由於半小時備份一次應該是能夠了,並且我以爲有若是作主從有點浪費機器。這個最終仍是看應用來定了。異步

========================memcached

數據持久化通俗講就是把數據保存到磁盤上,保證不會由於斷電等因素丟失數據。

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。

基於這點在作數據持久化會更加靈活。

相關文章
相關標籤/搜索