一区二区三区日韩精品-日韩经典一区二区三区-五月激情综合丁香婷婷-欧美精品中文字幕专区

分享

Hello Redis,我有7個問題想請教你!

 昵稱10087950 2022-07-29 發(fā)布于江蘇

Hello,Redis!我們相處已經(jīng)很多年了,從模糊的認(rèn)識到現(xiàn)在我們已經(jīng)深入結(jié)合,你的好我一直都知道也一直都記住,能否再讓我多問你的幾個問題,讓我更加深入的去了解你。


Redis 的通訊協(xié)議是什么

圖片

Redis 的通訊協(xié)議是文本協(xié)議,是的,Redis 服務(wù)器與客戶端通過 RESP(Redis Serialization Protocol)協(xié)議通信。

沒錯,文本協(xié)議確實是會浪費流量,不過它的優(yōu)點在于直觀,非常的簡單,解析性能極其的好,我們不需要一個特殊的 Redis 客戶端僅靠 Telnet 或者是文本流就可以跟 Redis 進(jìn)行通訊。

客戶端的命令格式: 

  • 簡單字符串 Simple Strings,以 "+"加號開頭。

  • 錯誤 Errors,以"-"減號開頭。

  • 整數(shù)型 Integer,以 ":" 冒號開頭。

  • 大字符串類型 Bulk Strings,以 "$"美元符號開頭。

  • 數(shù)組類型 Arrays,以 "*"星號開頭。

set hello abc
一個簡單的文本流就可以是redis的客戶端

圖片

簡單總結(jié):具體可以見 https:///topics/protocol ,Redis 文檔認(rèn)為簡單的實現(xiàn),快速的解析,直觀理解是采用 RESP 文本協(xié)議最重要的地方,有可能文本協(xié)議會造成一定量的流量浪費,但卻在性能上和操作上快速簡單,這中間也是一個權(quán)衡和協(xié)調(diào)的過程。

Redis 究竟有沒有 ACID 事務(wù)

圖片

要弄清楚 Redis 有沒有事務(wù),其實很簡單,上 Rredis 的官網(wǎng)查看文檔,發(fā)現(xiàn): 

圖片

Redis 確實是有事務(wù),不過按照傳統(tǒng)的事務(wù)定義 ACID 來看,Redis 是不是都具備了 ACID 的特性。

ACID 指的是:

  • 原子性

  • 一致性

  • 隔離性

  • 持久性

我們將使用以上 Redis 事務(wù)的命令來檢驗是否 Redis 都具備了 ACID 的各個特征。

原子性

事務(wù)具備原子性指的是,數(shù)據(jù)庫將事務(wù)中多個操作當(dāng)作一個整體來執(zhí)行,服務(wù)要么執(zhí)行事務(wù)中所有的操作,要么一個操作也不會執(zhí)行。

①事務(wù)隊列

首先弄清楚 Redis 開始事務(wù) multi 命令后,Redis 會為這個事務(wù)生成一個隊列,每次操作的命令都會按照順序插入到這個隊列中。

這個隊列里面的命令不會被馬上執(zhí)行,直到 exec 命令提交事務(wù),所有隊列里面的命令會被一次性,并且排他的進(jìn)行執(zhí)行。

圖片

對應(yīng)如下圖:

圖片

從上面的例子可以看出,當(dāng)執(zhí)行一個成功的事務(wù),事務(wù)里面的命令都是按照隊列里面順序的并且排他的執(zhí)行。

但原子性又一個特點就是要么全部成功,要么全部失敗,也就是我們傳統(tǒng) DB 里面說的回滾。

當(dāng)我們執(zhí)行一個失敗的事務(wù):

圖片

可以發(fā)現(xiàn),就算中間出現(xiàn)了失敗,set abc x 這個操作也已經(jīng)被執(zhí)行了,并沒有進(jìn)行回滾,從嚴(yán)格的意義上來說 Redis 并不具備原子性。

②為何 Redis 不支持回滾

這個其實跟 Redis 的定位和設(shè)計有關(guān)系,先看看為何我們的 MySQL 可以支持回滾,這個還是跟寫 Log 有關(guān)系,Redis 是完成操作之后才會進(jìn)行 AOF 日志記錄,AOF 日志的定位只是記錄操作的指令記錄。

而 MySQL 有完善的 Redolog,并且是在事務(wù)進(jìn)行 Commit 之前就會寫完成 Redolog,Binlog:

圖片

要知道 MySQL 為了能進(jìn)行回滾是花了不少的代價,Redis 應(yīng)用的場景更多是對抗高并發(fā)具備高性能,所以 Redis 選擇更簡單,更快速無回滾的方式處理事務(wù)也是符合場景。

