Redis是一個支持持久化的內存數據庫,也就是說redis需要經常將內存中的數據同步到磁盤來保證持久化。redis支持四種持久化方式,一是 Snapshotting(快照)也是默認方式;二是Append-only file(縮寫aof)的方式;三是虛擬內存方式;四是diskstore方式。下面分別介紹之。
(一)Snapshotting
快照是默認的持久化方式。這種方式是就是將內存中數據以快照的方式寫入到二進制文件中,默認的文件名為dump.rdb。可以通過配置設置自動做快照持久化的方式。我們可以配置redis在n秒內如果超過m個key被修改就自動做快照,下面是默認的快照保存配置:
save 900 1 #900秒內如果超過1個key被修改,則發起快照保存 |
快照保存過程:
1. redis調用fork,現在有了子進程和父進程。
2. 父進程繼續處理client請求,子進程負責將內存內容寫入到臨時文件。由於os的寫時復制機制(copy on write)父子進程會共享相同的物理頁面,當父進程處理寫請求時os會為父進程要修改的頁面創建副本,而不是寫共享的頁面。所以子進程的地址空間內的數據是fork時刻整個數據庫的一個快照。
3. 當子進程將快照寫入臨時文件完畢后,用臨時文件替換原來的快照文件,然后子進程退出(fork一個進程入內在也被復制了,即內存會是原來的兩倍)。
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持久化方式 |
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文件,這點和快照有點類似。
(三)虛擬內存方式(desprecated)
首先說明:在Redis-2.4后虛擬內存功能已經被deprecated了,原因如下:
1)slow restart重啟太慢
2)slow saving保存數據太慢
3)slow replication上面兩條導致 replication 太慢
4)complex code代碼過於復雜
下面還是介紹一下redis的虛擬內存。
redis的虛擬內存與os的虛擬內存不是一碼事,但是思路和目的都是相同的。就是暫時把不經常訪問的數據從內存交換到磁盤中,從而騰出寶貴的內存空間用於其他需要訪問的數據。尤其是對於redis這樣的內存數據庫,內存總是不夠用的。除了可以將數據分割到多個redis server外。另外的能夠提高數據庫容量的辦法就是使用vm把那些不經常訪問的數據交換的磁盤上。如果我們的存儲的數據總是有少部分數據被經常訪問,大部分數據很少被訪問,對於網站來說確實總是只有少量用戶經常活躍。當少量數據被經常訪問時,使用vm不但能提高單台redis server數據庫的容量,而且也不會對性能造成太多影響。
redis沒有使用os提供的虛擬內存機制而是自己在用戶態實現了自己的虛擬內存機制,作者在自己的blog專門解釋了其中原因。
http://antirez.com/post/redis-virtual-memory-story.html
主要的理由有兩點:
1. os 的虛擬內存是已4k頁面為最小單位進行交換的。而redis的大多數對象都遠小於4k,所以一個os頁面上可能有多個redis對象。另外redis的集合對象類型如list,set可能存在與多個os頁面上。最終可能造成只有10%key被經常訪問,但是所有os頁面都會被os認為是活躍的,這樣只有內存真正耗盡時os才會交換頁面。
2.相比於os的交換方式。redis可以將被交換到磁盤的對象進行壓縮,保存到磁盤的對象可以去除指針和對象元數據信息。一般壓縮后的對象會比內存中的對象小10倍。這樣redis的vm會比os vm能少做很多io操作。
(四)diskstore方式
diskstore方式是作者放棄了虛擬內存方式后選擇的一種新的實現方式,也就是傳統的B-tree的方式。具體細節是:
1) 讀操作,使用read through以及LRU方式。內存中不存在的數據從磁盤拉取並放入內存,內存中放不下的數據采用LRU淘汰。
2) 寫操作,采用另外spawn一個線程單獨處理,寫線程通常是異步的,當然也可以把cache-flush-delay配置設成0,Redis盡量保證即時寫入。但是在很多場合延遲寫會有更好的性能,比如一些計數器用Redis存儲,在短時間如果某個計數反復被修改,Redis只需要將最終的結果寫入磁盤。這種做法作者叫per key persistence。由於寫入會按key合並,因此和snapshot還是有差異,disk store並不能保證時間一致性。
由於寫操作是單線程,即使cache-flush-delay設成0,多個client同時寫則需要排隊等待,如果隊列容量超過cache-max-memory Redis設計會進入等待狀態,造成調用方卡住。
Google Group上有熱心網友迅速完成了壓力測試,當內存用完之后,set每秒處理速度從25k下降到10k再到后來幾乎卡住。 雖然通過增加cache-flush-delay可以提高相同key重復寫入性能;通過增加cache-max-memory可以應對臨時峰值寫入。但是diskstore寫入瓶頸最終還是在IO。
3) rdb 和新 diskstore 格式關系
rdb是傳統Redis內存方式的存儲格式,diskstore是另外一種格式,那兩者關系如何?
· 通過BGSAVE可以隨時將diskstore格式另存為rdb格式,而且rdb格式還用於Redis復制以及不同存儲方式之間的中間格式。
· 通過工具可以將rdb格式轉換成diskstore格式。
當然,diskstore原理很美好,但是目前還處於alpha版本,也只是一個簡單demo,diskstore.c加上注釋只有300行,實現的方法就是將每個value作為一個獨立文件保存,文件名是key的hash值。因此diskstore需要將來有一個更高效穩定的實現才能用於生產環境。但由於有清晰的接口設計,diskstore.c也很容易換成一種B-Tree的實現。很多開發者也在積極探討使用bdb或者innodb來替換默認diskstore.c的可行性。
轉自:https://blog.csdn.net/chajinglong/article/details/58594518