Redis6 最重要的新功能「客戶端緩存」是個(gè)什么鬼?
應(yīng)用緩存通常分兩種,本地緩存和遠(yuǎn)程緩存。本地緩存就是內(nèi)存緩存 LocalCache,遠(yuǎn)程緩存就是分布式共享緩存比如 Redis。本地緩存在訪問(wèn)性能上遠(yuǎn)勝過(guò)遠(yuǎn)程緩存,但是在一致性上要弱一些。我們平時(shí)經(jīng)常會(huì)用到的 Guava Cache 就是內(nèi)存緩存技術(shù)框架。
Redis6 反復(fù)提到的「客戶端緩存」就是本地緩存,這意味著 Redis 欲將緩存的魔爪從分布式共享緩存延伸到內(nèi)存緩存,進(jìn)一步榨干緩存的技術(shù)市場(chǎng)。如果該技術(shù)未來(lái)普遍流行起來(lái),內(nèi)存緩存相關(guān)技術(shù)框架也會(huì)被打掉半壁江山。Redis 誓要將緩存能力做到極致。
我們平時(shí)經(jīng)常說(shuō)的 CAP 定律,是說(shuō)在分布式系統(tǒng)中,如果出現(xiàn)了網(wǎng)絡(luò)分區(qū) P,一致性 C 和可用性 A 不能兩全。這里的可用性可以不嚴(yán)格的簡(jiǎn)單理解為訪問(wèn)性能,性能慢的難以忍受就是不可用。內(nèi)存緩存舍一致性得高性能,遠(yuǎn)程緩存舍高性能得一致性。
到這里可能有讀者要提問(wèn)了,Redis 不是最終一致性的超高性能存儲(chǔ)數(shù)據(jù)庫(kù)么,怎么到這里它又成了「舍高性能」得「一致性」呢?
有這個(gè)疑問(wèn)是正常的,因?yàn)檫@里說(shuō)的舍和得只是相對(duì)于內(nèi)存緩存而言的。相比于內(nèi)存緩存,遠(yuǎn)程緩存的讀寫涉及到網(wǎng)絡(luò) IO,性能上自然要弱一些。

