Redis持久化機(jī)制
Redis 的數(shù)據(jù)全部在內(nèi)存里,如果突然宕機(jī),數(shù)據(jù)就會(huì)全部丟失,因此必須有一種機(jī)制來(lái)保證 Redis 的數(shù)據(jù)不會(huì)因?yàn)楣收隙鴣G失,這種機(jī)制就是 Redis 的持久化機(jī)制。
什么是持久化?
就是把內(nèi)存里的數(shù)據(jù)保存到硬盤(pán)上。
必須使用數(shù)據(jù)持久化嗎?
Redis的數(shù)據(jù)持久化機(jī)制是可以關(guān)閉的。如果你只把Redis作為緩存服務(wù)使用,Redis中存儲(chǔ)的所有數(shù)據(jù)都不是該數(shù)據(jù)的主體而僅僅是同步過(guò)來(lái)的備份,那么可以關(guān)閉Redis的數(shù)據(jù)持久化機(jī)制。
但通常來(lái)說(shuō),仍然建議至少開(kāi)啟RDB方式的數(shù)據(jù)持久化,因?yàn)椋?br>
①數(shù)據(jù)量不是非常大時(shí),RDB方式的持久化幾乎不損耗Redis本身的性能,因?yàn)镽edis父進(jìn)程持久化時(shí)只需要fork一個(gè)子進(jìn)程,這個(gè)子進(jìn)程可以共享主進(jìn)程的所有內(nèi)存數(shù)據(jù),子進(jìn)程會(huì)去讀取主進(jìn)程的內(nèi)存數(shù)據(jù),并把它們寫(xiě)入RDB文件。
②Redis無(wú)論因?yàn)槭裁丛虬l(fā)送故障,重啟時(shí)能夠自動(dòng)恢復(fù)到上一次RDB快照中記錄的數(shù)據(jù)(自動(dòng)加載RDB文件)。這省去了手工從其他數(shù)據(jù)源(如數(shù)據(jù)庫(kù))同步數(shù)據(jù)的過(guò)程,而且要比其他任何的數(shù)據(jù)恢復(fù)方式都要快。
③服務(wù)器的硬盤(pán)都是T級(jí)別的,幾個(gè)G的數(shù)據(jù)影響忽略不計(jì)。
Redis 不同于 Memcached 的很重要一點(diǎn)就是,Redis 支持持久化,而且支持三種不同的持久化策略。
1.RDB
Redis提供了兩個(gè)命令來(lái)生成 RDB 文件:
- save:在主進(jìn)程中執(zhí)行,會(huì)導(dǎo)致寫(xiě)請(qǐng)求阻塞。
- bgsave:fork一個(gè)子進(jìn)程,專(zhuān)門(mén)用于寫(xiě)入 RDB 文件,避免了主進(jìn)程的阻塞。
為了快照而阻塞寫(xiě)請(qǐng)求,這是系統(tǒng)無(wú)法接受的,因此Redis借助操作系統(tǒng)提供的寫(xiě)時(shí)復(fù)制技術(shù)(Copy-On-Write, COW),在執(zhí)行快照的同時(shí),正常處理寫(xiě)操作。
Redis在執(zhí)行持久化時(shí),會(huì)fork出一個(gè)bgsave子進(jìn)程,這個(gè)子進(jìn)程可以共享主進(jìn)程的所有內(nèi)存數(shù)據(jù),bgsave子進(jìn)程運(yùn)行后,會(huì)去讀取主進(jìn)程的內(nèi)存數(shù)據(jù),并把它們寫(xiě)入RDB文件。
有小伙伴問(wèn),為什么要fork一個(gè)子線程?
redis是單線程程序,若單線程同時(shí)在服務(wù)線上的請(qǐng)求還需要進(jìn)行文件IO操作,這不僅影響性能而且還會(huì)阻塞線上業(yè)務(wù),因此這里主進(jìn)程fork出一個(gè)進(jìn)程,fork出的這個(gè)進(jìn)程去完成快照操作。
快照持久化是 Redis 默認(rèn)采用的持久化方式,我們可以根據(jù)業(yè)務(wù)需求配置下面參數(shù):
save 900 1 #每900秒(15分鐘)至少有1個(gè)key發(fā)生變化,Redis就會(huì)自動(dòng)觸發(fā)BGSAVE命令創(chuàng)建快照。
save 300 10 #每300秒(5分鐘)至少有10個(gè)key發(fā)生變化,Redis就會(huì)自動(dòng)觸發(fā)BGSAVE命令創(chuàng)建快照。
save 60 10000 #每60秒(1分鐘)至少有10000個(gè)key發(fā)生變化,Redis就會(huì)自動(dòng)觸發(fā)BGSAVE命令創(chuàng)建快照。
key發(fā)生變化(key數(shù)據(jù)添加、修改、刪除)
觸發(fā)快照的幾種方式:
①服務(wù)器正常關(guān)閉時(shí),會(huì)照一次快照 ./bin/redis-cli shutdown
②key滿足一定條件,會(huì)照一次快照(通過(guò)上述Redis.conf配置)
③通過(guò)BGSAVE命令(在redis中執(zhí)行)手動(dòng)觸發(fā)RDB快照保存
優(yōu)點(diǎn):
①RDB文件緊湊,體積小,網(wǎng)絡(luò)傳輸快,適合全量復(fù)制。
②與AOF方式相比,通過(guò)RDB文件恢復(fù)數(shù)據(jù)比較快更快。
③RDB最大化了Redis的性能,因?yàn)镽edis父進(jìn)程持久化時(shí)只需要fork一個(gè)子進(jìn)程,這個(gè)子進(jìn)程可以共享主進(jìn)程的所有內(nèi)存數(shù)據(jù),子進(jìn)程會(huì)去讀取主進(jìn)程的內(nèi)存數(shù)據(jù),并把它們寫(xiě)入RDB文件。
缺點(diǎn):
①快照是定期生成的,所有在 Redis 故障時(shí)或多或少會(huì)丟失一部分?jǐn)?shù)據(jù)。
②當(dāng)數(shù)據(jù)量比較大時(shí),fork 的過(guò)程是非常耗時(shí)的,fork 子進(jìn)程時(shí)是會(huì)阻塞的,在這期間 Redis 是不能響應(yīng)客戶端的請(qǐng)求的。
2.AOF
Redis會(huì)把每一個(gè)寫(xiě)請(qǐng)求都記錄在一個(gè)日志文件里,在Redis重啟時(shí),會(huì)把AOF文件中記錄的所有寫(xiě)操作順序執(zhí)行一遍,確保數(shù)據(jù)恢復(fù)到最新。
Redis 會(huì)在收到客戶端修改指令后,先進(jìn)行參數(shù)校驗(yàn),如果沒(méi)問(wèn)題,就立即將該指令文本存儲(chǔ)到 AOF 日志中,也就是先存到磁盤(pán),然后再執(zhí)行指令。這樣即使遇到突發(fā)宕機(jī),已經(jīng)存儲(chǔ)到 AOF 日志的指令進(jìn)行重放一下就可以恢復(fù)到宕機(jī)前的狀態(tài)。
日志文件太大怎么辦?
AOF 日志在長(zhǎng)期的運(yùn)行過(guò)程中會(huì)變的很大,Redis重啟時(shí)需要加載 AOF 日志進(jìn)行指令重放,此時(shí)這個(gè)過(guò)程就會(huì)非常耗時(shí)。 所以需要定期進(jìn)行AOF 重寫(xiě),給 AOF 日志進(jìn)行瘦身。
AOF如何重寫(xiě)?
Redis 提供了 bgrewriteaof 指令用于對(duì) AOF 日志進(jìn)行瘦身。每次執(zhí)行重寫(xiě)時(shí),主進(jìn)程 fork 出一個(gè)bgrewriteaof 子進(jìn)程,會(huì)把主進(jìn)程的內(nèi)存拷貝一份給 bgrewriteaof 子進(jìn)程,對(duì)內(nèi)存進(jìn)行遍歷轉(zhuǎn)換成一系列 Redis 的操作指令,序列化到一個(gè)新的 AOF 日志文件中。序列化完畢后再將操作期間發(fā)生的增量 AOF 日志追加到這個(gè)新的 AOF 日志文件中,追加完畢后就立即替代舊的 AOF 日志文件了,瘦身工作就完成了。
Redis提供了AOF rewrite功能,可以重寫(xiě)AOF文件,只保留能夠把數(shù)據(jù)恢復(fù)到最新?tīng)顟B(tài)的最小寫(xiě)操作集。
AOF 重寫(xiě)可以通過(guò)bgrewriteaof命令(在redis里執(zhí)行)觸發(fā),也可以配置Redis定期自動(dòng)進(jìn)行:
## Redis在每次AOF rewrite時(shí),會(huì)記錄完成rewrite后的AOF日志大小,當(dāng)AOF日志大小在該基礎(chǔ)上增長(zhǎng)了100%后,自動(dòng)進(jìn)行AOF rewrite。同時(shí)如果增長(zhǎng)的大小沒(méi)有達(dá)到64mb,則不會(huì)進(jìn)行rewrite。
auto-aof-rewrite-percentage 100
auto-aof-rewrite-min-size 64mb
AOF默認(rèn)是關(guān)閉的,如果需要開(kāi)啟,需要在redis.conf配置文件中配置:
appendonly yes
AOF提供三種fsync配置,always/everysec/no,通過(guò)配置項(xiàng)appendfsync指定,默認(rèn)是everysec。
appendfsync always # 每寫(xiě)入一條日志就進(jìn)行一次fsync操作,數(shù)據(jù)安全性最高,但速度最慢(每次有數(shù)據(jù)修改發(fā)生時(shí)都會(huì)寫(xiě)入AOF文)
appendfsync everysec # 折中的做法,交由后臺(tái)線程每秒fsync一次(每秒鐘同步一次,該策略為AOF的缺省策略)
appendfsync no # 不進(jìn)行fsync,將flush文件的時(shí)機(jī)交給OS決定,速度最快(從不同步。高效但是數(shù)據(jù)不會(huì)被持久化)
優(yōu)點(diǎn):
①數(shù)據(jù)安全性高,可以根據(jù)業(yè)務(wù)需求配置fsync策略
②AOF文件易讀,可修改,在進(jìn)行了某些錯(cuò)誤的數(shù)據(jù)清除操作后,只要AOF文件沒(méi)有rewrite,就可以把AOF文件備份出來(lái),把錯(cuò)誤命令刪除,然后恢復(fù)數(shù)據(jù)
缺點(diǎn):
①AOF方式生成的日志文件太大,即使通過(guò)AFO重寫(xiě),文件體積仍然很大
②數(shù)據(jù)恢復(fù)速度比RDB慢
3.混合持久化
如果我們采用 RDB 持久化會(huì)丟失一段時(shí)間數(shù)據(jù)。如果我們采用 AOF 持久化,AOF日志較大,重放比較慢。
Redis 4.0 為了解決這個(gè)問(wèn)題,支持混合持久化。將 RDB 文件的內(nèi)容和增量的 AOF 日志文件存在一起。
混合持久化同樣也是通過(guò) bgrewriteaof 完成的,不同的是當(dāng)開(kāi)啟混合持久化時(shí),fork出的子進(jìn)程先將共享的內(nèi)存副本全量的以 RDB 方式寫(xiě)入 AOF 文件,然后在將重寫(xiě)緩沖區(qū)的增量命令以 AOF 方式寫(xiě)入到文件,寫(xiě)入完成后通知主進(jìn)程更新統(tǒng)計(jì)信息,并將新的含有RDB格式和 AOF 格式的 AOF 文件替換舊的的 AOF 文件。簡(jiǎn)單的說(shuō):新的AOF文件前半段是RDB格式的全量數(shù)據(jù)后半段是AOF格式的增量數(shù)據(jù)。
于是在 Redis 重啟的時(shí)候,可以先加載 rdb 的內(nèi)容,然后再重放增量 AOF 日志就可以完全替代之前的 AOF 全量文件重放,重啟效率因此大幅得到提升。
4.實(shí)戰(zhàn)經(jīng)驗(yàn)
快照需要fork子進(jìn)程的方式進(jìn)行的,它是一個(gè)比較耗資源的操作。(當(dāng)數(shù)據(jù)量非常大時(shí),fork會(huì)很耗時(shí),需要大概幾百毫秒甚至1秒,fork時(shí)父進(jìn)程是阻塞的,不能正常服務(wù)redis讀寫(xiě)請(qǐng)求)
AOF 的 fsync 是一個(gè)耗時(shí)的 IO 操作,它會(huì)降低 Redis 性能,同時(shí)也會(huì)增加系統(tǒng) IO 負(fù)擔(dān)
所以通常 Redis 的主節(jié)點(diǎn)是不會(huì)進(jìn)行持久化操作,持久化操作主要在從節(jié)點(diǎn)進(jìn)行。從節(jié)點(diǎn)是備份節(jié)點(diǎn),沒(méi)有來(lái)自客戶端請(qǐng)求的壓力,它的操作系統(tǒng)資源往往比較充沛。
但是如果出現(xiàn)網(wǎng)絡(luò)分區(qū),從節(jié)點(diǎn)長(zhǎng)期連不上主節(jié)點(diǎn),就會(huì)出現(xiàn)數(shù)據(jù)不一致的問(wèn)題,特別是在網(wǎng)絡(luò)分區(qū)出現(xiàn)的情況下又不小心主節(jié)點(diǎn)宕機(jī)了,那么數(shù)據(jù)就會(huì)丟失,所以在生產(chǎn)環(huán)境要做好實(shí)時(shí)監(jiān)控工作,保證網(wǎng)絡(luò)暢通或者能快速修復(fù)。另外還應(yīng)該再增加一個(gè)從節(jié)點(diǎn)以降低網(wǎng)絡(luò)分區(qū)的概率,只要有一個(gè)從節(jié)點(diǎn)數(shù)據(jù)同步正常,數(shù)據(jù)也就不會(huì)輕易丟失。