? ? ? ?Redis是一種高級key-value數據庫。它跟memcached類似,不過數據可以持久化,而且支持的數據類型很豐富。有字符串,鏈表,集 合和有序集合。支持在服務器端計算集合的并,交和補集(difference)等,還支持多種排序功能。所以Redis也可以被看成是一個數據結構服務 器。Redis的所有數據都是保存在內存中,然后不定期的通過異步方式保存到磁盤上(這稱為“半持久化模式”);也可以把每一次數據變化都寫入到一個append only file(aof)里面(這稱為“全持久化模式”)。
? ? ? ? ?第一種方法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服務。
LOG Rewrite的工作原理:同樣用到了copy-on-write:首先redis會fork一個子進程;子進程將最新的AOF寫入一個臨時文件;父進程 增量的把內存中的最新執行的修改寫入(這時仍寫入舊的AOF,rewrite如果失敗也是安全的);當子進程完成rewrite臨時文件后,父進程會收到 一個信號,并把之前內存中增量的修改寫入臨時文件末尾;這時redis將舊AOF文件重命名,臨時文件重命名,開始向新的AOF中寫入。
最后,為以防萬一(機器壞掉或磁盤壞掉),記得定期把使用 filesnapshotting 或 Append-only 生成的*rdb *.aof文件備份到遠程機器上。我是用crontab每半小時SCP一次。我沒有使用redis的主從功能 ,因為半小時備份一次應該是可以了,而且我覺得有如果做主從有點浪費機器。這個最終還是看應用來定了。
========================
數據持久化通俗講就是把數據保存到磁盤上,保證不會因為斷電等因素丟失數據。
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。
基于這點在做數據持久化會更加靈活。