一致性

事務(wù)具備一致性指的是,如果數(shù)據(jù)庫在執(zhí)行事務(wù)之前是一致的,那么在事務(wù)執(zhí)行之后,無論事務(wù)是否成功,數(shù)據(jù)庫也應(yīng)該是一致的。

從 Redis 來說可以從 2 個層面看,一個是執(zhí)行錯誤是否有確保一致性,另一個是宕機(jī)時,Redis 是否有確保一致性的機(jī)制。

①執(zhí)行錯誤是否有確保一致性

圖片

依然去執(zhí)行一個錯誤的事務(wù),在事務(wù)執(zhí)行的過程中會識別出來并進(jìn)行錯誤處理,這些錯誤并不會對數(shù)據(jù)庫作出修改,也不會對事務(wù)的一致性產(chǎn)生影響。

②宕機(jī)對一致性的影響

暫不考慮分布式高可用的 Redis 解決方案,先從單機(jī)看宕機(jī)恢復(fù)是否能滿意數(shù)據(jù)完整性約束。

無論是 RDB 還是 AOF 持久化方案,可以使用 RDB 文件或 AOF 文件進(jìn)行恢復(fù)數(shù)據(jù),從而將數(shù)據(jù)庫還原到一個一致的狀態(tài)。

③再議一致性

上面執(zhí)行錯誤和宕機(jī)對一致性的影響的觀點摘自黃健宏 《Redis 設(shè)計與實現(xiàn)》。

當(dāng)在讀這章的時候還是有一些存疑的點,歸根到底 Redis 并非關(guān)系型數(shù)據(jù)庫。

如果僅僅就 ACID 的表述上來說,一致性就是從 A 狀態(tài)經(jīng)過事務(wù)到達(dá) B 狀態(tài)沒有破壞各種約束性,僅就 Redis 而言不談實現(xiàn)的業(yè)務(wù),那顯然就是滿意一致性。

但如果加上業(yè)務(wù)去談一致性,例如,A 轉(zhuǎn)賬給 B,A 減少 10 塊錢,B 增加 10 塊錢,因為 Redis 并不具備回滾,也就不具備傳統(tǒng)意義上的原子性,所以 Redis 也應(yīng)該不具備傳統(tǒng)的一致性。

其實,這里只是簡單討論下 Redis 在傳統(tǒng) ACID 上的概念怎么進(jìn)行對接,或許,有可能是我想多了,用傳統(tǒng)關(guān)系型數(shù)據(jù)庫的 ACID 去審核 Redis 是沒有意義的,Redis 本來就沒有意愿去實現(xiàn) ACID 的事務(wù)。

隔離性

隔離性指的是,數(shù)據(jù)庫中有多個事務(wù)并發(fā)的執(zhí)行,各個事務(wù)之間不會相互影響,并且在并發(fā)狀態(tài)下執(zhí)行的事務(wù)和串行執(zhí)行的事務(wù)產(chǎn)生的結(jié)果是完全相同的。

Redis 因為是單線程操作,所以在隔離性上有天生的隔離機(jī)制,當(dāng) Redis 執(zhí)行事務(wù)時,Redis 的服務(wù)端保證在執(zhí)行事務(wù)期間不會對事務(wù)進(jìn)行中斷,所以,Redis 事務(wù)總是以串行的方式運行,事務(wù)也具備隔離性。

持久性

事務(wù)的持久性指的是,當(dāng)一個事務(wù)執(zhí)行完畢,執(zhí)行這個事務(wù)所得到的結(jié)果被保存在持久化的存儲中,即使服務(wù)器在事務(wù)執(zhí)行完成后停機(jī)了,執(zhí)行的事務(wù)的結(jié)果也不會被丟失。

Redis 是否具備持久化,這個取決于 Redis 的持久化模式:

  • 純內(nèi)存運行,不具備持久化,服務(wù)一旦停機(jī),所有數(shù)據(jù)將丟失。

  • RDB 模式,取決于 RDB 策略,只有在滿足策略才會執(zhí)行 Bgsave,異步執(zhí)行并不能保證 Redis 具備持久化。

  • AOF 模式,只有將 appendfsync 設(shè)置為 always,程序才會在執(zhí)行命令同步保存到磁盤,這個模式下,Redis 具備持久化。(將 appendfsync 設(shè)置為 always,只是在理論上持久化可行,但一般不會這么操作)


簡單總結(jié):

  • Redis 具備了一定的原子性,但不支持回滾。

  • Redis 不具備 ACID 中一致性的概念。(或者說 Redis 在設(shè)計時就無視這點)

  • Redis 具備隔離性。

  • Redis 通過一定策略可以保證持久性。

