Redis是一個(gè)支持持久化的內(nèi)存數(shù)據(jù)庫(kù)闯第,也就是說(shuō)redis需要經(jīng)常將內(nèi)存中的數(shù)據(jù)同步到磁盤(pán)來(lái)保證持久化繁堡。redis支持四種持久化方式迷帜,一是?Snapshotting(快照)也是默認(rèn)方式迹鹅;二是Append-only file(縮寫(xiě)aof)的方式担孔;三是虛擬內(nèi)存方式江锨;四是diskstore方式。下面分別介紹之糕篇。
(一)Snapshotting
???????快照是默認(rèn)的持久化方式啄育。這種方式是就是將內(nèi)存中數(shù)據(jù)以快照的方式寫(xiě)入到二進(jìn)制文件中,默認(rèn)的文件名為dump.rdb“柘可以通過(guò)配置設(shè)置自動(dòng)做快照持久化的方式挑豌。我們可以配置redis在n秒內(nèi)如果超過(guò)m個(gè)key被修改就自動(dòng)做快照,下面是默認(rèn)的快照保存配置:
save 900 1#900秒內(nèi)如果超過(guò)1個(gè)key被修改,則發(fā)起快照保存
save 300 10#300秒內(nèi)容如超過(guò)10個(gè)key被修改浮毯,則發(fā)起快照保存
save 60 10000
快照保存過(guò)程:
?????? 1. redis調(diào)用fork,現(xiàn)在有了子進(jìn)程和父進(jìn)程完疫。
?????? 2.?父進(jìn)程繼續(xù)處理client請(qǐng)求,子進(jìn)程負(fù)責(zé)將內(nèi)存內(nèi)容寫(xiě)入到臨時(shí)文件债蓝。由于os的寫(xiě)時(shí)復(fù)制機(jī)制(copy on write)父子進(jìn)程會(huì)共享相同的物理頁(yè)面壳鹤,當(dāng)父進(jìn)程處理寫(xiě)請(qǐng)求時(shí)os會(huì)為父進(jìn)程要修改的頁(yè)面創(chuàng)建副本,而不是寫(xiě)共享的頁(yè)面饰迹。所以子進(jìn)程的地址空間內(nèi)的數(shù)據(jù)是fork時(shí)刻整個(gè)數(shù)據(jù)庫(kù)的一個(gè)快照芳誓。
?????? 3.?當(dāng)子進(jìn)程將快照寫(xiě)入臨時(shí)文件完畢后,用臨時(shí)文件替換原來(lái)的快照文件啊鸭,然后子進(jìn)程退出(fork一個(gè)進(jìn)程入內(nèi)在也被復(fù)制了锹淌,即內(nèi)存會(huì)是原來(lái)的兩倍)。
?????? client?也可以使用save或者bgsave命令通知redis做一次快照持久化赠制。save操作是在主線程中保存快照的赂摆,由于redis是用一個(gè)主線程來(lái)處理所有?client的請(qǐng)求,這種方式會(huì)阻塞所有client請(qǐng)求钟些。所以不推薦使用烟号。另一點(diǎn)需要注意的是,每次快照持久化都是將內(nèi)存數(shù)據(jù)完整寫(xiě)入到磁盤(pán)一次政恍,并不是增量的只同步臟數(shù)據(jù)汪拥。如果數(shù)據(jù)量大的話,而且寫(xiě)操作比較多篙耗,必然會(huì)引起大量的磁盤(pán)io操作迫筑,可能會(huì)嚴(yán)重影響性能。???????另外由于快照方式是在一定間隔時(shí)間做一次的宗弯,所以如果redis意外down掉的話脯燃,就會(huì)丟失最后一次快照后的所有修改。如果應(yīng)用要求不能丟失任何修改的話罕伯,可以采用aof持久化方式曲伊。下面介紹:
(二)Append-only file
aof?比快照方式有更好的持久化性,是由于在使用aof持久化方式時(shí)追他,redis會(huì)將每一個(gè)收到的寫(xiě)命令都通過(guò)write函數(shù)追加到文件中(默認(rèn)是appendonly.aof)。當(dāng)redis重啟時(shí)會(huì)通過(guò)重新執(zhí)行文件中保存的寫(xiě)命令來(lái)在內(nèi)存中重建整個(gè)數(shù)據(jù)庫(kù)的內(nèi)容岛蚤。當(dāng)然由于os會(huì)在內(nèi)核中緩存?write做的修改邑狸,所以可能不是立即寫(xiě)到磁盤(pán)上。這樣aof方式的持久化也還是有可能會(huì)丟失部分修改涤妒。不過(guò)我們可以通過(guò)配置文件告訴redis我們想要通過(guò)fsync函數(shù)強(qiáng)制os寫(xiě)入到磁盤(pán)的時(shí)機(jī)单雾。有三種方式如下(默認(rèn)是:每秒fsync一次):
appendonly yes#啟用aof持久化方式
# appendfsync always#每次收到寫(xiě)命令就立即強(qiáng)制寫(xiě)入磁盤(pán),最慢的,但是保證完全的持久化硅堆,不推薦使用
appendfsync everysec#每秒鐘強(qiáng)制寫(xiě)入磁盤(pán)一次屿储,在性能和持久化方面做了很好的折中,推薦
# appendfsync no#完全依賴os渐逃,性能最好,持久化沒(méi)保證
aof?的方式也同時(shí)帶來(lái)了另一個(gè)問(wèn)題够掠。持久化文件會(huì)變的越來(lái)越大。例如我們調(diào)用incr test命令100次茄菊,文件中必須保存全部的100條命令疯潭,其實(shí)有99條都是多余的。因?yàn)橐謴?fù)數(shù)據(jù)庫(kù)的狀態(tài)其實(shí)文件中保存一條set test 100就夠了面殖。為了壓縮aof的持久化文件竖哩。redis提供了bgrewriteaof命令。收到此命令redis將使用與快照類似的方式將內(nèi)存中的數(shù)據(jù)以命令的方式保存到臨時(shí)文件中脊僚,最后替換原來(lái)的文件相叁。具體過(guò)程如下:
?????? 1.? redis調(diào)用fork?,現(xiàn)在有父子兩個(gè)進(jìn)程
?????? 2.?子進(jìn)程根據(jù)內(nèi)存中的數(shù)據(jù)庫(kù)快照辽幌,往臨時(shí)文件中寫(xiě)入重建數(shù)據(jù)庫(kù)狀態(tài)的命令
?????? 3.?父進(jìn)程繼續(xù)處理client請(qǐng)求钝荡,除了把寫(xiě)命令寫(xiě)入到原來(lái)的aof文件中。同時(shí)把收到的寫(xiě)命令緩存起來(lái)舶衬。這樣就能保證如果子進(jìn)程重寫(xiě)失敗的話并不會(huì)出問(wèn)題埠通。
?????? 4.?當(dāng)子進(jìn)程把快照內(nèi)容寫(xiě)入已命令方式寫(xiě)到臨時(shí)文件中后,子進(jìn)程發(fā)信號(hào)通知父進(jìn)程逛犹。然后父進(jìn)程把緩存的寫(xiě)命令也寫(xiě)入到臨時(shí)文件端辱。
?????? 5.?現(xiàn)在父進(jìn)程可以使用臨時(shí)文件替換老的aof文件,并重命名虽画,后面收到的寫(xiě)命令也開(kāi)始往新的aof文件中追加舞蔽。
???????需要注意到是重寫(xiě)aof文件的操作,并沒(méi)有讀取舊的aof文件码撰,而是將整個(gè)內(nèi)存中的數(shù)據(jù)庫(kù)內(nèi)容用命令的方式重寫(xiě)了一個(gè)新的aof文件渗柿,這點(diǎn)和快照有點(diǎn)類似。
(三)虛擬內(nèi)存方式(desprecated)
首先說(shuō)明:在Redis-2.4后虛擬內(nèi)存功能已經(jīng)被deprecated了脖岛,原因如下:
1)slow restart重啟太慢
2)slow saving保存數(shù)據(jù)太慢
3)slow replication上面兩條導(dǎo)致?replication?太慢
4)complex code代碼過(guò)于復(fù)雜
下面還是介紹一下redis的虛擬內(nèi)存朵栖。
redis的虛擬內(nèi)存與os的虛擬內(nèi)存不是一碼事,但是思路和目的都是相同的柴梆。就是暫時(shí)把不經(jīng)常訪問(wèn)的數(shù)據(jù)從內(nèi)存交換到磁盤(pán)中陨溅,從而騰出寶貴的內(nèi)存空間用于其他需要訪問(wèn)的數(shù)據(jù)。尤其是對(duì)于redis這樣的內(nèi)存數(shù)據(jù)庫(kù)绍在,內(nèi)存總是不夠用的门扇。除了可以將數(shù)據(jù)分割到多個(gè)redis server外雹有。另外的能夠提高數(shù)據(jù)庫(kù)容量的辦法就是使用vm把那些不經(jīng)常訪問(wèn)的數(shù)據(jù)交換的磁盤(pán)上。如果我們的存儲(chǔ)的數(shù)據(jù)總是有少部分?jǐn)?shù)據(jù)被經(jīng)常訪問(wèn)臼寄,大部分?jǐn)?shù)據(jù)很少被訪問(wèn)霸奕,對(duì)于網(wǎng)站來(lái)說(shuō)確實(shí)總是只有少量用戶經(jīng)常活躍吉拳。當(dāng)少量數(shù)據(jù)被經(jīng)常訪問(wèn)時(shí)质帅,使用vm不但能提高單臺(tái)redis server數(shù)據(jù)庫(kù)的容量,而且也不會(huì)對(duì)性能造成太多影響合武。
??????? redis沒(méi)有使用os提供的虛擬內(nèi)存機(jī)制而是自己在用戶態(tài)實(shí)現(xiàn)了自己的虛擬內(nèi)存機(jī)制,作者在自己的blog專門(mén)解釋了其中原因临梗。
http://antirez.com/post/redis-virtual-memory-story.html
主要的理由有兩點(diǎn):
?????? 1. os?的虛擬內(nèi)存是已4k頁(yè)面為最小單位進(jìn)行交換的。而redis的大多數(shù)對(duì)象都遠(yuǎn)小于4k稼跳,所以一個(gè)os頁(yè)面上可能有多個(gè)redis對(duì)象盟庞。另外redis的集合對(duì)象類型如list,set可能存在與多個(gè)os頁(yè)面上。最終可能造成只有10%key被經(jīng)常訪問(wèn)汤善,但是所有os頁(yè)面都會(huì)被os認(rèn)為是活躍的什猖,這樣只有內(nèi)存真正耗盡時(shí)os才會(huì)交換頁(yè)面。
?????? 2.相比于os的交換方式红淡。redis可以將被交換到磁盤(pán)的對(duì)象進(jìn)行壓縮,保存到磁盤(pán)的對(duì)象可以去除指針和對(duì)象元數(shù)據(jù)信息不狮。一般壓縮后的對(duì)象會(huì)比內(nèi)存中的對(duì)象小10倍。這樣redis的vm會(huì)比os vm能少做很多io操作在旱。
???????下面是vm相關(guān)配置:
slaveof 192.168.1.1 6379#指定master的ip和端口
vm-enabled yes#開(kāi)啟vm功能
vm-swap-file /tmp/redis.swap#交換出來(lái)的value保存的文件路徑/tmp/redis.swap
vm-max-memory 1000000#redis使用的最大內(nèi)存上限摇零,超過(guò)上限后redis開(kāi)始交換value到磁盤(pán)文件中
vm-page-size 32#每個(gè)頁(yè)面的大小32個(gè)字節(jié)
vm-pages 134217728#最多使用在文件中使用多少頁(yè)面,交換文件的大小 = vm-page-size * vm-pages
vm-max-threads 4#用于執(zhí)行value對(duì)象換入換出的工作線程數(shù)量,0表示不使用工作線程(后面介紹)
?????? redis的vm在設(shè)計(jì)上為了保證key的查找速度桶蝎,只會(huì)將value交換到swap文件中驻仅。所以如果是內(nèi)存問(wèn)題是由于太多value很小的key造成的,那么vm并不能解決登渣。和os一樣redis也是按頁(yè)面來(lái)交換對(duì)象的噪服。redis規(guī)定同一個(gè)頁(yè)面只能保存一個(gè)對(duì)象。但是一個(gè)對(duì)象可以保存在多個(gè)頁(yè)面中胜茧。
在redis使用的內(nèi)存沒(méi)超過(guò)vm-max-memory之前是不會(huì)交換任何value的粘优。當(dāng)超過(guò)最大內(nèi)存限制后,redis會(huì)選擇較老的對(duì)象呻顽。如果兩個(gè)對(duì)象一樣老會(huì)優(yōu)先交換比較大的對(duì)象雹顺,精確的公式swappability = age*log(size_in_memory)。對(duì)于vm-page-size的設(shè)置應(yīng)該根據(jù)自己的應(yīng)用將頁(yè)面的大小設(shè)置為可以容納大多數(shù)對(duì)象的大小芬位。太大了會(huì)浪費(fèi)磁盤(pán)空間无拗,太小了會(huì)造成交換文件出現(xiàn)碎片。對(duì)于交換文件中的每個(gè)頁(yè)面昧碉,redis會(huì)在內(nèi)存中對(duì)應(yīng)一個(gè)1bit值來(lái)記錄頁(yè)面的空閑狀態(tài)英染。所以像上面配置中頁(yè)面數(shù)量(vm-pages 134217728 )會(huì)占用16M內(nèi)存用來(lái)記錄頁(yè)面空閑狀態(tài)。vm-max-threads表示用做交換任務(wù)的線程數(shù)量被饿。如果大于0推薦設(shè)為服務(wù)器的cpu core的數(shù)量四康。如果是0則交換過(guò)程在主線程進(jìn)行。
參數(shù)配置討論完后狭握,在來(lái)簡(jiǎn)單介紹下vm是如何工作的:
當(dāng)vm-max-threads設(shè)為0時(shí)(Blocking VM)
換出:
???????主線程定期檢查發(fā)現(xiàn)內(nèi)存超出最大上限后闪金,會(huì)直接已阻塞的方式,將選中的對(duì)象保存到swap文件中,并釋放對(duì)象占用的內(nèi)存,此過(guò)程會(huì)一直重復(fù)直到下面條件滿足
?????? 1.內(nèi)存使用降到最大限制以下
?????? 2.swap文件滿了
?????? 3.幾乎全部的對(duì)象都被交換到磁盤(pán)了
換入:
??????????????當(dāng)有client請(qǐng)求value被換出的key時(shí)论颅。主線程會(huì)以阻塞的方式從文件中加載對(duì)應(yīng)的value對(duì)象哎垦,加載時(shí)此時(shí)會(huì)阻塞所有client。然后處理client的請(qǐng)求
當(dāng)vm-max-threads大于0(Threaded VM)
換出:
???????當(dāng)主線程檢測(cè)到使用內(nèi)存超過(guò)最大上限恃疯,會(huì)將選中的要交換的對(duì)象信息放到一個(gè)隊(duì)列中交由工作線程后臺(tái)處理漏设,主線程會(huì)繼續(xù)處理client請(qǐng)求。
換入:
???????如果有client請(qǐng)求的key被換出了今妄,主線程先阻塞發(fā)出命令的client,然后將加載對(duì)象的信息放到一個(gè)隊(duì)列中郑口,讓工作線程去加載。加載完畢后工作線程通知主線程盾鳞。主線程再執(zhí)行client的命令犬性。這種方式只阻塞請(qǐng)求value被換出key的client
總的來(lái)說(shuō)blocking vm的方式總的性能會(huì)好一些,因?yàn)椴恍枰€程同步腾仅,創(chuàng)建線程和恢復(fù)被阻塞的client等開(kāi)銷乒裆。但是也相應(yīng)的犧牲了響應(yīng)性。threaded vm的方式主線程不會(huì)阻塞在磁盤(pán)io上推励,所以響應(yīng)性更好鹤耍。如果我們的應(yīng)用不太經(jīng)常發(fā)生換入換出,而且也不太在意有點(diǎn)延遲的話則推薦使用blocking vm的方式吹艇。
關(guān)于redis vm的更詳細(xì)介紹可以參考下面鏈接:
???????http://antirez.com/post/redis-virtual-memory-story.html
???????http://redis.io/topics/internals-vm
(四)diskstore方式
diskstore方式是作者放棄了虛擬內(nèi)存方式后選擇的一種新的實(shí)現(xiàn)方式惰蜜,也就是傳統(tǒng)的B-tree的方式。具體細(xì)節(jié)是:
1)?讀操作受神,使用read through以及LRU方式抛猖。內(nèi)存中不存在的數(shù)據(jù)從磁盤(pán)拉取并放入內(nèi)存,內(nèi)存中放不下的數(shù)據(jù)采用LRU淘汰鼻听。
2)?寫(xiě)操作财著,采用另外spawn一個(gè)線程單獨(dú)處理,寫(xiě)線程通常是異步的撑碴,當(dāng)然也可以把cache-flush-delay配置設(shè)成0撑教,Redis盡量保證即時(shí)寫(xiě)入。但是在很多場(chǎng)合延遲寫(xiě)會(huì)有更好的性能醉拓,比如一些計(jì)數(shù)器用Redis存儲(chǔ)伟姐,在短時(shí)間如果某個(gè)計(jì)數(shù)反復(fù)被修改收苏,Redis只需要將最終的結(jié)果寫(xiě)入磁盤(pán)。這種做法作者叫per key persistence愤兵。由于寫(xiě)入會(huì)按key合并鹿霸,因此和snapshot還是有差異,disk store并不能保證時(shí)間一致性秆乳。
由于寫(xiě)操作是單線程懦鼠,即使cache-flush-delay設(shè)成0,多個(gè)client同時(shí)寫(xiě)則需要排隊(duì)等待屹堰,如果隊(duì)列容量超過(guò)cache-max-memory Redis設(shè)計(jì)會(huì)進(jìn)入等待狀態(tài)肛冶,造成調(diào)用方卡住。
Google Group上有熱心網(wǎng)友迅速完成了壓力測(cè)試扯键,當(dāng)內(nèi)存用完之后睦袖,set每秒處理速度從25k下降到10k再到后來(lái)幾乎卡住。 雖然通過(guò)增加cache-flush-delay可以提高相同key重復(fù)寫(xiě)入性能忧陪;通過(guò)增加cache-max-memory可以應(yīng)對(duì)臨時(shí)峰值寫(xiě)入扣泊。但是diskstore寫(xiě)入瓶頸最終還是在IO。
3) rdb?和新 diskstore?格式關(guān)系
rdb是傳統(tǒng)Redis內(nèi)存方式的存儲(chǔ)格式嘶摊,diskstore是另外一種格式延蟹,那兩者關(guān)系如何?
·通過(guò)BGSAVE可以隨時(shí)將diskstore格式另存為rdb格式叶堆,而且rdb格式還用于Redis復(fù)制以及不同存儲(chǔ)方式之間的中間格式阱飘。
·通過(guò)工具可以將rdb格式轉(zhuǎn)換成diskstore格式。
當(dāng)然虱颗,diskstore原理很美好沥匈,但是目前還處于alpha版本,也只是一個(gè)簡(jiǎn)單demo忘渔,diskstore.c加上注釋只有300行高帖,實(shí)現(xiàn)的方法就是將每個(gè)value作為一個(gè)獨(dú)立文件保存,文件名是key的hash值畦粮。因此diskstore需要將來(lái)有一個(gè)更高效穩(wěn)定的實(shí)現(xiàn)才能用于生產(chǎn)環(huán)境散址。但由于有清晰的接口設(shè)計(jì),diskstore.c也很容易換成一種B-Tree的實(shí)現(xiàn)宣赔。很多開(kāi)發(fā)者也在積極探討使用bdb或者innodb來(lái)替換默認(rèn)diskstore.c的可行性预麸。
下面介紹一下Diskstore的算法。
其實(shí)DiskStore類似于Hash算法儒将,首先通過(guò)SHA1算法把Key轉(zhuǎn)化成一個(gè)40個(gè)字符的Hash值吏祸,然后把Hash值的前兩位作為一級(jí)目錄,然后把Hash值的三四位作為二級(jí)目錄钩蚊,最后把Hash值作為文件名贡翘,類似于“/0b/ee/0beec7b5ea3f0fdbc95d0dd47f3c5bc275da8a33”形式蹈矮。算法如下:
dsKeyToPath(key):
char path[1024];
char *hashKey = sha1(key);
path[0] = hashKey[0];
path[1] = hashKey[1];
path[2] = '/';
path[3] = hashKey[2];
path[4] = hashKey[3];
path[5] = '/';
memcpy(path + 6, hashKey, 40);
return path;
存儲(chǔ)算法(如key == apple):
dsSet(key, value, expireTime):
????// d0be2dc421be4fcd0172e5afceea3970e2f3d940
char *hashKey = sha1(key);
// d0/be/d0be2dc421be4fcd0172e5afceea3970e2f3d940
char *path = dsKeyToPath(hashKey);
FILE *fp = fopen(path, "w");
rdbSaveKeyValuePair(fp, key, value, expireTime);
fclose(fp)
獲取算法:
dsGet(key):
char *hashKey = sha1(key);
char *path = dsKeyToPath(hashKey);
FILE *fp = fopen(path, "r");
robj *val = rdbLoadObject(fp);
return val;
不過(guò)DiskStore有個(gè)缺點(diǎn),就是有可能發(fā)生兩個(gè)不同的Key生成一個(gè)相同的SHA1 Hash值床估,這樣就有可能出現(xiàn)丟失數(shù)據(jù)的問(wèn)題含滴。不過(guò)這種情況發(fā)生的幾率比較少诱渤,所以是可以接受的丐巫。根據(jù)作者的意圖,未來(lái)可能使用B+tree來(lái)替換這種高度依賴文件系統(tǒng)的實(shí)現(xiàn)方法勺美。
原文博主:http://blog.csdn.net/freebird_lb/article/details/7778981