如果一個(gè) API 服務(wù)有多個(gè)物理進(jìn)程,每個(gè)進(jìn)程里面都有一份內(nèi)存緩存的數(shù)據(jù)(比如全局配置參數(shù)),這多個(gè)進(jìn)程的內(nèi)存緩存的數(shù)據(jù)在同一時(shí)間就會(huì)不一致。API 服務(wù)進(jìn)程可能會(huì)選擇每隔 N 秒輪詢式從遠(yuǎn)程緩存同步一次最新的數(shù)據(jù)到內(nèi)存,那么在這 N 秒范圍內(nèi),數(shù)據(jù)的一致性是要打折的。如果沒(méi)有這個(gè)內(nèi)存緩存,API 服務(wù)獲取全局配置參數(shù)總是要從遠(yuǎn)程緩存獲取最新的參數(shù),這就不存在配置一致性問(wèn)題。
那 Redis 要對(duì)這個(gè)「客戶端緩存」做到什么程度呢?它如何平衡性能和一致性的問(wèn)題呢?上面的例子中提到的多進(jìn)程之間本地緩存不一致的本質(zhì)在于「輪詢式」的時(shí)間間隔。如果輪詢的夠快,數(shù)據(jù)也就會(huì)更加一致一些,但是這也會(huì)對(duì)遠(yuǎn)程緩存增加訪問(wèn)壓力。還有一種比較明顯的方式就是當(dāng)遠(yuǎn)程緩存中的數(shù)據(jù)發(fā)生變動(dòng)時(shí),主動(dòng)通知各進(jìn)程更新本地緩存,那么不一致的問(wèn)題就可以得到非常顯著的緩解。
Redis6 的這個(gè)「客戶端緩存」就是用的這種方式,主動(dòng)通知客戶端 —— 你的數(shù)據(jù)過(guò)時(shí)了,請(qǐng)趕快刷新。看到這里,對(duì) Redis 稍微熟悉一點(diǎn)的同學(xué)可能很快就會(huì)想到 Redis 有個(gè) Pub/Sub 的訂閱更新能力是不是可以實(shí)現(xiàn)這個(gè)小需求,何必要大張旗鼓發(fā)明「客戶端緩存」這個(gè)新概念呢?
好,下面我們來(lái)看一下 Redis 提供的 Pub/Sub 該如何才能做到這一點(diǎn)呢?有兩種方式
使用自定義的 channel,當(dāng)遠(yuǎn)程緩存變化時(shí),修改方(業(yè)務(wù)進(jìn)程中的生產(chǎn)方)需要執(zhí)行 Publish 指令。消費(fèi)方訂閱這個(gè) channel,收到消息時(shí)刷新本地緩存。這里生產(chǎn)和消費(fèi)就有了一定程度的耦合,消費(fèi)者能不能及時(shí)刷新緩存取決于生產(chǎn)者有沒(méi)有配合 Publish 消息。而且每個(gè)業(yè)務(wù)點(diǎn)需要一個(gè)獨(dú)立的不一樣的 channel 名稱,不能混淆。
使用 Redis 自帶的 Keyspace Notification Event 內(nèi)置的一些 channel。當(dāng)某個(gè) Key 被刪除時(shí),會(huì)向 del channel 發(fā)送一個(gè) Del 事件。當(dāng)某個(gè) Key 過(guò)期時(shí),會(huì)向 expire channel 發(fā)送一個(gè) Expire 事件 。會(huì)有非常多的內(nèi)置 channel。當(dāng)某個(gè) Key 被 Set 時(shí),會(huì)向 ?set channel 發(fā)送一個(gè) Set 事件等等。這里的問(wèn)題在于客戶端需要監(jiān)聽(tīng)處理很多的 內(nèi)置channel 才能知道內(nèi)存緩存關(guān)聯(lián)的那個(gè) Redis Key 值是否發(fā)生了變化。如果開(kāi)啟了 Keyspace Notification Event,事件發(fā)生的太頻繁了,Redis 的性能也會(huì)受到顯著的影響。除此之外,這里還存在一個(gè)明顯的驚群?jiǎn)栴},我不想關(guān)心的事件 Redis 也會(huì)通知給我,因?yàn)檫@里的內(nèi)置 channel 是所有 key 共享的,任意的 key 發(fā)生的變化,channel 的消費(fèi)者都能收到相應(yīng)的事件。
基于這個(gè)原因,Redis6 對(duì)「客戶端緩存」進(jìn)行了重新設(shè)計(jì),讓它使用起來(lái)更加方便而且不會(huì)顯著導(dǎo)致 Redis 本身的性能下降。有了「客戶端緩存」,Redis 服務(wù)器本身的訪問(wèn)壓力也會(huì)顯著減輕,應(yīng)用程序只需要訪問(wèn)本地內(nèi)存就可以得到期望的數(shù)據(jù),如此 Redis 就可以應(yīng)用于更高的并發(fā)應(yīng)用場(chǎng)景。
Redis6 將「客戶端緩存」稱為「Client Key Tracking」,表示客戶端對(duì)指定的 Key 感興趣,它會(huì)訂閱這些 Key 的修改通知,如果 Key 發(fā)生了變化,客戶端會(huì)立即收到一個(gè)「緩存失效」通知。緊接著客戶端就會(huì)清空并重建本地緩存。
那如何訂閱具體的 Key 呢,Redis6 提供了兩種方式,自動(dòng)訂閱和手動(dòng)訂閱。自動(dòng)訂閱就是客戶端的某個(gè)開(kāi)關(guān)打開(kāi)后,服務(wù)器會(huì)自動(dòng)幫助客戶端訂閱它所讀取的所有的 Key。這種自動(dòng)訂閱的方式雖然很方便,在某些特定的場(chǎng)合下可能并不合適。所以 Redis 也提供了 手動(dòng)訂閱的方式,需要在每一條需要緩存的讀 Key 命令之前打上一條特殊的標(biāo)記表示接下來(lái)的這條指令讀取的值會(huì)緩存在內(nèi)存里。
除此之外,Redis 還提供了前綴訂閱指令(也叫廣播指令),可以讓客戶端一次性訂閱以固定前綴開(kāi)頭的所有的 Key。這種方式需要小心使用,如果前綴對(duì)應(yīng)的 Key 非常多而且修改又很頻繁就會(huì)給服務(wù)器帶來(lái)廣播風(fēng)暴,嚴(yán)重影響服務(wù)器的性能。
使用 Client Key Tracking 的原則就是讀多寫少,比如業(yè)務(wù)系統(tǒng)使用的全局配置參數(shù)
變化頻繁的 Key 不要本地緩存,緩存刷新過(guò)于頻繁
讀頻率低的 Key 不要緩存,緩存意義不大
遺憾的是,大部分企業(yè)都還沒(méi)能用得上 Redis5,就更別提 Redis6 了。對(duì)于這個(gè)新特性,我們還是慢慢等著時(shí)間來(lái)逐步接受它吧。