Redis 和 ACID 純屬站在使用者的角度去思想,Redis 設(shè)計更多的是追求簡單與高性能,不會受制于傳統(tǒng) ACID 的束縛。

Redis 的樂觀鎖 Watch 是怎么實現(xiàn)的

當(dāng)我們一提到樂觀鎖就會想起 CAS(Compare And Set),CAS 操作包含三個操作數(shù):

  • 內(nèi)存位置的值(V)

  • 預(yù)期原值(A)

  • 新值(B)

如果內(nèi)存位置的值與預(yù)期原值相匹配,那么處理器會自動將該位置更新為新值。否則,處理器不做任何操作。

在 Redis 的事務(wù)中使用 Watch 實現(xiàn),Watch 會在事務(wù)開始之前盯住 1 個或多個關(guān)鍵變量。

當(dāng)事務(wù)執(zhí)行時,也就是服務(wù)器收到了 exec 指令要順序執(zhí)行緩存的事務(wù)隊列時, Redis 會檢查關(guān)鍵變量自 Watch 之后,是否被修改了。

圖片

①Java 的 AtomicXXX 的樂觀鎖機(jī)制

在 Java 中我們也經(jīng)常的使用到一些樂觀鎖的參數(shù),例如 AtomicXXX,這些機(jī)制的背后是怎么去實現(xiàn)的,是否 Redis 也跟 Java 的 CAS 實現(xiàn)機(jī)制一樣?

先來看看 Java 的 Atomic 類,我們追一下源碼,可以看到它的背后其實是  Unsafe_CompareAndSwapObject:

圖片

可以看見 compareAndSwapObject 是 Native 方法,需要在繼續(xù)追查,可以下載源碼或打開 :http://hg.openjdk./jdk8u/。

圖片

圖片

②Cmpxchg

可以發(fā)現(xiàn)追查到最終 CAS,“比較并修改”,本來是兩個語意,但是最終確實一條 CPU 指令 Cmpxchg 完成。

Cmpxchg 是一條 CPU 指令的命令而不是多條 CPU 指令,所以它不會被多線程的調(diào)度所打斷,所以能夠保證 CAS 的操作是一個原子操作。

當(dāng)然 Cmpxchg 的機(jī)制其實存在 ABA 還有多次重試的問題,這個不在這里討論。

③Redis 的 Watch 機(jī)制

Redis 的 Watch 也是使用 Cmpxchg 嗎,兩者存在相似之處在用法上也有一些不同,Redis 的 Watch 不存在 ABA 問題,也沒有多次重試機(jī)制,其中有一個重大的不同是:Redis 事務(wù)執(zhí)行其實是串行的。

簡單追一下源碼:摘錄出來的源碼可能有些凌亂,不過可以簡單總結(jié)出來數(shù)據(jù)結(jié)構(gòu)圖和簡單的流程圖,之后再看源碼就會清晰很多。

圖片

圖片

圖片

圖片

存儲如下圖:

圖片

RedisDb 存放了一個 watched_keys 的 Dcit 結(jié)構(gòu),每個被 Watch 的 Key 的值是一個鏈表結(jié)構(gòu),存放的是一組 Redis 客戶端標(biāo)志。

流程如下圖:

圖片

每一次 Watch,Multi,Exec 時都會去查詢這個 watched_keys 結(jié)構(gòu)進(jìn)行判斷,每次 Touch 到被 Watch 的 Key 時都會標(biāo)志為  CLIENT_DIRTY_CAS。

因為在 Redis 中所有的事務(wù)都是串行的,假設(shè)有客戶端 A 和客戶端 B 都 Watch 同一個 Key。

當(dāng)客戶端 A 進(jìn)行 Touch 修改或者 A 率先執(zhí)行完,會把客戶端 A 從這個 watched_keys 的這個 Key 的列表刪除,然后把這個列表所有的客戶端都設(shè)置成 CLIENT_DIRTY_CAS。

當(dāng)后面的客戶端 B 開始執(zhí)行時,判斷到自己的狀態(tài)是 CLIENT_DIRTY_CAS,便 discardTransaction 終止事務(wù)。

簡單總結(jié):Cmpxchg 的實現(xiàn)主要是利用了 CPU 指令,看似兩個操作使用一條 CPU 指令完成,所以不會被多線程進(jìn)行打斷。

而 Redis 的 Watch 機(jī)制,更多是利用了 Redis 本身單線程的機(jī)制,采用了 watched_keys 的數(shù)據(jù)結(jié)構(gòu)和串行流程實現(xiàn)了樂觀鎖機(jī)制。

