自拍偷在线精品自拍偷,亚洲欧美中文日韩v在线观看不卡

Hello Redis,我有7個(gè)問(wèn)題想請(qǐng)教你!

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

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

[[269816]]

圖片來(lái)自包圖網(wǎng)

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

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

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

客戶端的命令格式:

  • 簡(jiǎn)單字符串 Simple Strings,以 "+"加號(hào)開(kāi)頭。
  • 錯(cuò)誤 Errors,以"-"減號(hào)開(kāi)頭。
  • 整數(shù)型 Integer,以 ":" 冒號(hào)開(kāi)頭。
  • 大字符串類型 Bulk Strings,以 "$"美元符號(hào)開(kāi)頭。
  • 數(shù)組類型 Arrays,以 "*"星號(hào)開(kāi)頭。
  1. set hello abc 
  2. 一個(gè)簡(jiǎn)單的文本流就可以是redis的客戶端 

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

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

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

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

ACID 指的是:

  • 原子性
  • 一致性
  • 隔離性
  • 持久性

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

原子性

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

①事務(wù)隊(duì)列

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

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

對(duì)應(yīng)如下圖:

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

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

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

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

②為何 Redis 不支持回滾

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

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

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

一致性

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

從 Redis 來(lái)說(shuō)可以從 2 個(gè)層面看,一個(gè)是執(zhí)行錯(cuò)誤是否有確保一致性,另一個(gè)是宕機(jī)時(shí),Redis 是否有確保一致性的機(jī)制。

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

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

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

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

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

③再議一致性

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

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

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

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

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

隔離性

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

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

持久性

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

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

  • 純內(nèi)存運(yùn)行,不具備持久化,服務(wù)一旦停機(jī),所有數(shù)據(jù)將丟失。
  • RDB 模式,取決于 RDB 策略,只有在滿足策略才會(huì)執(zhí)行 Bgsave,異步執(zhí)行并不能保證 Redis 具備持久化。
  • AOF 模式,只有將 appendfsync 設(shè)置為 always,程序才會(huì)在執(zhí)行命令同步保存到磁盤(pán),這個(gè)模式下,Redis 具備持久化。(將 appendfsync 設(shè)置為 always,只是在理論上持久化可行,但一般不會(huì)這么操作)

簡(jiǎn)單總結(jié):

  • Redis 具備了一定的原子性,但不支持回滾。
  • Redis 不具備 ACID 中一致性的概念。(或者說(shuō) Redis 在設(shè)計(jì)時(shí)就無(wú)視這點(diǎn))
  • Redis 具備隔離性。
  • Redis 通過(guò)一定策略可以保證持久性。

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

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

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

  • 內(nèi)存位置的值(V)
  • 預(yù)期原值(A)
  • 新值(B)

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

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

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

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

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

先來(lái)看看 Java 的 Atomic 類,我們追一下源碼,可以看到它的背后其實(shí)是 Unsafe_CompareAndSwapObject:

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

②Cmpxchg

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

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

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

③Redis 的 Watch 機(jī)制

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

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

存儲(chǔ)如下圖:

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

流程如下圖:

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

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

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

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

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

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

Redis 是如何持久化的

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

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

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

RDB

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

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

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

①Fork 機(jī)制

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

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

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

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

②分裂

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

因?yàn)樽舆M(jìn)程在 Fork 的時(shí)候就可以固定內(nèi)存,這個(gè)時(shí)間點(diǎn)的數(shù)據(jù)將不會(huì)產(chǎn)生變化。

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

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

AOF

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

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

不過(guò) AOF 日志也有兩個(gè)比較大的問(wèn)題:

  • 一個(gè)是 AOF 的日志會(huì)隨著時(shí)間遞增,如果一個(gè)數(shù)據(jù)量大運(yùn)行的時(shí)間久,AOF 日志量將變得異常龐大。
  • 另一個(gè)問(wèn)題是 AOF 在做數(shù)據(jù)恢復(fù)時(shí),由于重放的量非常龐大,恢復(fù)的時(shí)間將會(huì)非常的長(zhǎng)。

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

也因?yàn)榇嗽颍琑edis 因?yàn)樘幚磉壿嬙谇岸涗洸僮魅罩驹诤?,也是?dǎo)致 Redis 無(wú)法進(jìn)行回滾的一個(gè)原因。

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

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

RDB 和 AOF 混合搭配模式

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

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

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

