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

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