Redis 是如何持久化的

圖片

Redis 的持久化有兩種機(jī)制,一個是 RDB,也就是快照,快照就是一次全量的備份,會把所有 Redis 的內(nèi)存數(shù)據(jù)進(jìn)行二進(jìn)制的序列化存儲到磁盤。

另一種是 AOF 日志,AOF 日志記錄的是數(shù)據(jù)操作修改的指令記錄日志,可以類比 MySQL 的 Binlog,AOF 日期隨著時間的推移只會無限增量。

在對 Redis 進(jìn)行恢復(fù)時,RDB 快照直接讀取磁盤即可恢復(fù),而 AOF 需要對所有的操作指令進(jìn)行重放進(jìn)行恢復(fù),這個過程有可能非常漫長。

圖片

RDB

Redis 在進(jìn)行 RDB 的快照生成有兩種方法,一種是 Save,由于 Redis 是單進(jìn)程單線程,直接使用 Save,Redis 會進(jìn)行一個龐大的文件 IO 操作。

由于單進(jìn)程單線程勢必會阻塞線上的業(yè)務(wù),一般的話不會直接采用 Save,而是采用 Bgsave,之前一直說 Redis 是單進(jìn)程單線程,其實不然。

在使用 Bgsave 的時候,Redis 會 Fork 一個子進(jìn)程,快照的持久化就交給子進(jìn)程去處理,而父進(jìn)程繼續(xù)處理線上業(yè)務(wù)的請求。

①Fork 機(jī)制

想要弄清楚 RDB 快照的生成原理就必須弄清楚 Fork 機(jī)制,F(xiàn)ork 機(jī)制是 Linux 操作系統(tǒng)的一個進(jìn)程機(jī)制。

當(dāng)父進(jìn)程 Fork 出來一個子進(jìn)程,子進(jìn)程和父進(jìn)程擁有共同的內(nèi)存數(shù)據(jù)結(jié)構(gòu),子進(jìn)程剛剛產(chǎn)生時,它和父進(jìn)程共享內(nèi)存里面的代碼段和數(shù)據(jù)段。

圖片

一開始兩個進(jìn)程都具備了相同的內(nèi)存段,子進(jìn)程在做數(shù)據(jù)持久化時,不會去修改現(xiàn)在的內(nèi)存數(shù)據(jù),而是會采用 COW(Copy On Write)的方式將數(shù)據(jù)段頁面進(jìn)行分離。

當(dāng)父進(jìn)程修改了某一個數(shù)據(jù)段時,被共享的頁面就會復(fù)制一份分離出來,然后父進(jìn)程再在新的數(shù)據(jù)段進(jìn)行修改。

圖片

②分裂

這個過程也成為分裂的過程,本來父子進(jìn)程都指向很多相同的內(nèi)存塊,但是如果父進(jìn)程對其中某個內(nèi)存塊進(jìn)行該修改,就會將其復(fù)制出來,進(jìn)行分裂再在新的內(nèi)存塊上面進(jìn)行修改。

因為子進(jìn)程在 Fork 的時候就可以固定內(nèi)存,這個時間點的數(shù)據(jù)將不會產(chǎn)生變化。

所以我們可以安心的產(chǎn)生快照不用擔(dān)心快照的內(nèi)容受到父進(jìn)程業(yè)務(wù)請求的影響。

另外可以想象,如果在 Bgsave 的過程中,Redis 沒有任何操作,父進(jìn)程沒有接收到任何業(yè)務(wù)請求也沒有任何的背后例如過期移除等操作,父進(jìn)程和子進(jìn)程將會使用相同的內(nèi)存塊。

AOF

AOF 是 Redis 操作指令的日志存儲,類同于 MySQL 的 Binlog,假設(shè) AOF 從 Redis 創(chuàng)建以來就一直執(zhí)行,那么 AOF 就記錄了所有的 Redis 指令的記錄。

如果要恢復(fù) Redis,可以對 AOF 進(jìn)行指令重放,便可修復(fù)整個 Redis 實例。

不過 AOF 日志也有兩個比較大的問題:

  • 一個是 AOF 的日志會隨著時間遞增,如果一個數(shù)據(jù)量大運行的時間久,AOF 日志量將變得異常龐大。

  • 另一個問題是 AOF 在做數(shù)據(jù)恢復(fù)時,由于重放的量非常龐大,恢復(fù)的時間將會非常的長。

AOF 寫操作是在 Redis 處理完業(yè)務(wù)邏輯之后,按照一定的策略才會進(jìn)行些 AOF 日志存盤,這點跟 MySQL 的 Redolog 和 Binlog 有很大的不同。

