? ? ? ?Redis是一種高級(jí)key-value數(shù)據(jù)庫。它跟memcached類似,不過數(shù)據(jù)可以持久化,而且支持的數(shù)據(jù)類型很豐富。有字符串,鏈表,集 合和有序集合。支持在服務(wù)器端計(jì)算集合的并,交和補(bǔ)集(difference)等,還支持多種排序功能。所以Redis也可以被看成是一個(gè)數(shù)據(jù)結(jié)構(gòu)服務(wù) 器。Redis的所有數(shù)據(jù)都是保存在內(nèi)存中,然后不定期的通過異步方式保存到磁盤上(這稱為“半持久化模式”);也可以把每一次數(shù)據(jù)變化都寫入到一個(gè)append only file(aof)里面(這稱為“全持久化模式”)。
? ? ? ? ?第一種方法filesnapshotting:默認(rèn)redis是會(huì)以快照的形式將數(shù)據(jù)持久化到磁盤的(一個(gè)二進(jìn) 制文件,dump.rdb,這個(gè)文件名字可以指定),在配置文件中的格式是:save N M表示在N秒之內(nèi),redis至少發(fā)生M次修改則redis抓快照到磁盤。當(dāng)然我們也可以手動(dòng)執(zhí)行save或者bgsave(異步)做快照。
工作原理簡單介紹一下:當(dāng)redis需要做持久化時(shí),redis會(huì)fork一個(gè)子進(jìn)程;子進(jìn)程將數(shù)據(jù)寫到磁盤上一個(gè)臨時(shí)RDB文件中;當(dāng)子進(jìn)程完成寫臨時(shí)文件后,將原來的RDB替換掉,這樣的好處就是可以copy-on-write
還有一種持久化方法是Append-only:filesnapshotting方法在redis異常死掉時(shí), 最近的數(shù)據(jù)會(huì)丟失(丟失數(shù)據(jù)的多少視你save策略的配置),所以這是它最大的缺點(diǎn),當(dāng)業(yè)務(wù)量很大時(shí),丟失的數(shù)據(jù)是很多的。Append-only方法可 以做到全部數(shù)據(jù)不丟失,但redis的性能就要差些。AOF就可以做到全程持久化,只需要在配置文件中開啟(默認(rèn)是no),appendonly yes開啟AOF之后,redis每執(zhí)行一個(gè)修改數(shù)據(jù)的命令,都會(huì)把它添加到aof文件中,當(dāng)redis重啟時(shí),將會(huì)讀取AOF文件進(jìn)行“重放”以恢復(fù)到 redis關(guān)閉前的最后時(shí)刻。
LOG Rewriting隨著修改數(shù)據(jù)的執(zhí)行AOF文件會(huì)越來越大,其中很多內(nèi)容記錄某一個(gè)key的變化情況。因此redis有了一種比較有意思的特性:在后臺(tái)重建AOF文件,而不會(huì)影響client端操作。在任何時(shí)候執(zhí)行BGREWRITEAOF命令,都會(huì)把當(dāng)前內(nèi)存中最短序列的命令寫到磁盤,這些命令可以完全構(gòu)建當(dāng)前的數(shù)據(jù)情況,而不會(huì)存在多余的變化情況(比如狀態(tài)變化,計(jì)數(shù)器變化等),縮小的AOF文件的大小。所以當(dāng)使用AOF時(shí),redis推薦同時(shí)使用BGREWRITEAOF。
AOF文件刷新的方式,有三種,參考配置參數(shù)appendfsync:appendfsync always每提交一個(gè)修改命令都調(diào)用fsync刷新到AOF文件,非常非常慢,但也非常安全;appendfsync everysec每秒鐘都調(diào)用fsync刷新到AOF文件,很快,但可能會(huì)丟失一秒以內(nèi)的數(shù)據(jù);appendfsync no依靠OS進(jìn)行刷新,redis不主動(dòng)刷新AOF,這樣最快,但安全性就差。默認(rèn)并推薦每秒刷新,這樣在速度和安全上都做到了兼顧。
可能由于系統(tǒng)原因?qū)е铝薃OF損壞,redis無法再加載這個(gè)AOF,可以按照下面步驟來修復(fù):首先做一個(gè)AOF文件的備份,復(fù)制到其他地方;修復(fù)原始AOF文件,執(zhí)行:$ redis-check-aof –fix ;可以通過diff –u命令來查看修復(fù)前后文件不一致的地方;重啟redis服務(wù)。
LOG Rewrite的工作原理:同樣用到了copy-on-write:首先redis會(huì)fork一個(gè)子進(jìn)程;子進(jìn)程將最新的AOF寫入一個(gè)臨時(shí)文件;父進(jìn)程 增量的把內(nèi)存中的最新執(zhí)行的修改寫入(這時(shí)仍寫入舊的AOF,rewrite如果失敗也是安全的);當(dāng)子進(jìn)程完成rewrite臨時(shí)文件后,父進(jìn)程會(huì)收到 一個(gè)信號(hào),并把之前內(nèi)存中增量的修改寫入臨時(shí)文件末尾;這時(shí)redis將舊AOF文件重命名,臨時(shí)文件重命名,開始向新的AOF中寫入。
最后,為以防萬一(機(jī)器壞掉或磁盤壞掉),記得定期把使用 filesnapshotting 或 Append-only 生成的*rdb *.aof文件備份到遠(yuǎn)程機(jī)器上。我是用crontab每半小時(shí)SCP一次。我沒有使用redis的主從功能 ,因?yàn)榘胄r(shí)備份一次應(yīng)該是可以了,而且我覺得有如果做主從有點(diǎn)浪費(fèi)機(jī)器。這個(gè)最終還是看應(yīng)用來定了。
========================
數(shù)據(jù)持久化通俗講就是把數(shù)據(jù)保存到磁盤上,保證不會(huì)因?yàn)閿嚯姷纫蛩貋G失數(shù)據(jù)。
redis 需要經(jīng)常將內(nèi)存中的數(shù)據(jù)同步到磁盤來保證持久化。redis支持兩種持久化方式,一種是 Snapshotting(快照)也是默認(rèn)方式,另一種是Append-only file(縮寫aof)的方式。先介紹下這兩種dump方式再講講自己遇到的一些現(xiàn)象和想法,前面的內(nèi)容是從網(wǎng)上整理出來的。
Snapshotting
快照是默認(rèn)的持久化方式。這種方式是就是將內(nèi)存中數(shù)據(jù)以快照的方式寫入到二進(jìn)制文件中,默認(rèn)的文件名為dump.rdb??梢酝ㄟ^配置設(shè)置自動(dòng)做快照持久 化的方式。我們可以配置redis在n秒內(nèi)如果超過m個(gè)key被修改就自動(dòng)做快照,下面是默認(rèn)的快照保存配置
save 900 1? #900秒內(nèi)如果超過1個(gè)key被修改,則發(fā)起快照保存
save 300 10 #300秒內(nèi)容如超過10個(gè)key被修改,則發(fā)起快照保存
save 60 10000
下面介紹詳細(xì)的快照保存過程
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)容寫入到臨時(shí)文件。由于os的寫時(shí)復(fù)制機(jī)制(copy on write)父子進(jìn)程會(huì)共享相同的物理頁面,當(dāng)父進(jìn)程處理寫請(qǐng)求時(shí)os會(huì)為父進(jìn)程要修改的頁面創(chuàng)建副本,而不是寫共享的頁面。所以子進(jìn)程的地址空間內(nèi)的數(shù) 據(jù)是fork時(shí)刻整個(gè)數(shù)據(jù)庫的一個(gè)快照。
3.當(dāng)子進(jìn)程將快照寫入臨時(shí)文件完畢后,用臨時(shí)文件替換原來的快照文件,然后子進(jìn)程退出。
client 也可以使用save或者bgsave命令通知redis做一次快照持久化。save操作是在主線程中保存快照的,由于redis是用一個(gè)主線程來處理所有 client的請(qǐng)求,這種方式會(huì)阻塞所有client請(qǐng)求。所以不推薦使用。另一點(diǎn)需要注意的是,每次快照持久化都是將內(nèi)存數(shù)據(jù)完整寫入到磁盤一次,并不 是增量的只同步臟數(shù)據(jù)。如果數(shù)據(jù)量大的話,而且寫操作比較多,必然會(huì)引起大量的磁盤io操作,可能會(huì)嚴(yán)重影響性能。
另外由于快照方式是在一定間隔時(shí)間做一次的,所以如果redis意外down掉的話,就會(huì)丟失最后一次快照后的所有修改。如果應(yīng)用要求不能丟失任何修改的話,可以采用aof持久化方式。下面介紹
Append-only file
aof 比快照方式有更好的持久化性,是由于在使用aof持久化方式時(shí),redis會(huì)將每一個(gè)收到的寫命令都通過write函數(shù)追加到文件中(默認(rèn)是 appendonly.aof)。當(dāng)redis重啟時(shí)會(huì)通過重新執(zhí)行文件中保存的寫命令來在內(nèi)存中重建整個(gè)數(shù)據(jù)庫的內(nèi)容。當(dāng)然由于os會(huì)在內(nèi)核中緩存 write做的修改,所以可能不是立即寫到磁盤上。這樣aof方式的持久化也還是有可能會(huì)丟失部分修改。不過我們可以通過配置文件告訴redis我們想要 通過fsync函數(shù)強(qiáng)制os寫入到磁盤的時(shí)機(jī)。有三種方式如下(默認(rèn)是:每秒fsync一次)
appendonly yes????????????? //啟用aof持久化方式
# appendfsync always????? //每次收到寫命令就立即強(qiáng)制寫入磁盤,最慢的,但是保證完全的持久化,不推薦使用
appendfsync everysec???? //每秒鐘強(qiáng)制寫入磁盤一次,在性能和持久化方面做了很好的折中,推薦
# appendfsync no?? ?//完全依賴os,性能最好,持久化沒保證
aof 的方式也同時(shí)帶來了另一個(gè)問題。持久化文件會(huì)變的越來越大。例如我們調(diào)用incr test命令100次,文件中必須保存全部的100條命令,其實(shí)有99條都是多余的。因?yàn)橐謴?fù)數(shù)據(jù)庫的狀態(tài)其實(shí)文件中保存一條set test 100就夠了。為了壓縮aof的持久化文件。redis提供了bgrewriteaof命令。收到此命令redis將使用與快照類似的方式將內(nèi)存中的數(shù)據(jù) 以命令的方式保存到臨時(shí)文件中,最后替換原來的文件。具體過程如下
1. redis調(diào)用fork ,現(xiàn)在有父子兩個(gè)進(jìn)程
2. 子進(jìn)程根據(jù)內(nèi)存中的數(shù)據(jù)庫快照,往臨時(shí)文件中寫入重建數(shù)據(jù)庫狀態(tài)的命令
3.父進(jìn)程繼續(xù)處理client請(qǐng)求,除了把寫命令寫入到原來的aof文件中。同時(shí)把收到的寫命令緩存起來。這樣就能保證如果子進(jìn)程重寫失敗的話并不會(huì)出問題。
4.當(dāng)子進(jìn)程把快照內(nèi)容寫入已命令方式寫到臨時(shí)文件中后,子進(jìn)程發(fā)信號(hào)通知父進(jìn)程。然后父進(jìn)程把緩存的寫命令也寫入到臨時(shí)文件。
5.現(xiàn)在父進(jìn)程可以使用臨時(shí)文件替換老的aof文件,并重命名,后面收到的寫命令也開始往新的aof文件中追加。
需要注意到是重寫aof文件的操作,并沒有讀取舊的aof文件,而是將整個(gè)內(nèi)存中的數(shù)據(jù)庫內(nèi)容用命令的方式重寫了一個(gè)新的aof文件,這點(diǎn)和快照有點(diǎn)類似。
運(yùn)維上的想法
其實(shí)快照和aof一樣,都使用了Copy-on-write技術(shù)。多次試驗(yàn)發(fā)現(xiàn)每次做數(shù)據(jù)dump的時(shí)候,內(nèi)存都會(huì)擴(kuò)大一倍(關(guān)于這個(gè)問題可以參考我去年寫的redis的內(nèi)存陷阱,很多人用redis做為緩存,數(shù)據(jù)量小,dump耗時(shí)非常短暫,所以不太容易發(fā)現(xiàn)),這個(gè)時(shí)候會(huì)有三種情況:
一:物理內(nèi)存足以滿足,這個(gè)時(shí)候dump非???,性能最好
二:物理內(nèi)存+虛擬內(nèi)存可以滿足,這個(gè)時(shí)候dump速度會(huì)比較慢,磁盤swap繁忙,服務(wù)性能也會(huì)下降。所幸的是經(jīng)過一段比較長的時(shí)候數(shù)據(jù)dump完成了,然后內(nèi)存恢復(fù)正常。這個(gè)情況系統(tǒng)穩(wěn)定性差。
三: 物理內(nèi)存+虛擬內(nèi)存不能滿足,這個(gè)時(shí)候dump一直死著,時(shí)間久了機(jī)器掛掉。這個(gè)情況就是災(zāi)難!
如果數(shù)據(jù)要做持久化又想保證穩(wěn)定性,建議留空一半的物理內(nèi)存。如果覺得無法接受還是有辦法,下面講:
快照和aof雖然都使用Copy-on-write,但有個(gè)不同點(diǎn),快照你無法預(yù)測(cè)redis什么時(shí)候做dump,aof可以通過bgrewriteaof命令控制dump的時(shí)機(jī)。
根據(jù)這點(diǎn)我可以在一個(gè)服務(wù)器上開啟多個(gè)redis節(jié)點(diǎn)(利用多CPU),使用aof的持久化方式。
例 如在24G內(nèi)存的服務(wù)器上開啟3個(gè)節(jié)點(diǎn),每天用bgrewriteaof定期重新整理數(shù)據(jù),每個(gè)節(jié)點(diǎn)dump的時(shí)間都不一樣,這 樣理論上每個(gè)節(jié)點(diǎn)可以消耗6G內(nèi)存,一共使用18G內(nèi)存,另外6G內(nèi)存在單個(gè)節(jié)點(diǎn)dump時(shí)用到,內(nèi)存一下多利用了6G! 當(dāng)然節(jié)點(diǎn)開的越多內(nèi)存的利用率也越高。如果帶寬不是問題,節(jié)點(diǎn)數(shù)建議 = CPU數(shù)。
我的應(yīng)用里為了保證高性能,數(shù)據(jù)沒有做dump,也沒有用aof。因?yàn)椴蛔鰀ump發(fā)生的故障遠(yuǎn)遠(yuǎn)低于做dump的時(shí)候,即使數(shù)據(jù)丟失了,自動(dòng)修復(fù)腳本可以馬上數(shù)據(jù)恢復(fù)。畢竟對(duì)海量數(shù)據(jù)redis只能做數(shù)據(jù)分片,那么落到每個(gè)節(jié)點(diǎn)上的數(shù)據(jù)量也不會(huì)很多。
redis的虛擬內(nèi)存建議也不要用,用redis本來就是為了達(dá)到變態(tài)的性能,虛擬內(nèi)存、aof看起來都有些雞肋。
現(xiàn)在還離不開redis,因?yàn)樗膍get是現(xiàn)在所有db里性能最好的,以前也考慮過用tokyocabinet hash方式做mget,性能不給力。直接用redis,基本上單個(gè)redis節(jié)點(diǎn)mget可以達(dá)到10W/s
糾錯(cuò)
之前說過redis做數(shù)據(jù)dump的時(shí)候內(nèi)容會(huì)擴(kuò)大一倍,后來我又做了些測(cè)試,發(fā)現(xiàn)有些地方說的不對(duì)。
top 命令并不是反映真實(shí)的內(nèi)存占用情況,在top里盡管fork出來的子進(jìn)程占了和父進(jìn)程一樣的內(nèi)存,但是當(dāng)做dump的時(shí)候沒有寫操作,實(shí)際使 用的是同一份內(nèi)存的數(shù)據(jù)。當(dāng)有寫操作的時(shí)候內(nèi)存才會(huì)真實(shí)的擴(kuò)大(具體是不是真實(shí)的擴(kuò)大一倍不確定,可能數(shù)據(jù)是按照頁分片的),這才是真正的Copy- on-write。
基于這點(diǎn)在做數(shù)據(jù)持久化會(huì)更加靈活。