RDB 可以使用相隔較長(zhǎng)的時(shí)間保存策略,AOF 不需要是全量日志,只需要保存前一次 RDB 存儲(chǔ)開(kāi)始到這段時(shí)間增量 AOF 日志即可,一般來(lái)說(shuō),這個(gè)日志量是非常小的。

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

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

Ziplist

  1. 127.0.0.1:6379> hset hash_test abc 1 
  2. (integer) 1 
  3. 127.0.0.1:6379> object encoding hash_test 
  4. "ziplist" 
  5. 127.0.0.1:6379> zadd z_test 10 key 
  6. (integer) 1 
  7. 127.0.0.1:6379> object encoding z_test 
  8. "ziplist" 

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

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

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

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

在查找的時(shí)候,雖然復(fù)雜度是 O(n),但是因?yàn)閿?shù)據(jù)量少遍歷也非???,增至比 Hash 結(jié)構(gòu)本身的查詢更快。

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

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

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

Quicklist

  1. 127.0.0.1:6379> rpush key v1 
  2. (integer) 1 
  3. 127.0.0.1:6379> object encoding key 
  4. "quicklist" 

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

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

假使,Quicklist 包含了 5 個(gè) Ziplist 的節(jié)點(diǎn),每個(gè) Ziplist 列表又包含了 5 個(gè)數(shù)據(jù),那么在外部看來(lái),這個(gè) Quicklist 就包含了 25 個(gè)數(shù)據(jù)項(xiàng)。

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

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

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

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

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

對(duì)象共享

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

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

那么在 Redis 的操作時(shí):

  • 講數(shù)據(jù)庫(kù)鍵的指針指向一個(gè)現(xiàn)有的值對(duì)象。
  • 講被共享的值對(duì)象引用計(jì)數(shù)加一。

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

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

幾個(gè)定義:

  • runID:服務(wù)器運(yùn)行的 ID。
  • Offset:主服務(wù)器的復(fù)制偏移量和從服務(wù)器復(fù)制的偏移量。
  • Replication backlog:主服務(wù)器的復(fù)制積壓緩沖區(qū)。

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

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

  • 完整同步用于處理初次復(fù)制情況:完整重同步的執(zhí)行步驟和 Sync 命令執(zhí)行步驟一致,都是通過(guò)讓主服務(wù)器創(chuàng)建并發(fā)送 RDB 文件,以及向從服務(wù)器發(fā)送保存在緩沖區(qū)的寫(xiě)命令來(lái)進(jìn)行同步。
  • 部分重同步是用于處理斷線后重復(fù)制情況:當(dāng)從服務(wù)器在斷線后重新連接主服務(wù)器時(shí),主服務(wù)可以將主從服務(wù)器連接斷開(kāi)期間執(zhí)行的寫(xiě)命令發(fā)送給從服務(wù)器,從服務(wù)器只要接收并執(zhí)行這些寫(xiě)命令,就可以將數(shù)據(jù)庫(kù)更新至主服務(wù)器當(dāng)前所處的狀態(tài)。

完整重同步:

  • Slave 發(fā)送 Psync 給 Master,由于是***次發(fā)送,不帶上 runID 和 Offset。
  • Master 接收到請(qǐng)求,發(fā)送 Master 的 runID 和 Offset 給從節(jié)點(diǎn)。
  • Master 生成保存 RDB 文件。
  • Master 發(fā)送 RDB 文件給 Slave。
  • 在發(fā)送 RDB 這個(gè)操作的同時(shí),寫(xiě)操作會(huì)復(fù)制到緩沖區(qū) Replication Backlog Buffer 中,并從 Buffer 區(qū)發(fā)送到 Slave。
  • Slave 將 RDB 文件的數(shù)據(jù)裝載,并更新自身數(shù)據(jù)。

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

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

部分重同步:

  • 網(wǎng)絡(luò)發(fā)生錯(cuò)誤,Master 和 Slave 失去連接。
  • Master 依然向 Buffer 緩沖區(qū)寫(xiě)入數(shù)據(jù)。
  • Slave 重新連接上 Master。
  • Slave 向 Master 發(fā)送自己目前的 runID 和 Offset。
  • Master 會(huì)判斷 Slave 發(fā)送給自己的 Offset 是否存在 Buffer 隊(duì)列中。
  • 如果存在,則發(fā)送 Continue 給 Slave;如果不存在,意味著可能錯(cuò)誤了太多的數(shù)據(jù),緩沖區(qū)已經(jīng)被清空,這個(gè)時(shí)候就需要重新進(jìn)行全量的復(fù)制。
  • Master 發(fā)送從 Offset 偏移后的緩沖區(qū)數(shù)據(jù)給 Slave。
  • Slave 獲取數(shù)據(jù)更新自身數(shù)據(jù)。