也因為此原因,Redis 因為處理邏輯在前而記錄操作日志在后,也是導(dǎo)致 Redis 無法進(jìn)行回滾的一個原因。

bgrewriteaof:針對上述的問題,Redis 在 2.4 之后也使用了 bgrewriteaof 對 AOF 日志進(jìn)行瘦身。

bgrewriteaof 命令用于異步執(zhí)行一個 AOF 文件重寫操作。重寫會創(chuàng)建一個當(dāng)前 AOF 文件的體積優(yōu)化版本。

RDB 和 AOF 混合搭配模式

在對 Redis 進(jìn)行恢復(fù)的時候,如果我們采用了 RDB 的方式,因為 Bgsave 的策略,可能會導(dǎo)致我們丟失大量的數(shù)據(jù)。

如果我們采用了 AOF 的模式,通過 AOF 操作日志重放恢復(fù),重放 AOF 日志比 RDB 要長久很多。

圖片

Redis 4.0 之后,為了解決這個問題,引入了新的持久化模式,混合持久化,將 RDB 的文件和局部增量的 AOF 文件相結(jié)合。

RDB 可以使用相隔較長的時間保存策略,AOF 不需要是全量日志,只需要保存前一次 RDB 存儲開始到這段時間增量 AOF 日志即可,一般來說,這個日志量是非常小的。

Redis 在內(nèi)存使用上是如何開源節(jié)流

圖片

Redis 跟其他傳統(tǒng)數(shù)據(jù)庫不同,Redis 是一個純內(nèi)存的數(shù)據(jù)庫,并且存儲了都是一些數(shù)據(jù)結(jié)構(gòu)的數(shù)據(jù),如果不對內(nèi)存加以控制的話,Redis 很可能會因為數(shù)據(jù)量過大導(dǎo)致系統(tǒng)的奔潰。

Ziplist

127.0.0.1:6379hset hash_test abc 1
(integer) 1
127.0.0.1:6379object encoding hash_test
"ziplist"
127.0.0.1:6379zadd z_test 10 key
(integer) 1
127.0.0.1:6379object encoding z_test
"ziplist"

當(dāng)最開始嘗試開啟一個小數(shù)據(jù)量的 Hash 結(jié)構(gòu)和一個 Zset 結(jié)構(gòu)時,發(fā)現(xiàn)他們在 Redis 里面的真正結(jié)構(gòu)類型是一個 Ziplist。

Ziplist 是一個緊湊的數(shù)據(jù)結(jié)構(gòu),每一個元素之間都是連續(xù)的內(nèi)存,如果在 Redis 中,Redis 啟用的數(shù)據(jù)結(jié)構(gòu)數(shù)據(jù)量很小時,Redis 就會切換到使用緊湊存儲的形式來進(jìn)行壓縮存儲。

圖片

例如,上面的例子,我們采用了 Hash 結(jié)構(gòu)進(jìn)行存儲,Hash 結(jié)構(gòu)是一個二維的結(jié)構(gòu),是一個典型的用空間換取時間的結(jié)構(gòu)。

但是如果使用的數(shù)據(jù)量很小,使用二維結(jié)構(gòu)反而浪費了空間,在時間的性能上也并沒有得到太大的提升,還不如直接使用一維結(jié)構(gòu)進(jìn)行存儲。

在查找的時候,雖然復(fù)雜度是 O(n),但是因為數(shù)據(jù)量少遍歷也非??欤鲋帘?Hash 結(jié)構(gòu)本身的查詢更快。

如果當(dāng)集合對象的元素不斷的增加,或者某個 Value 的值過大,這種小對象存儲也會升級生成標(biāo)準(zhǔn)的結(jié)構(gòu)。

Redis 也可以在配置中進(jìn)行定義緊湊結(jié)構(gòu)和標(biāo)準(zhǔn)結(jié)構(gòu)的轉(zhuǎn)換參數(shù):

hash-max-ziplist-entries 512  # hash的元素個數(shù)超過512就必須用標(biāo)準(zhǔn)結(jié)構(gòu)存儲
hash-max-ziplist-value 64     # hash的任意元素的key/value的長度超過 64 就必須用標(biāo)準(zhǔn)結(jié)構(gòu)存儲
list-max-ziplist-entries 512  
list-max-ziplist-value 64  
zset-max-ziplist-entries 128 
zset-max-ziplist-value 64  
set-max-intset-entries 512 

Quicklist

127.0.0.1:6379rpush key v1
(integer) 1
127.0.0.1:6379object encoding key
"quicklist"

