一、兩者比較
RDB的數(shù)據(jù)不實時,同時使用兩者時服務(wù)器重啟也只會找AOF文件。那要不要只使用AOF呢?作者建議不要,因為RDB更適合用于備份數(shù)據(jù)庫(AOF在不斷變化不好備份),快速重啟,而且不會有AOF可能潛在的bug,留著作為一個萬一的手段。
1.性能:Snapshot方式的性能是要明顯高于AOF方式的,原因有兩點:
1)采用二進制方式存儲數(shù)據(jù),數(shù)據(jù)文件比較小,加載快速.
2)存儲的時候是按照配置中的save策略來存儲,每次都是聚合很多數(shù)據(jù)批量存儲,寫入的效率很好,而AOF則一般都是工作在實時存儲或者準實時模式下。相對來說存儲的頻率高,效率卻偏低。
3) 對于redis的服務(wù)進程而言,在開始持久化時,它唯一需要做的只是fork出子進程,之后再由子進程完成這些持久化的工作,這樣就可以極大的避免服務(wù)進程執(zhí)行IO操作了。
2.數(shù)據(jù)安全:AOF數(shù)據(jù)安全性高于Snapshot存儲,原因:
Snapshot存儲是基于累計批量的思想,也就是說在允許的情況下,累計的數(shù)據(jù)越多那么寫入效率也就越高,但數(shù)據(jù)的累計是靠時間的積累完成的,那么如果在長時間數(shù)據(jù)不寫入RDB,但Redis又遇到了崩潰,那么沒有寫入的數(shù)據(jù)就無法恢復了,但是AOF方式偏偏相反,根據(jù)AOF配置的存儲頻率的策略可以做到最少的數(shù)據(jù)丟失和較高的數(shù)據(jù)恢復能力。
RDB優(yōu)勢:
1). 備份策略方便:一旦采用該方式,那么你的整個Redis數(shù)據(jù)庫將只包含一個文件,這對于文件備份而言是非常完美的。比如,你可能打算每個小時歸檔一次最近24小時的數(shù)據(jù),同時還要每天歸檔一次最近30天的數(shù)據(jù)。通過這樣的備份策略,一旦系統(tǒng)出現(xiàn)災難性故障,我們可以非常容易的進行恢復。
2). 災難恢復方便:,RDB是非常不錯的選擇。因為我們可以非常輕松的將一個單獨的文件壓縮后再轉(zhuǎn)移到其它存儲介質(zhì)上。
3). 性能最大化:對于Redis的服務(wù)進程而言,在開始持久化時,它唯一需要做的只是fork出子進程,之后再由子進程完成這些持久化的工作,這樣就可以極大的避免服務(wù)進程執(zhí)行IO操作了。
4). 啟動效率高:相比于AOF機制,如果數(shù)據(jù)集很大,RDB的啟動效率會更高。
RDB缺點:
1). 數(shù)據(jù)的可靠性不高,沒辦法做到實時持久化:如果你想保證數(shù)據(jù)的高可用性,即最大限度的避免數(shù)據(jù)丟失,那么RDB將不是一個很好的選擇。因為系統(tǒng)一旦在定時持久化之前出現(xiàn)宕機現(xiàn)象,此前沒有來得及寫入磁盤的數(shù)據(jù)都將丟失。
2). 影響性能:由于RDB是通過fork子進程來協(xié)助完成數(shù)據(jù)持久化工作的,因此,如果當數(shù)據(jù)集較大時,可能會導致整個服務(wù)器停止服務(wù)幾百毫秒,甚至是1秒鐘。
3)版本兼容RDB格式問題
AOF的優(yōu)點:
1). 數(shù)據(jù)安全性:該機制可以帶來更高的數(shù)據(jù)安全性,即數(shù)據(jù)持久性。Redis中提供了3中同步策略,即每秒同步、每修改同步和不同步。事實上,每秒同步也是異步完成的,其效率也是非常高的,所差的是一旦系統(tǒng)出現(xiàn)宕機現(xiàn)象,那么這一秒鐘之內(nèi)修改的數(shù)據(jù)將會丟失。而每修改同步,我們可以將其視為同步持久化,即每次發(fā)生的數(shù)據(jù)變化都會被立即記錄到磁盤中。可以預見,這種方式在效率上是最低的。
2). 數(shù)據(jù)一致性:由于該機制對日志文件的寫入操作采用的是Append模式,因此在寫入過程中即使出現(xiàn)宕機現(xiàn)象,也不會破壞日志文件中已經(jīng)存在的內(nèi)容。然而如果我們本次操作只是寫入了一半數(shù)據(jù)就出現(xiàn)了系統(tǒng)崩潰問題,不用擔心,在Redis下一次啟動之前,我們可以通過redis-check-aof工具來幫助我們解決數(shù)據(jù)一致性的問題。
3). 如果日志過大,Redis可以自動啟用rewrite機制。即Redis以append模式不斷的將修改數(shù)據(jù)寫入到老的磁盤文件中,同時Redis還會創(chuàng)建一個新的文件用于記錄此期間有哪些修改命令被執(zhí)行。因此在進行rewrite切換時可以更好的保證數(shù)據(jù)安全性。
4). AOF包含一個格式清晰、易于理解的日志文件用于記錄所有的修改操作。事實上,我們也可以通過該文件完成數(shù)據(jù)的重建。
AOF的缺點:
1). 恢復速度慢:對于相同數(shù)量的數(shù)據(jù)集而言,AOF文件通常要大于RDB文件。RDB 在恢復大數(shù)據(jù)集時的速度比 AOF 的恢復速度要快。
2). 性能低:根據(jù)同步策略的不同,AOF在運行效率上往往會慢于RDB。總之,每秒同步策略的效率是比較高的,同步禁用策略的效率和RDB一樣高效。
二者選擇的標準,就是看系統(tǒng)是愿意犧牲一些性能,換取更高的緩存一致性(aof),還是愿意寫操作頻繁的時候,不啟用備份來換取更高的性能,待手動運行save的時候,再做備份(rdb)。rdb這個就更有些 eventually consistent的意思了。
二、Redis持久化磁盤IO方式及其帶來的問題
1、RDB持久化可能導致系統(tǒng)內(nèi)存不足導致redis 被 oom kill
發(fā)現(xiàn)Redis在物理內(nèi)存使用比較多,但還沒有超過實際物理內(nèi)存總?cè)萘繒r就會發(fā)生不穩(wěn)定甚至崩潰的問題。
redis持久化AOF重寫和RDB寫入都是通過fork產(chǎn)生子進程來操作,AOF重寫和RDB寫入都是通過fork產(chǎn)生子進程來操作,理論上需要兩倍的內(nèi)存來完成持久化操作,但linux有寫時復制機制(copy-on-write),父子進程會共享相同的物理內(nèi)存頁(只有有寫入的臟頁會被復制), 當父進程處理寫請求時會把要修改的頁創(chuàng)建副本,而子進程在fork操作過程中共享整個父進程內(nèi)存快照。避免在大量寫入時做子進程重寫操作,這樣將導致父進程維護大量頁副本,造成內(nèi)存消耗。
但是由于Redis的持久化使用了Buffer IO,所謂Buffer IO是指Redis對持久化文件的寫入和讀取操作都會使用物理內(nèi)存的Page Cache, 而大多數(shù)數(shù)據(jù)庫系統(tǒng)會使用Direct IO來繞過這層Page Cache并自行維護一個數(shù)據(jù)的Cache,而當Redis的持久化文件過大(尤其是快照RDB文件),并對其進行讀寫時,磁盤文件中的數(shù)據(jù)都會被加載到物理內(nèi)存中作為操作系統(tǒng)對該文件的一層Cache,而這層Cache的數(shù)據(jù)與Redis內(nèi)存中管理的數(shù)據(jù)實際是重復存儲的,雖然內(nèi)核在物理內(nèi)存緊張時會做Page Cache的剔除工作,但內(nèi)核很可能認為某塊Page Cache更重要,而讓你的進程開始Swap ,這時你的系統(tǒng)就會開始出現(xiàn)不穩(wěn)定或者崩潰了。我們的經(jīng)驗是當你的Redis物理內(nèi)存使用超過內(nèi)存總?cè)萘康?/5時就會開始比較危險了。
下圖是Redis在讀取或者寫入快照文件dump.rdb后的內(nèi)存數(shù)據(jù)圖:
2、持久化Fork引起redis主進程阻塞
AOF重寫和RDB寫入都是通過fork產(chǎn)生子進程來操作,子進程占用內(nèi)存大小等同于父進程,理論上需要兩倍的內(nèi)存來完成持久化操作,但Linux有寫時復制機制(copy-on-write)。父子進程會共享相同的物理內(nèi)存頁,當父進程處理寫請求時會把要修改的頁創(chuàng)建副本,而子進程在fork操作過程中共享整個父進程內(nèi)存快照。避免在大量寫入時做子進程重寫操作,這樣將導致父進程維護大量頁副本,造成內(nèi)存消耗。
測試把benchmark的11G數(shù)據(jù)寫成一個1.3的RDB文件,或者等大的AOF文件rewrite,需要80秒,在redis-cli info中可查看。啟動時載入一個AOF或RDB文件的速度與上面寫入時相同,在log中可查看。
Fork一個使用了大量內(nèi)存的進程也要時間,大約10ms per GB的樣子,但Xen在EC2上是讓人郁悶的239ms (KVM和VMWare貌似沒有這個毛病),各種系統(tǒng)的對比,Info指令里的latest_fork_usec顯示上次花費的時間。
3、在AOF持久化重寫時,AOF重寫緩沖區(qū)的指令追加新文件和改名替換造成阻塞:
在AOF持久化過程中,所有新來的寫入請求依然會被寫入舊的AOF文件,同時放到buffer中,當rewrite完成后,會在主線程把這部分內(nèi)容合并到臨時文件中之后才rename成新的AOF文件,所以rewrite過程中會不斷打印”Background AOF buffer size: xx MB, Background AOF buffer size: xxMB”,這個合并的過程是阻塞的,如果你產(chǎn)生了280MB的buffer,在100MB/s的傳統(tǒng)硬盤 上,Redis就要阻塞2.8秒!!!
4、持久化的系統(tǒng)IO阻塞redis主進程的問題
持久化很容易造成阻塞,不管是AOF rewrite,還是RDB bgsave,都有可能會出現(xiàn)阻塞,原因是一般情況下,如果Redis服務(wù)設(shè)置了appendfsync everysec, 主進程每秒鐘便會調(diào)用fsync, 將數(shù)據(jù)寫到存儲磁盤里. 但由于服務(wù)器的重寫子進程正在進行大量IO操作, 導致主進程fsync/操作被阻塞, 最終導致Redis主進程阻塞.解決方案這里就不多說了
5、Redis持久化性能調(diào)整
因為RDB文件只用作后備用途,建議只在Slave上持久化RDB文件,而且只要15分鐘備份一次就夠了,只保留save 900 1這條規(guī)則。
如果啟用AOF,好處是在最惡劣情況下也只會丟失不超過兩秒數(shù)據(jù),啟動腳本較簡單只load自己的AOF文件就可以了。代價一是帶來了持續(xù)的IO,二是AOF rewrite的最后將rewrite過程中產(chǎn)生的新數(shù)據(jù)寫到新文件造成的阻塞幾乎是不可避免的。只要硬盤許可,應(yīng)該盡量減少AOF rewrite的頻率,AOF重寫的基礎(chǔ)大小默認值64M太小了,可以設(shè)到5G以上。默認超過原大小100%大小時重寫可以改到適當?shù)臄?shù)值,比如之前的 benchmark每個小時會產(chǎn)生40G大小的AOF文件,如果硬盤能撐到半夜系統(tǒng)閑時才用cron調(diào)度bgaofrewrite就好了。
如 果不Enable AOF ,僅靠Master-Slave Replication 實現(xiàn)高可用性也可以。能省掉一大筆IO也減少了rewrite時帶來的系統(tǒng)波動。代價是如果Master/Slave同時倒掉,會丟失十幾分鐘的數(shù)據(jù),啟 動腳本也要比較兩個Master/Slave中的RDB文件,載入較新的那個。新浪微博就選用了這種架構(gòu),見Tim的博客
三、 實際問題
1、Trouble Shooting —— Enable AOF可能導致整個Redis被Block住,在2.6.12版之前
現(xiàn)象描述:當AOF rewrite 15G大小的內(nèi)存時,Redis整個死掉的樣子,所有指令甚至包括slave發(fā)到master的ping,redis-cli info都不能被執(zhí)行。
原因分析:
官方文檔,由IO產(chǎn)生的Latency詳細分析, 已經(jīng)預言了悲劇的發(fā)生,但一開始沒留意。
Redis為求簡單,采用了單請求處理線程結(jié)構(gòu)。
打開AOF持久化功能后, Redis處理完每個事件后會調(diào)用write(2)將變化寫入kernel的buffer,如果此時write(2)被阻塞,Redis就不能處理下一個事件。
Linux規(guī)定執(zhí)行write(2)時,如果對同一個文件正在執(zhí)行fdatasync(2)將kernel buffer寫入物理磁盤,或者有system wide sync在執(zhí)行,write(2)會被block住,整個Redis被block住。
如 果系統(tǒng)IO繁忙,比如有別的應(yīng)用在寫盤,或者Redis自己在AOF rewrite或RDB snapshot(雖然此時寫入的是另一個臨時文件,雖然各自都在連續(xù)寫,但兩個文件間的切換使得磁盤磁頭的尋道時間加長),就可能導致 fdatasync(2)遲遲未能完成從而block住write(2),block住整個Redis。
為了更清晰的看到fdatasync(2)的執(zhí)行時長,可以使用”strace -p (pid of redis server) -T -e -f trace=fdatasync”,但會影響系統(tǒng)性能。
Redis 提供了一個自救的方式,當發(fā)現(xiàn)文件有在執(zhí)行fdatasync(2)時,就先不調(diào)用write(2),只存在cache里,免得被block。但如果已經(jīng) 超過兩秒都還是這個樣子,則會硬著頭皮執(zhí)行write(2),即使redis會被block住。此時那句要命的log會打印:“Asynchronous AOF fsync is taking too long (disk is busy?). Writing the AOF buffer without waiting for fsync to complete, this may slow down Redis.” 之后用redis-cli INFO可以看到aof_delayed_fsync的值被加1。
因此,對于fsync設(shè)為everysec時丟失數(shù) 據(jù)的可能性的最嚴謹說法是:如果有fdatasync在長時間的執(zhí)行,此時redis意外關(guān)閉會造成文件里不多于兩秒的數(shù)據(jù)丟失。如果fdatasync 運行正常,redis意外關(guān)閉沒有影響,只有當操作系統(tǒng)crash時才會造成少于1秒的數(shù)據(jù)丟失。
解決方法:
然后提了個issue,AOF rewrite時定時也執(zhí)行一下fdatasync嘛, antirez三分鐘后就回復了,新版中,AOF rewrite時32M就會重寫主動調(diào)用fdatasync。
2、Redis log 一直在報錯:background: fork: Cannot allocate memory
Redis數(shù)據(jù)回寫機制
數(shù)據(jù)回寫分同步和異步兩種方式
同步回寫(SAVE), 主進程直接向磁盤回寫數(shù)據(jù). 在數(shù)據(jù)量大的情況下會導致系統(tǒng)假死很長時間
異步回寫(BGSAVE), 主進程fork后, 復制自身并通過這個新的進程回寫磁盤, 回寫結(jié)束后新進程自行關(guān)閉.
由于 BGSAVE 不需要主進程阻塞, 系統(tǒng)也不會假死, 一般會采用 BGSAVE 來實現(xiàn)數(shù)據(jù)回寫.
故障分析
在小內(nèi)存的進程上做fork, 不需要太多資源. 但當這個進程的內(nèi)存空間以G為單位時, fork就成為一件很恐怖的操作.
在16G內(nèi)存的足跡上fork 14G的進程, 系統(tǒng)肯定Cannot allocate memory.
主機的Redis 改動的越頻繁 fork進程也越頻繁, 所以一直在Cannot allocate memory
解決方案:
1、redis有個默認的選項:
stop-writes-on-bgsave-error yes
這個選項默認情況下,如果在RDB snapshots持久化過程中出現(xiàn)問題,設(shè)置該參數(shù)后,Redis是不允許用戶進行任何更新操作。
不徹底的解決方式是,將這個選項改為false,但是這樣只是當redis寫硬盤快照出錯時,可以讓用戶繼續(xù)做更新操作,但是寫硬盤仍然是失敗的;
2、徹底的解決方式:直接修改內(nèi)核參數(shù) vm.overcommit_memory
直接修改內(nèi)核參數(shù) vm.overcommit_memory = 1, Linux內(nèi)核會根據(jù)參數(shù)vm.overcommit_memory參數(shù)的設(shè)置決定是否放行。
編輯/etc/sysctl.conf ,改vm.overcommit_memory=1
vm.overcommit_memory = 1,直接放行
vm.overcommit_memory = 0:則比較 此次請求分配的虛擬內(nèi)存大小和系統(tǒng)當前空閑的物理內(nèi)存加上swap,決定是否放行。
vm.overcommit_memory = 2:則會比較進程所有已分配的虛擬內(nèi)存加上此次請求分配的虛擬內(nèi)存和系統(tǒng)當前的空閑物理內(nèi)存加上swap,決定是否放行。
執(zhí)行sysctl -p使其生效;
vm.overcommit_memory 的作用
Linux對大部分申請內(nèi)存的請求都回復"yes",以便能跑更多更大的程序。因為申請內(nèi)存后,并不會馬上使用內(nèi)存,將這些不會使用的空閑內(nèi)存分配給其它程序使用,以提高內(nèi)存利用率,這種技術(shù)叫做Overcommit。一般情況下,當所有程序都不會用到自己申請的所有內(nèi)存時,系統(tǒng)不會出問題,但是如果程序隨著運行,需要的內(nèi)存越來越大,在自己申請的大小范圍內(nèi),不斷占用更多內(nèi)存,直到超出物理內(nèi)存,當linux發(fā)現(xiàn)內(nèi)存不足時,會發(fā)生OOM killer(OOM=out-of-memory)。它會選擇殺死一些進程(用戶態(tài)進程,不是內(nèi)核線程,哪些占用內(nèi)存越多,運行時間越短的進程越有可能被殺掉),以便釋放內(nèi)存。
當oom-killer發(fā)生時,linux會選擇殺死哪些進程?選擇進程的函數(shù)是oom_badness函數(shù)(在mm/oom_kill.c中),該函數(shù)會計算每個進程的點數(shù)(0~1000)。點數(shù)越高,這個進程越有可能被殺死。每個進程的點數(shù)跟(/proc/<pid>/oom_adj)oom_score_adj有關(guān),而且oom_score_adj可以被設(shè)置(-1000最低,1000最高)。
當發(fā)生oom killer時,會將記錄在系統(tǒng)日志中/var/log/messages