Redis 是怎么制定過(guò)期刪除策略的

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

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

  • 定時(shí)刪除:在設(shè)置鍵過(guò)去的時(shí)間同時(shí),創(chuàng)建一個(gè)定時(shí)器,讓定時(shí)器在鍵過(guò)期時(shí)間來(lái)臨,立即執(zhí)行對(duì)鍵的刪除操作。
  • 惰性刪除:放任鍵過(guò)期不管,但是每次從鍵空間獲取鍵時(shí),都會(huì)檢查該鍵是否過(guò)期,如果過(guò)期的話,就刪除該鍵。
  • 定期刪除:每隔一段時(shí)間,程序都要對(duì)數(shù)據(jù)庫(kù)進(jìn)行一次檢查,刪除里面的過(guò)期鍵,至于要?jiǎng)h除多少過(guò)期鍵,由算法而定。

①定時(shí)刪除

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

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

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

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

②惰性刪除

與定時(shí)刪除相反,惰性刪除策略對(duì) CPU 來(lái)說(shuō)是最友好的,程序只有在取出鍵的時(shí)候才會(huì)進(jìn)行檢查,是一種被動(dòng)的過(guò)程。

與此同時(shí),惰性刪除對(duì)內(nèi)存來(lái)說(shuō)又是最不友好的,一個(gè)鍵過(guò)期,只要不再被取出,這個(gè)過(guò)期鍵就不會(huì)被刪除,它占用的內(nèi)存也不會(huì)被釋放。

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

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

 

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

  • 查看 Key 是否過(guò)期。
  • 向 Slave 節(jié)點(diǎn)傳播執(zhí)行過(guò)去 Key 的動(dòng)作。
  • 刪除過(guò)期 Key。

③定期刪除

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

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

  • 定期刪除策略每隔一段時(shí)間執(zhí)行一次刪除過(guò)期鍵操作,并通過(guò)限制刪除操作執(zhí)行的時(shí)間和頻率來(lái)減少刪除操作對(duì) CPU 時(shí)間的影響。
  • 通過(guò)合理的刪除執(zhí)行的時(shí)長(zhǎng)和頻率,來(lái)達(dá)到合理的刪除過(guò)期鍵。

總結(jié)

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

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

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

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

【51CTO原創(chuàng)稿件,合作站點(diǎn)轉(zhuǎn)載請(qǐng)注明原文作者和出處為51CTO.com】

 

責(zé)任編輯:武曉燕 來(lái)源: 51CTO技術(shù)棧
相關(guān)推薦

2018-11-26 14:55:07

iPad ProiOS 13蘋(píng)果

2021-03-12 08:02:34

Redis數(shù)據(jù)類型.

2022-08-29 08:44:39

RocketMQ數(shù)據(jù)結(jié)構(gòu)

2012-05-17 08:43:26

Windows 7Linux

2011-03-04 12:18:24

FileZilla

2021-03-18 12:08:22

概率問(wèn)題算法前端

2022-07-26 08:14:16

注冊(cè)中心ProviderConsumer

2020-03-25 14:40:45

語(yǔ)言編程語(yǔ)言Hello

2019-08-23 15:54:55

編程語(yǔ)言程序員電腦

2021-09-13 08:06:15

Redis

2020-04-29 14:50:40

代碼對(duì)比工具

2021-09-30 07:52:06

AbortSignalpromiseAPI

2011-02-23 09:55:42

konqueror

2020-12-28 05:54:37

構(gòu)造builder模式

2021-03-11 08:32:58

參數(shù)模式構(gòu)造

2022-07-05 16:36:19

通信網(wǎng)絡(luò)通信故障

2021-09-20 11:09:13

項(xiàng)目語(yǔ)言語(yǔ)法

2014-07-25 09:10:48

2015-11-12 10:45:11

問(wèn)題系統(tǒng)Linux

2024-05-06 00:00:00

緩存高并發(fā)數(shù)據(jù)
點(diǎn)贊
收藏

51CTO技術(shù)棧公眾號(hào)