Quicklist 數(shù)據(jù)結(jié)構(gòu)是 Redis 在 3.2 才引入的一個雙向鏈表的數(shù)據(jù)結(jié)構(gòu),確實來說是一個 Ziplist 的雙向鏈表。

Quicklist 的每一個數(shù)據(jù)節(jié)點是一個 Ziplist,Ziplist 本身就是一個緊湊列表。

假使,Quicklist 包含了 5 個 Ziplist 的節(jié)點,每個 Ziplist 列表又包含了 5 個數(shù)據(jù),那么在外部看來,這個 Quicklist 就包含了 25 個數(shù)據(jù)項。

圖片

Quicklist 的結(jié)構(gòu)設(shè)計簡單總結(jié)起來,是一個空間和時間的折中方案: 

  • 雙向鏈表可以在兩端進(jìn)行 Push 和 Pop 操作,但是它在每一個節(jié)點除了保存自身的數(shù)據(jù)外,還要保存兩個指針,增加額外的內(nèi)存開銷。

    其次是由于每個節(jié)點都是獨立的,在內(nèi)存地址上并不連續(xù),節(jié)點多了容易產(chǎn)生內(nèi)存碎片。 

  • Ziplist 本身是一塊連續(xù)的內(nèi)存,存儲和查詢效率很高,但是,它不利于修改操作,每次數(shù)據(jù)變動時都會引發(fā)內(nèi)存 Realloc,如果 Ziplist 長度很長時,一次 Realloc 會導(dǎo)致大批量數(shù)據(jù)拷貝。

所以,結(jié)合 Ziplist 和雙向鏈表的優(yōu)點,Quciklist 就孕育而生。

對象共享

Redis 在自己的對象系統(tǒng)中構(gòu)建了一個引用計數(shù)方法,通過這個方法程序可以跟蹤對象的引用計數(shù)信息,除了可以在適當(dāng)?shù)臅r候進(jìn)行對象釋放,還可以用來作為對象共享。 

舉個例子,假使鍵 A 創(chuàng)建了一個整數(shù)值 100 的字符串作為值對象,這個時候鍵 B 也創(chuàng)建保存同樣整數(shù)值 100 的字符串對象作為值對象。

圖片

那么在 Redis 的操作時:

  • 講數(shù)據(jù)庫鍵的指針指向一個現(xiàn)有的值對象。

  • 講被共享的值對象引用計數(shù)加一。

假使,我們的數(shù)據(jù)庫中指向整數(shù)值 100 的鍵不止鍵 A 和鍵 B,而是有幾百個,那么 Redis 服務(wù)器中只需要一個字符串對象的內(nèi)存就可以保存原本需要幾百個字符串對象的內(nèi)存才能保存的數(shù)據(jù)。

Redis 是如何實現(xiàn)主從復(fù)制

圖片

幾個定義:

  • runID:服務(wù)器運行的 ID。

  • Offset:主服務(wù)器的復(fù)制偏移量和從服務(wù)器復(fù)制的偏移量。

  • Replication backlog:主服務(wù)器的復(fù)制積壓緩沖區(qū)。

在 Redis 2.8 之后,使用 Psync 命令代替 Sync 命令來執(zhí)行復(fù)制的同步操作。

Psync 命令具有完整重同步和部分重同步兩種模式: 

  • 完整同步用于處理初次復(fù)制情況:完整重同步的執(zhí)行步驟和 Sync 命令執(zhí)行步驟一致,都是通過讓主服務(wù)器創(chuàng)建并發(fā)送 RDB 文件,以及向從服務(wù)器發(fā)送保存在緩沖區(qū)的寫命令來進(jìn)行同步。

  • 部分重同步是用于處理斷線后重復(fù)制情況:當(dāng)從服務(wù)器在斷線后重新連接主服務(wù)器時,主服務(wù)可以將主從服務(wù)器連接斷開期間執(zhí)行的寫命令發(fā)送給從服務(wù)器,從服務(wù)器只要接收并執(zhí)行這些寫命令,就可以將數(shù)據(jù)庫更新至主服務(wù)器當(dāng)前所處的狀態(tài)。

圖片

完整重同步: 

  • Slave 發(fā)送 Psync 給 Master,由于是第一次發(fā)送,不帶上 runID 和 Offset。

  • Master 接收到請求,發(fā)送 Master 的 runID 和 Offset 給從節(jié)點。

  • Master 生成保存 RDB 文件。

  • Master 發(fā)送 RDB 文件給 Slave。

  • 在發(fā)送 RDB 這個操作的同時,寫操作會復(fù)制到緩沖區(qū) Replication Backlog Buffer 中,并從 Buffer 區(qū)發(fā)送到 Slave。

  • Slave 將 RDB 文件的數(shù)據(jù)裝載,并更新自身數(shù)據(jù)。

