redis rdb aof区别_Redis(三):持久化RDB,fork.copyonwrite,AOF,RDBamp;AOF混合使用
Redis的數(shù)據(jù)全部在內(nèi)存里,如果突然宕機(jī),數(shù)據(jù)就會(huì)全部丟失,因此必須有一種機(jī)制來(lái)保證Redis的數(shù)據(jù)不會(huì)因?yàn)楣收隙鴣G失,這種機(jī)制就是Redis的持久化機(jī)制。
Redis的持久化有兩種,第一種是快照,第二種是AOF日志。快照是一次性全量備份,AOF日志是連續(xù)的增量備份。
第一種快照是內(nèi)存數(shù)據(jù)的二進(jìn)制序列化形式,在存儲(chǔ)上非常緊湊,而第二種AOF日志記錄的是內(nèi)存數(shù)據(jù)修改的指令記錄文本。
AOF日志在長(zhǎng)期的運(yùn)行過(guò)程中會(huì)變得無(wú)比龐大,數(shù)據(jù)庫(kù)重啟時(shí)需要加載AOF日志進(jìn)行指令重放,這個(gè)時(shí)間就會(huì)很漫長(zhǎng),所以需要定期進(jìn)行AOF重寫(xiě),給AOF日志瘦身。
快照(RDB)的原理:
Redis是單線(xiàn)程程序,這個(gè)線(xiàn)程要同時(shí)負(fù)責(zé):
1、多個(gè)客戶(hù)端socket的并發(fā)讀寫(xiě)操作 2、內(nèi)存數(shù)據(jù)結(jié)構(gòu)的邏輯重寫(xiě)在服務(wù)線(xiàn)上請(qǐng)求的同時(shí),Redis還需要進(jìn)行內(nèi)存快照,內(nèi)存快照要求Redis必須進(jìn)行文件io操作,可是文件io操作不能使用多路復(fù)用的API。
這就意味著單線(xiàn)程在服務(wù)器上請(qǐng)求的同時(shí),還要進(jìn)行文件io操作,而文件io操作會(huì)嚴(yán)重拖累服務(wù)器的性能。
另外,為了不阻塞線(xiàn)上的業(yè)務(wù),Redis需要一邊持久化,一邊相應(yīng)客戶(hù)端的請(qǐng)求。持久化的同時(shí),內(nèi)存數(shù)據(jù)結(jié)構(gòu)還在變化,比如一個(gè)大型的hash字典正在持久化的時(shí)候,這時(shí)一個(gè)請(qǐng)求過(guò)來(lái)把它給刪掉了,或者修改了,可是還沒(méi)持久化完畢,這怎么辦?Redis使用操作系統(tǒng)的多進(jìn)程COW(Copy on Write)機(jī)制來(lái)實(shí)現(xiàn)快照持久化
Fork多進(jìn)程
Redis在持久化的時(shí)候會(huì)調(diào)用glicb的函數(shù)fork產(chǎn)生一個(gè)子進(jìn)程,快照持久化完全交給子進(jìn)程來(lái)處理,父進(jìn)程繼續(xù)處理客戶(hù)端業(yè)務(wù)請(qǐng)求。子進(jìn)程剛剛產(chǎn)生的時(shí)候和父進(jìn)程共享內(nèi)存里的代碼段和數(shù)據(jù)段。
子進(jìn)程做數(shù)據(jù)持久化的時(shí)候,不會(huì)修改現(xiàn)有內(nèi)存數(shù)據(jù)結(jié)構(gòu),它只是對(duì)數(shù)據(jù)結(jié)構(gòu)進(jìn)行遍歷讀取,然后序列化寫(xiě)到磁盤(pán)上。
但是父進(jìn)程不一樣,必須持續(xù)服務(wù)客戶(hù)端的請(qǐng)求,然后對(duì)內(nèi)存數(shù)據(jù)結(jié)構(gòu)進(jìn)行不斷的修改。這個(gè)時(shí)候就會(huì)使用OS的COW機(jī)制來(lái)進(jìn)行數(shù)據(jù)段頁(yè)面的分離。
如上圖所示,數(shù)據(jù)段是由很多操作系統(tǒng)的頁(yè)面組合而成,當(dāng)父進(jìn)程對(duì)其中一個(gè)頁(yè)面的數(shù)據(jù)進(jìn)行修改時(shí),會(huì)將被共享的頁(yè)面復(fù)制一份分離出來(lái),然后對(duì)復(fù)制的這個(gè)頁(yè)面進(jìn)行修改。這時(shí)子進(jìn)程相應(yīng)的頁(yè)面是沒(méi)有變化的,還是進(jìn)程產(chǎn)生時(shí)那一瞬間的數(shù)據(jù)。
隨著父進(jìn)程修改操作的持續(xù)進(jìn)行,越來(lái)越多的共享頁(yè)面被分離出來(lái),內(nèi)存就會(huì)持續(xù)增長(zhǎng),但是也不會(huì)超過(guò)原有數(shù)據(jù)的2倍大小。另外Redis實(shí)例里的冷數(shù)據(jù)占的比例往往是比較高的,所以很少會(huì)出現(xiàn)所有的頁(yè)面都被分離的情況,被分離的往往只有其中一部分頁(yè)面。每個(gè)頁(yè)面的大小只有4KB,一個(gè)Redis實(shí)例里面一般都會(huì)有成千上萬(wàn)個(gè)頁(yè)面。
子進(jìn)程因?yàn)閿?shù)據(jù)沒(méi)有變化,它能看到的內(nèi)存里的數(shù)據(jù)在進(jìn)程產(chǎn)生的那一瞬間就凝固了,再也不會(huì)發(fā)生改變。這也是為何Redis的持久化叫做“快照”的原因了。接下來(lái)子進(jìn)程就可以安心的遍歷數(shù)據(jù),進(jìn)行序列化寫(xiě)到磁盤(pán)中了。
那現(xiàn)在就有一個(gè)問(wèn)題了,父進(jìn)程如果占了10G,那么子進(jìn)程也占10G,redis的空間夠不夠呢?持久化的時(shí)候速度快不快呢?其實(shí)父進(jìn)程和子進(jìn)程只保存了物理機(jī)的內(nèi)存的地址,兩份地址而已,指向的都是內(nèi)存。所就不存在兩個(gè)都占10G了。而父進(jìn)程和子進(jìn)程是通過(guò)fork()出來(lái)的,還有就是子進(jìn)程也不是一下就創(chuàng)建出來(lái),其中的COW是copy on write,意思是寫(xiě)的時(shí)候才復(fù)制地址。創(chuàng)建子進(jìn)程并不發(fā)生復(fù)制,玩的是指針。
例如看一下fork :
輸入 man 2 fork 命令
然后輸入/copy
NOTES
Under Linux, fork() is implemented using copy-on-write pages, so the only
penalty that it incurs is the time and memory required to duplicate the parent’s
page tables, and to create a unique task structure for the child.
Since version 2.3.3, rather than invoking the kernel’s fork() system call, the
glibc fork() wrapper that is provided as part of the NPTL threading implementa-
tion invokes clone(2) with flags that provide the same effect as the traditional
system call. The glibc wrapper invokes any fork handlers that have been estab-
lished using pthread_atfork(3).
在Linux下,fork()是使用寫(xiě)時(shí)復(fù)制的頁(yè)面實(shí)現(xiàn)的,因此它帶來(lái)的唯一損失是復(fù)制父頁(yè)面表和為子頁(yè)面創(chuàng)建唯一的任務(wù)結(jié)構(gòu)所需的時(shí)間和內(nèi)存。自從2.3.3版本以來(lái),作為NPTL線(xiàn)程實(shí)現(xiàn)一部分提供的glibc fork()包裝器沒(méi)有調(diào)用內(nèi)核的fork()系統(tǒng)調(diào)用,而是使用提供與傳統(tǒng)系統(tǒng)調(diào)用相同效果的標(biāo)志調(diào)用clone(2)。glibc包裝器調(diào)用使用pthread_atfork(3)建立的任何fork處理程序。
就是父進(jìn)程進(jìn)行修改的時(shí)候,會(huì)新創(chuàng)建一個(gè),然后指向,而子進(jìn)程還是指向原來(lái)的。
fork是系統(tǒng)調(diào)用,copy on write是內(nèi)核機(jī)制。 redis調(diào)用fork()優(yōu)點(diǎn)是速度快,占用空間小。關(guān)機(jī)維護(hù)的時(shí)候,用save命令,其余的一般都是bgsave達(dá)到60秒了 操作數(shù)達(dá)到10000,滿(mǎn)足其中一個(gè),就會(huì)寫(xiě)rdb。依次類(lèi)推,如果到900秒了,操作數(shù)達(dá)到1筆的時(shí)候,滿(mǎn)足其中一個(gè),就會(huì)寫(xiě)RDB。
Redis默認(rèn)是開(kāi)啟RDB的,如果想關(guān)閉,那么就把下圖中這個(gè)刪除,或者 save ""
那么持久化的RDB文件存儲(chǔ)在哪呢?繼續(xù)往下翻:
RDB的優(yōu)缺點(diǎn):
優(yōu)點(diǎn):1,壓縮后的二進(jìn)制文件,適用于備份、全量復(fù)制,用于災(zāi)難恢復(fù)
2,加載RDB恢復(fù)數(shù)據(jù)遠(yuǎn)快于AOF方式
缺點(diǎn):1,無(wú)法做到實(shí)時(shí)持久化,每次都要?jiǎng)?chuàng)建子進(jìn)程,頻繁操作成本過(guò)高
2,保存后的二進(jìn)制文件,存在老版本不兼容新版本rdb文件的問(wèn)題
3,不支持拉鏈,只有一個(gè)dump.rdb
4,丟失數(shù)據(jù)相對(duì)較多,窗口數(shù)據(jù)容易丟失,8點(diǎn)做了一個(gè)rdb,假設(shè)9點(diǎn)又要做一個(gè)rdb,此時(shí)斷電了,那么就丟了一個(gè)小時(shí)的數(shù)據(jù)。這是全量備份技術(shù)的通用的缺點(diǎn)。
以上是RDB。
針對(duì)RDB不適合實(shí)時(shí)持久化,redis提供了AOF持久化方式來(lái)解決
AOF(append only file )(只會(huì)向文件追加)AOF日志存儲(chǔ)的是Redis服務(wù)器的順序指令序列,AOF日志只記錄對(duì)內(nèi)存進(jìn)行修改的指令記錄,假設(shè)AOF日志記錄了自Redis實(shí)例創(chuàng)建以來(lái)所有的修改性指令序列,那么就可以通過(guò)對(duì)一個(gè)空的Redis實(shí)例順序執(zhí)行所有的指令--也就是“重放”,來(lái)恢復(fù)Redis當(dāng)前實(shí)例的內(nèi)存數(shù)據(jù)結(jié)構(gòu)的狀態(tài)。
Redis會(huì)在收到客戶(hù)端修改指令后,進(jìn)行參數(shù)校驗(yàn)、邏輯處理,如果沒(méi)問(wèn)題,就立即將該指令文本存儲(chǔ)到AOF日志中,也就是說(shuō),先執(zhí)行指令才將日志存盤(pán)。這點(diǎn)不同于hbase等存儲(chǔ)引擎,他們都是先存儲(chǔ)日志再做邏輯處理的。
Redis在長(zhǎng)期運(yùn)行的過(guò)程中,AOF日志會(huì)越來(lái)越長(zhǎng),如果實(shí)例宕機(jī)重啟,重放整個(gè)AOF日志會(huì)非常耗時(shí),導(dǎo)致Redis長(zhǎng)時(shí)間無(wú)法對(duì)外提供服務(wù),所以需要對(duì)AOF日志瘦身。
AOF重寫(xiě):
Redis 提供了 bgrewriteaof 指令用于對(duì)AOF日志進(jìn)行瘦身,原理是開(kāi)辟一個(gè)子進(jìn)程對(duì)內(nèi)存進(jìn)行遍歷,轉(zhuǎn)換成一系列Redis的操作指令,序列化到一個(gè)新的AOF日志文件中。序列化完畢后再將操作期間發(fā)生增量AOF日志追加到這個(gè)新的AOF日志文件中,追加完畢后就立即替代舊的AOF日志文件了,瘦身工作就完成了。而重寫(xiě)的意義是以后加載速度變快,減小AOF體積,比如set k1 a set k1 b set k1 c,就會(huì)在aof文件中出現(xiàn)這些記錄,而此時(shí)k1對(duì)應(yīng)的value是C,a和b是沒(méi)用的,經(jīng)過(guò)bgrewriteaof命令后,aof文件中將不再有a和b的影子,就只保留了k1 c。
fsync:
AOF日志是以文件的形式存在的,當(dāng)程序?qū)OF日志文件進(jìn)行寫(xiě)操作時(shí),實(shí)際上是將內(nèi)容寫(xiě)到了內(nèi)核為文件描述符(File Descriptors ,簡(jiǎn)稱(chēng)fd ,很多fd就是 fds)分配到一個(gè)內(nèi)存緩沖中,然后內(nèi)核會(huì)異步將臟數(shù)據(jù)刷回到磁盤(pán)。這就意味著如果突然宕機(jī),AOF日志內(nèi)容可能還沒(méi)來(lái)得及完全刷到磁盤(pán)中,這個(gè)時(shí)候就會(huì)出現(xiàn)日志丟失,這怎么辦?不急,Linux的glibc提供了 fsync(int fd)函數(shù)可以將指定文件的內(nèi)容強(qiáng)制從內(nèi)核緩存刷到磁盤(pán)。只要Redis進(jìn)程實(shí)時(shí)調(diào)用fsync函數(shù)就可以保證AOF日志不丟失。但是fsync是磁盤(pán)io操作,很慢,如果Redis執(zhí)行一條指令就要 fsync 一次,那么Redis高性能的地位就不保了。所以在生產(chǎn)環(huán)境的服務(wù)器中,Redis通常是每隔1s左右執(zhí)行一次 fsync 操作,這個(gè)1s周期是可以配置的。這是在數(shù)據(jù)安全性和性能之間做的一個(gè)折中,在保持高性能的同時(shí),盡可能是數(shù)據(jù)少丟失。
在Redis的主節(jié)點(diǎn)不會(huì)進(jìn)行持久化操作,因?yàn)闊o(wú)論是AOF還是RDB都比較耗資源,持久化操作主要在從節(jié)點(diǎn)進(jìn)行。從節(jié)點(diǎn)是備份節(jié)點(diǎn),沒(méi)有來(lái)自客戶(hù)端請(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ì)輕易丟失。
假設(shè)AOF文件是10T,那么恢復(fù)的時(shí)候不會(huì)內(nèi)存溢出,因?yàn)橹灰獙?xiě)寫(xiě)寫(xiě)直到AOF文件成了10T,但凡能寫(xiě)到10個(gè)T,那肯定沒(méi)有內(nèi)存溢出。所以恢復(fù)的時(shí)候也不會(huì)內(nèi)存溢出。如果要溢出的話(huà),那么早就溢出了,還能把AOF文件寫(xiě)到10個(gè)T么?
下來(lái)說(shuō)AOF的配置:
vi 6379.conf, 不是vim 用 vi 打開(kāi) 上面寫(xiě)錯(cuò)了。
總是,每秒,從不。紅框中的,fsync上面說(shuō)過(guò)了Redis4.0混合持久化:
重啟Redis時(shí),很少使用rdb的方式恢復(fù)內(nèi)存狀態(tài),因?yàn)閞db會(huì)丟失大量數(shù)據(jù)。通常使用AOF日志重放,但是重放AOF日志相對(duì)于使用rdb來(lái)說(shuō)慢很多,這樣在Redis實(shí)例很大的情況下,啟動(dòng)需要花很長(zhǎng)的時(shí)間。
Redis4.0帶來(lái)了一個(gè)新的持久化選項(xiàng)--混合持久化。將rdb的內(nèi)容和增量的AOF日志文件存在一起。這里的AOF不再是全量的日志,而是自持久化開(kāi)始到持久化結(jié)束的這段時(shí)間發(fā)生的增量AOF日志,通常這部分AOF日志很小。一定是重寫(xiě)之后才開(kāi)始混合,比如開(kāi)啟了混合持久化后,set k1 v1 set k1 v2 等等 一定是輸入 bgrewriteaof 命令后,前面的一系列操作就變成了 RDB形式,保存在aof文件中,后續(xù)繼續(xù)set k3 v3 的時(shí)候,是以明文的形式追加到aof文件中
于是,在Redis重啟的時(shí)候,可以先加載rdb的內(nèi)容,然后再重放增量AOF日志,就可以完全替代之前的AOF全量文件重放,重啟效率得到大幅提升。
下來(lái)演示一下redis持久化
下來(lái)改配置文件:來(lái)驗(yàn)證rdb和aof是什么概念
vi /etc/redis/6379.conf
把daemonize yes改成daemonize no ,表示不讓redis變成后臺(tái)服務(wù)進(jìn)程運(yùn)行,成為前臺(tái)阻塞的運(yùn)行。
打開(kāi)AOF持久化,因?yàn)镽DB是默認(rèn)開(kāi)啟的不用管
原來(lái)是no,改成yes然后把混合持久化關(guān)閉
現(xiàn)在準(zhǔn)備工作已經(jīng)做好了,打開(kāi)了aof,打開(kāi)了rdb(默認(rèn)開(kāi)啟),關(guān)閉了混合持久化。
我現(xiàn)在是在root根目錄:
然后把1號(hào)shell窗口切換到etc下的redis目錄:
然后切換到2號(hào)shell窗口:
然后切回到1號(hào)shell窗口,啟動(dòng)redis
然后切回到2號(hào)shell窗口,LL看一下
然后 vi appendonly.aof,發(fā)現(xiàn)什么都沒(méi)有記錄
然后在3號(hào)shell打開(kāi) redis 6379 客戶(hù)端
然后設(shè)置一個(gè)kv。set k1 hello
然后切回到2號(hào)shell。LL看一下,因?yàn)椴粷M(mǎn)足rdb的那三個(gè)條件
所以可以看到還是只落了一個(gè)aof文件
打開(kāi)這個(gè)aof文件看一下
*2代表:兩個(gè)元素組成 一個(gè)是 select 另一個(gè)是 0號(hào)庫(kù)
然后在3號(hào)shell 繼續(xù)輸入 set k2 xxoo
然后切換到2號(hào)shell 查看 aof文件:
然后想一下 把這兩筆數(shù)據(jù) 弄到rdb里面去。在3號(hào)shell 輸入 bgsave
然后在2號(hào)shell查看:
然后1號(hào)shell窗口 會(huì)發(fā)生
然后切換到2號(hào)shell,打開(kāi)這個(gè)dump.rdb
下來(lái)看一下 aof 的重寫(xiě)功能:
那么aof文件會(huì)相應(yīng)的增長(zhǎng)很多對(duì)k1的操作:
那現(xiàn)在k1的最終value是c,所以以上k1之前的value值都是垃圾。所以給aof文件瘦身一下:瘦身前是170字節(jié)
然后在3號(hào)shell 窗口 :輸入bgrewriteaof
然后在2號(hào)shell 看一下 目前 aof的大小 :
然后打開(kāi)aof文件:
然后在1號(hào)shell 中 ctrl +c 結(jié)束:
把3號(hào)shell的客戶(hù)端 也退出去 exit,把2號(hào)的aof和rdb文件也全部刪除 rm -fr ./*
然后切到1號(hào)shell,改配置:
開(kāi)啟混合持久化:
保存后,啟動(dòng)redis:
然后2號(hào)shell 里還是一個(gè)空的 aof,沒(méi)毛病
然后3號(hào)shell開(kāi)啟redis客戶(hù)端 :set 兩次
然后在2號(hào)shell 看一下:然并卵,不是混合持久化。
然后繼續(xù)3號(hào)shell 寫(xiě),寫(xiě)完后bgrewriteaof
現(xiàn)在是混合持久化:所以查看2號(hào)shell:沒(méi)有增量前是103字節(jié)
打開(kāi),可以看到變成了二進(jìn)制的rdb了。和以前的aof文件不一樣
然后繼續(xù)set兩條記錄 3號(hào)shell:
然后轉(zhuǎn)到2號(hào)shell:查看aof文件:變成104字節(jié)了。
我不知道為什么,我打開(kāi)看不到增量的內(nèi)容,可能是io的延遲吧,不知道出什么bug了。
這下能看了:
dump.rdb
appendonly.aof
appendonly yes (默認(rèn)不開(kāi)啟,為no)
總結(jié)
以上是生活随笔為你收集整理的redis rdb aof区别_Redis(三):持久化RDB,fork.copyonwrite,AOF,RDBamp;AOF混合使用的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: 建行惠存通是怎么回事
- 下一篇: mysql不支持union_MySQL中