咔擦,不就是快照嘛
時(shí)間:2021-08-19 16:30:42
手機(jī)看文章
掃描二維碼
隨時(shí)隨地手機(jī)看文章
[導(dǎo)讀]大家好,我是小林哥。雖說Redis是內(nèi)存數(shù)據(jù)庫,但是它為數(shù)據(jù)的持久化提供了兩個(gè)技術(shù)。分別是「AOF日志和RDB快照」。這兩種技術(shù)都會(huì)用各用一個(gè)日志文件來記錄信息,但是記錄的內(nèi)容是不同的。AOF文件的內(nèi)容是操作命令;RDB文件的內(nèi)容是二進(jìn)制數(shù)據(jù)。關(guān)于AOF持久化的原理我在上一篇已經(jīng)...
大家好,我是小林哥。雖說 Redis 是內(nèi)存數(shù)據(jù)庫,但是它為數(shù)據(jù)的持久化提供了兩個(gè)技術(shù)。分別是「 AOF 日志和 RDB 快照」。這兩種技術(shù)都會(huì)用各用一個(gè)日志文件來記錄信息,但是記錄的內(nèi)容是不同的。
- AOF 文件的內(nèi)容是操作命令;
- RDB 文件的內(nèi)容是二進(jìn)制數(shù)據(jù)。
快照怎么用?
要熟悉一個(gè)東西,先看看怎么用是比較好的方式。Redis 提供了兩個(gè)命令來生成 RDB 文件,分別是save
和 bgsave
,他們的區(qū)別就在于是否在「主線程」里執(zhí)行:- 執(zhí)行了 save 命令,就會(huì)在主線程生成 RDB 文件,由于和執(zhí)行操作命令在同一個(gè)線程,所以如果寫入 RDB 文件的時(shí)間太長,會(huì)阻塞主線程;
- 執(zhí)行了 bgsava 命令,會(huì)創(chuàng)建一個(gè)子進(jìn)程來生成 RDB 文件,這樣可以避免主線程的阻塞;
save?900?1
save?300?10
save?60?10000
別看選項(xiàng)名叫 sava,實(shí)際上執(zhí)行的是 bgsava 命令,也就是會(huì)創(chuàng)建子進(jìn)程來生成 RDB 快照文件。只要滿足上面條件的任意一個(gè),就會(huì)執(zhí)行 bgsava,它們的意思分別是:- 900 秒之內(nèi),對數(shù)據(jù)庫進(jìn)行了至少 1 次修改;
- 300 秒之內(nèi),對數(shù)據(jù)庫進(jìn)行了至少 10 次修改;
- 60 秒之內(nèi),對數(shù)據(jù)庫進(jìn)行了至少 10000 次修改。
執(zhí)行快照時(shí),數(shù)據(jù)能被修改嗎?
那問題來了,執(zhí)行 bgsava 過程中,由于是交給子進(jìn)程來構(gòu)建 RDB 文件,主線程還是可以繼續(xù)工作的,此時(shí)主線程可以修改數(shù)據(jù)嗎?如果不可以修改數(shù)據(jù)的話,那這樣性能一下就降低了很多。如果可以修改數(shù)據(jù),又是如何做到到呢?直接說結(jié)論吧,執(zhí)行 bgsava 過程中,Redis 依然可以繼續(xù)處理操作命令的,也就是數(shù)據(jù)是能被修改的。那具體如何做到到呢?關(guān)鍵的技術(shù)就在于寫時(shí)復(fù)制技術(shù)(Copy-On-Write, COW)。執(zhí)行 bgsava 命令的時(shí)候,會(huì)通過fork()
創(chuàng)建子進(jìn)程,此時(shí)子進(jìn)程和父進(jìn)程是共享同一片內(nèi)存數(shù)據(jù)的,因?yàn)閯?chuàng)建子進(jìn)程的時(shí)候,會(huì)復(fù)制父進(jìn)程的頁表,但是頁表指向的物理內(nèi)存還是一個(gè)。只有在發(fā)生修改內(nèi)存數(shù)據(jù)的情況時(shí),物理內(nèi)存才會(huì)被復(fù)制一份。這樣的目的是為了減少創(chuàng)建子進(jìn)程時(shí)的性能損耗,從而加快創(chuàng)建子進(jìn)程的速度,畢竟創(chuàng)建子進(jìn)程的過程中,是會(huì)阻塞主線程的。所以,創(chuàng)建 bgsave 子進(jìn)程后,由于共享父進(jìn)程的所有內(nèi)存數(shù)據(jù),于是就可以直接讀取主線程里的內(nèi)存數(shù)據(jù),并將數(shù)據(jù)寫入到 RDB 文件。當(dāng)主線程對這些共享的內(nèi)存數(shù)據(jù)也都是只讀操作,那么,主線程和 bgsave 子進(jìn)程相互不影響。但是,如果主線程要修改共享數(shù)據(jù)里的某一塊數(shù)據(jù)(比如鍵值對 A
)時(shí),就會(huì)發(fā)生寫時(shí)復(fù)制,于是這塊數(shù)據(jù)的物理內(nèi)存就會(huì)被復(fù)制一份(鍵值對 A'
),然后主線程在這個(gè)數(shù)據(jù)副本(鍵值對 A'
)進(jìn)行修改操作。與此同時(shí),bgsave 子進(jìn)程可以繼續(xù)把原來的數(shù)據(jù)(鍵值對 A
)寫入到 RDB 文件。就是這樣,Redis 使用 bgsave 對當(dāng)前內(nèi)存中的所有數(shù)據(jù)做快照,這個(gè)操作是由 bgsave 子進(jìn)程在后臺(tái)完成的,執(zhí)行時(shí)不會(huì)阻塞主線程,這就使得主線程同時(shí)可以修改數(shù)據(jù)。細(xì)心的同學(xué),肯定發(fā)現(xiàn)了,bgsave 快照過程中,如果主線程修改了共享數(shù)據(jù),發(fā)生了寫時(shí)復(fù)制后,RDB 快照保存的是原本的內(nèi)存數(shù)據(jù),而主線程剛修改的數(shù)據(jù),是被辦法在這一時(shí)間寫入 RDB 文件的,只能交由下一次的 bgsave 快照。所以 Redis 在使用 bgsave 快照過程中,如果主線程修改了內(nèi)存數(shù)據(jù),不管是否是共享的內(nèi)存數(shù)據(jù),RDB 快照都無法寫入主線程剛修改的數(shù)據(jù),因?yàn)榇藭r(shí)主線程的內(nèi)存數(shù)據(jù)和子線程的內(nèi)存數(shù)據(jù)已經(jīng)分離了,子線程寫入到 RDB 文件的內(nèi)存數(shù)據(jù)只能是原本的內(nèi)存數(shù)據(jù)。如果系統(tǒng)恰好在 RDB 快照文件創(chuàng)建完畢后崩潰了,那么 Redis 將會(huì)丟失主線程在快照期間修改的數(shù)據(jù)。另外,寫時(shí)復(fù)制的時(shí)候會(huì)出現(xiàn)這么個(gè)極端的情況。在 Redis 執(zhí)行 RDB 持久化期間,剛 fork 時(shí),主進(jìn)程和子進(jìn)程共享同一物理內(nèi)存,但是途中主進(jìn)程處理了寫操作,修改了共享內(nèi)存,于是當(dāng)前被修改的數(shù)據(jù)的物理內(nèi)存就會(huì)被復(fù)制一份。那么極端情況下,如果所有的共享內(nèi)存都被修改,則此時(shí)的內(nèi)存占用是原先的 2 倍。所以,針對寫操作多的場景,我們要留意下快照過程中內(nèi)存的變化,防止內(nèi)存被占滿了。RDB 和 AOF 合體
盡管 RDB 比 AOF 的數(shù)據(jù)恢復(fù)速度快,但是快照的頻率不好把握:- 如果頻率太低,兩次快照間一旦服務(wù)器發(fā)生宕機(jī),就可能會(huì)比較多的數(shù)據(jù)丟失;
- 如果頻率太高,頻繁寫入磁盤和創(chuàng)建子進(jìn)程會(huì)帶來額外的性能開銷。
aof-use-rdb-preamble?yes
混合持久化工作在 AOF 日志重寫過程。當(dāng)開啟了混合持久化時(shí),在 AOF 重寫日志時(shí),fork
出來的重寫子進(jìn)程會(huì)先將與主線程共享的內(nèi)存數(shù)據(jù)以 RDB 方式寫入到 AOF 文件,然后主線程處理的操作命令會(huì)被記錄在重寫緩沖區(qū)里,重寫緩沖區(qū)里的增量命令會(huì)以 AOF 方式寫入到 AOF 文件,寫入完成后通知主進(jìn)程將新的含有 RDB 格式和 AOF 格式的 AOF 文件替換舊的的 AOF 文件。也就是說,使用了混合持久化,AOF 文件的前半部分是 RDB 格式的全量數(shù)據(jù),后半部分是 AOF 格式的增量數(shù)據(jù)。這樣的好處在于,重啟 Redis 加載數(shù)據(jù)的時(shí)候,由于前半部分是 RDB 內(nèi)容,這樣加載的時(shí)候速度會(huì)很快。加載完 RDB 的內(nèi)容后,才會(huì)加載后半部分的 AOF 內(nèi)容,這里的內(nèi)容是 Redis 后臺(tái)子進(jìn)程重寫 AOF 期間,主線程處理的操作命令,可以使得數(shù)據(jù)更少的丟失。