如果網(wǎng)絡(luò)的抖動或者是短時間的斷鏈也需要進(jìn)行完整同步就會導(dǎo)致大量的開銷,這些開銷包括了,Bgsave 的時間,RDB 文件傳輸?shù)臅r間,Slave 重新加載 RDB 時間,如果 Slave 有 AOF,還會導(dǎo)致 AOF 重寫。

這些都是大量的開銷,所以在 Redis 2.8 之后也實現(xiàn)了部分重同步的機(jī)制。

圖片

部分重同步:

  • 網(wǎng)絡(luò)發(fā)生錯誤,Master 和 Slave 失去連接。

  • Master 依然向 Buffer 緩沖區(qū)寫入數(shù)據(jù)。

  • Slave 重新連接上 Master。

  • Slave 向 Master 發(fā)送自己目前的 runID 和 Offset。

  • Master 會判斷 Slave 發(fā)送給自己的 Offset 是否存在 Buffer 隊列中。

    如果存在,則發(fā)送 Continue 給 Slave;如果不存在,意味著可能錯誤了太多的數(shù)據(jù),緩沖區(qū)已經(jīng)被清空,這個時候就需要重新進(jìn)行全量的復(fù)制。

  • Master 發(fā)送從 Offset 偏移后的緩沖區(qū)數(shù)據(jù)給 Slave。

  • Slave 獲取數(shù)據(jù)更新自身數(shù)據(jù)。


Redis 是怎么制定過期刪除策略的

當(dāng)一個鍵處于過期的狀態(tài),其實在 Redis 中這個內(nèi)存并不是實時就被從內(nèi)存中進(jìn)行摘除,而是 Redis 通過一定的機(jī)制去把一些處于過期鍵進(jìn)行移除,進(jìn)而達(dá)到內(nèi)存的釋放,那么當(dāng)一個鍵處于過期,Redis 會在什么時候去刪除?

幾時被刪除存在三種可能性,這三種可能性也代表了 Redis 的三種不同的刪除策略。 

  • 定時刪除:在設(shè)置鍵過去的時間同時,創(chuàng)建一個定時器,讓定時器在鍵過期時間來臨,立即執(zhí)行對鍵的刪除操作。

  • 惰性刪除:放任鍵過期不管,但是每次從鍵空間獲取鍵時,都會檢查該鍵是否過期,如果過期的話,就刪除該鍵。

  • 定期刪除:每隔一段時間,程序都要對數(shù)據(jù)庫進(jìn)行一次檢查,刪除里面的過期鍵,至于要刪除多少過期鍵,由算法而定。

①定時刪除

設(shè)置鍵的過期時間,創(chuàng)建定時器,一旦過期時間來臨,就立即對鍵進(jìn)行操作。

這種對內(nèi)存是友好的,但是對 CPU 的時間是最不友好的,特別是在業(yè)務(wù)繁忙,過期鍵很多的時候,刪除過期鍵這個操作就會占據(jù)很大一部分 CPU 的時間。

要知道 Redis 是單線程操作,在內(nèi)存不緊張而 CPU 緊張的時候,將 CPU 的時間浪費在與業(yè)務(wù)無關(guān)的刪除過期鍵上面,會對 Redis 的服務(wù)器的響應(yīng)時間和吞吐量造成影響。 

另外,創(chuàng)建一個定時器需要用到 Redis 服務(wù)器中的時間事件,而當(dāng)前時間事件的實現(xiàn)方式是無序鏈表,時間復(fù)雜度為 O(n),讓服務(wù)器大量創(chuàng)建定時器去實現(xiàn)定時刪除策略,會產(chǎn)生較大的性能影響,所以,定時刪除并不是一種好的刪除策略。

②惰性刪除

與定時刪除相反,惰性刪除策略對 CPU 來說是最友好的,程序只有在取出鍵的時候才會進(jìn)行檢查,是一種被動的過程。

與此同時,惰性刪除對內(nèi)存來說又是最不友好的,一個鍵過期,只要不再被取出,這個過期鍵就不會被刪除,它占用的內(nèi)存也不會被釋放。 

很明顯,惰性刪除也不是一個很好的策略,Redis 是非常依賴內(nèi)存和較好內(nèi)存的,如果一些長期鍵長期沒有被訪問,就會造成大量的內(nèi)存垃圾,甚至?xí)俪蓛?nèi)存的泄漏。

在對執(zhí)行數(shù)據(jù)寫入時,通過 expireIfNeeded 函數(shù)對寫入的 Key 進(jìn)行過期判斷。

圖片

其中 expireIfNeeded 在內(nèi)部做了三件事情,分別是: 

  • 查看 Key 是否過期。

  • 向 Slave 節(jié)點傳播執(zhí)行過去 Key 的動作。

  • 刪除過期 Key。

③定期刪除

上面兩種刪除策略,無論是定時刪除和惰性刪除,這兩種刪除方式在單一的使用上都存在明顯的缺陷,要么占用太多 CPU 時間,要么浪費太多內(nèi)存。

定期刪除策略是前兩種策略的一個整合和折中:

  • 定期刪除策略每隔一段時間執(zhí)行一次刪除過期鍵操作,并通過限制刪除操作執(zhí)行的時間和頻率來減少刪除操作對 CPU 時間的影響。

  • 通過合理的刪除執(zhí)行的時長和頻率,來達(dá)到合理的刪除過期鍵。


總結(jié)

Redis 可謂博大精深,簡單的七連問只是盲人摸象,這次只是摸到了一根象鼻子,還應(yīng)該順著鼻子向下摸,下次可能摸到了一只象耳朵。

只要愿意往下深入去了解去摸索,而不只應(yīng)用不思考,總有一天會把 Redis 這只大象給摸透了。

作者:陳于喆,注:部分章節(jié)參考和引用黃健宏 《Redis 設(shè)計與實現(xiàn)》

簡介:十余年的開發(fā)和架構(gòu)經(jīng)驗,國內(nèi)較早一批微服務(wù)開發(fā)實施者。曾任職國內(nèi)互聯(lián)網(wǎng)公司網(wǎng)易和唯品會高級研發(fā)工程師,后在創(chuàng)業(yè)公司擔(dān)任技術(shù)總監(jiān)/架構(gòu)師,目前在洋蔥集團(tuán)任職技術(shù)研發(fā)副總監(jiān)。負(fù)責(zé)技術(shù)部門研發(fā)體系建設(shè),團(tuán)建建設(shè),人才培養(yǎng),推動整個技術(shù)架構(gòu)演進(jìn)以及升級,帶領(lǐng)技術(shù)團(tuán)隊構(gòu)建微服務(wù)架構(gòu)體系、平臺架構(gòu)體系、自動化運維體系。

    本站是提供個人知識管理的網(wǎng)絡(luò)存儲空間,所有內(nèi)容均由用戶發(fā)布,不代表本站觀點。請注意甄別內(nèi)容中的聯(lián)系方式、誘導(dǎo)購買等信息,謹(jǐn)防詐騙。如發(fā)現(xiàn)有害或侵權(quán)內(nèi)容,請點擊一鍵舉報。
    轉(zhuǎn)藏 分享 獻(xiàn)花(0

    0條評論

    發(fā)表

    請遵守用戶 評論公約

    類似文章 更多

    免费午夜福利不卡片在线 视频| 风韵人妻丰满熟妇老熟女av| 久久精品一区二区少妇| 亚洲中文字幕免费人妻| 风韵人妻丰满熟妇老熟女av| 又黄又爽禁片视频在线观看| 国产精品欧美激情在线观看| 久久精品中文字幕人妻中文| 亚洲国产中文字幕在线观看| 国产日韩欧美专区一区| 欧美日韩人妻中文一区二区| 99精品人妻少妇一区二区人人妻| 欧美野外在线刺激在线观看| 成人午夜激情在线免费观看| 色婷婷国产精品视频一区二区保健| 日本午夜精品视频在线观看| 丝袜人妻夜夜爽一区二区三区| 欧美一级不卡视频在线观看| 国内外激情免费在线视频| 欧美一级片日韩一级片| 男人和女人草逼免费视频| 欧美日韩在线观看自拍| 欧美成人精品一区二区久久| 中国美女草逼一级黄片视频| 亚洲男人天堂成人在线视频| 国产精品二区三区免费播放心| 日本午夜福利视频免费观看| 男人把女人操得嗷嗷叫| 国产成人综合亚洲欧美日韩 | 国产麻豆视频一二三区| 国产丝袜美女诱惑一区二区| 日韩高清一区二区三区四区| 免费亚洲黄色在线观看| 国内自拍偷拍福利视频| 午夜福利黄片免费观看| 国产大屁股喷水在线观看视频 | 好骚国产99在线中文| 人人妻在人人看人人澡| 亚洲熟妇av一区二区三区色堂| 国产日本欧美韩国在线| 色综合久久六月婷婷中文字幕|