Redis 6.0 客戶端緩存的服務(wù)器端實現(xiàn)

原文:https://redis.io/topics/client-side-caching

翻譯:Wen Hui

轉(zhuǎn)載:中間件小哥

客戶端緩存是用于提供高性能服務(wù)的一項技術(shù)。它使用應(yīng)用服務(wù)器節(jié)點(通常情況下和數(shù)據(jù)庫服務(wù)器使用不同的物理機)的可用內(nèi)存,用來在應(yīng)用端直接存儲一部分?jǐn)?shù)據(jù)庫信息。

正常情況下當(dāng)客戶端請求應(yīng)用服務(wù)器一些數(shù)據(jù)時,應(yīng)用服務(wù)器會請求數(shù)據(jù)庫這些信息,如下圖所示:

image

當(dāng)使用客戶端緩存時,應(yīng)用服務(wù)器端會存儲經(jīng)常訪問的數(shù)據(jù)請求,以便在下次客戶端請求過程中重用之前的數(shù)據(jù)庫查詢回復(fù),而無需再向數(shù)據(jù)庫進(jìn)行查詢。

image

盡管用于本地緩存的應(yīng)用程序內(nèi)存可能不是很大,但是與請求諸如數(shù)據(jù)庫之類的網(wǎng)絡(luò)服務(wù)相比,訪問本地計算機內(nèi)存所需的時間要小幾個數(shù)量級。由于在通常情況下,少量比例數(shù)據(jù)會經(jīng)常頻繁的被訪問,因此該模式可以極大地減少應(yīng)用程序獲取數(shù)據(jù)的延遲,并同時減少數(shù)據(jù)庫端的負(fù)載。

此外,在許多數(shù)據(jù)集中,信息很少進(jìn)行更改。例如,社交網(wǎng)絡(luò)中的大多數(shù)用戶帖子要么是不變的,要么很少被用戶編輯。再加上通常只有一小部分帖子非常受歡迎的事實,要么是因為一小群用戶擁有大量關(guān)注者,或者因為最近的帖子具有更高的曝光度,由此可見為什么這種模式在實際情況下會非常有用。

通常來說,客戶端緩存的兩個主要優(yōu)點是:

1. 可用的數(shù)據(jù)延遲非常短。

2. 數(shù)據(jù)庫系統(tǒng)接收的查詢較少,從而可以使用更少的節(jié)點來提供相同的數(shù)據(jù)服務(wù)。

在計算機科學(xué)中只有兩大問題

上述模式的問題是在數(shù)據(jù)被修改或過期時,如何使應(yīng)用程序保存的信息無效,以避免向用戶顯示陳舊數(shù)據(jù)。例如,在上面的應(yīng)用程序本地緩存了user:1234信息之后,Alice可以將其用戶名更新為Flora。但是應(yīng)用程序可能會繼續(xù)為用戶1234提供舊的用戶名。

有時,取決于我們要建模的應(yīng)用程序,這個問題并不重要,因此客戶端將只使用固定的最大“生存時間”來緩存信息。一旦過了給定的時間,該信息將不再被視為有效。使用Redis時,更復(fù)雜的模式會利用發(fā)布/訂閱系統(tǒng),以便向偵聽的客戶端發(fā)送無效消息。從使用的帶寬的角度來看,這是可行的,但卻是棘手且昂貴的,因為這種模式通常涉及向應(yīng)用程序中的每個客戶端發(fā)送無效消息,即使某些客戶端可能沒有無效數(shù)據(jù)的任何副本。 此外,每個更改數(shù)據(jù)的應(yīng)用程序查詢都需要使用PUBLISH命令,從而使數(shù)據(jù)庫花費更多的CPU時間來處理此命令。

無論使用哪種模式,都有一個簡單的事實:許多非常大的應(yīng)用程序都實現(xiàn)某種形式的客戶端緩存,因為這是擁有快速存儲或快速緩存服務(wù)器的下一個邏輯步驟。因此,Redis 6實現(xiàn)了對客戶端緩存的直接支持,以使該模式更易于實現(xiàn),更易于訪問,可靠且高效。

客戶端緩存的Redis實現(xiàn)

Redis客戶端緩存支持稱為跟蹤(tracking),并具有兩種模式:

在默認(rèn)模式下,服務(wù)器會記住給定客戶端訪問了哪些鍵,并在別的客戶端修改相同的鍵時向客戶端發(fā)送無效消息。這將花費服務(wù)器端的內(nèi)存,但僅對在內(nèi)存中擁有修改的鍵的客戶端發(fā)送無效消息。

相反,在廣播模式下,服務(wù)器不會嘗試記住給定客戶端訪問了哪些鍵,因此該模式在服務(wù)器端根本不會花費任何內(nèi)存。相反,客戶端會訂閱鍵前綴(例如object:或user :),并且每次其他客戶端修改與該前綴匹配的鍵值時都會收到通知消息。

回顧一下,現(xiàn)在讓我們暫時忘記廣播模式,重點關(guān)注第一種模式。我們將在后面詳細(xì)介紹廣播模式。

客戶可以根據(jù)需要啟用跟蹤。連接開始時未啟用跟蹤。

啟用跟蹤后,服務(wù)器會記住每個客戶端在連接生存期內(nèi)請求過的鍵(通過發(fā)送有關(guān)此鍵的讀取命令)。

當(dāng)某個客戶端修改了某個鍵,或者由于鍵具有關(guān)聯(lián)的到期時間而將其逐出,或者由于最大內(nèi)存策略而將其逐出時,所有啟用了跟蹤且可能已緩存鍵的客戶端都會收到無效消息通知。

當(dāng)客戶端收到無效消息時,要求它們刪除相應(yīng)的鍵值信息,以避免提供過時的數(shù)據(jù)。

這是協(xié)議的示例:

image

從表面上看,這看起來很棒,但是如果你想到有10萬個已連接的客戶端在每個持久連接中都請求數(shù)百萬個鍵,則服務(wù)器將會因為存儲太多信息而崩潰。因此,Redis使用兩個關(guān)鍵思想來限制服務(wù)器端使用的內(nèi)存量以及處理實現(xiàn)該功能的數(shù)據(jù)結(jié)構(gòu)的CPU成本:

服務(wù)器會在一個全局列表中記住可能已將給定鍵值緩存過的客戶端列表。該表稱為無效表。這個無效表可以設(shè)置最大數(shù)量的記錄,如果插入了新鍵值,則服務(wù)器可以通過假裝已修改(即使沒有修改)并將其發(fā)送到客戶端來驅(qū)逐舊條目。這樣做,它可以使服務(wù)器端回收用于此鍵值的內(nèi)存,即使這樣會迫使鍵值在本地客戶端緩存被逐出。

在無效表內(nèi)部,我們實際上不需要存儲指向客戶端結(jié)構(gòu)的指針,這將在客戶端斷開連接時在無效表中需要強制執(zhí)行垃圾回收過程:相反,我們要做的只是存儲客戶端ID(每個Redis客戶端都有唯一的數(shù)字ID)。如果客戶端斷開連接,則隨著緩存槽無效,將逐步收集垃圾信息。

在這里只有一個鍵空間,不以數(shù)據(jù)庫編號做劃分。因此,如果客戶端在數(shù)據(jù)庫2中緩存鍵foo,而其他一些客戶端在數(shù)據(jù)庫3中更改了鍵foo的值,則仍然會發(fā)送無效消息。這樣,我們可以忽略數(shù)據(jù)庫編號,從而減少了內(nèi)存使用量和實現(xiàn)的復(fù)雜度。

兩種連接方式

使用Redis 6支持的新版本的Redis協(xié)議RESP3,可以在同一連接中運行數(shù)據(jù)查詢并接收無效消息。但是,許多客戶端實現(xiàn)可能更喜歡使用兩個獨立的連接來實現(xiàn)客戶端緩存:一個用于數(shù)據(jù),另一個用于無效消息。因此,當(dāng)客戶端啟用跟蹤時,它可以通過指定不同連接的“客戶端ID”來指定將無效消息重定向到另一個連接。許多數(shù)據(jù)連接可以將無效消息重定向到同一連接,這對于實現(xiàn)連接池的客戶端很有用。這兩個連接模型是RESP2唯一支持的模型(缺乏在同一連接中復(fù)用不同類型信息的能力)。

這次我們將通過在舊的RRESP2模式下使用實際的Redis協(xié)議顯示一個示例,如何完成一個完整的會話,包括以下步驟:啟用跟蹤重定向到另一個連接,請求鍵的值信息以及在鍵的內(nèi)容被其他客戶端修改的情況下,獲取服務(wù)器發(fā)送的鍵失效信息。

首先,客戶端打開第一個用于失效的連接,請求返回連接的ID,并通過Pub / Sub訂閱專用通道,該通道在RESP2模式下用于獲得失效消息(請記住,RESP2是通常的Redis協(xié)議,而不是你可以使用的更高級的協(xié)議):

image

客戶端可以決定在本地內(nèi)存中緩存“ foo” =>“ bar”。

現(xiàn)在,另一個客戶端將修改“ foo”鍵的值:

image

因此,失效連接將收到一條失效信息,該信息使指定的鍵值失效。

image

客戶端將檢查在此緩存槽中是否有緩存的鍵值信息,并將逐出不再有效的信息。

請注意,Pub / Sub消息的第三個元素不是單個鍵,而是只有一個元素的Redis數(shù)組。因為我們發(fā)送一個數(shù)組,所以如果有成組的鍵無效,我們可以在一條消息中做到這一點。

關(guān)于理解使用RESP2的客戶端緩存以及為了讀取無效消息而進(jìn)行的Pub / Sub連接非常重要的一點是,使用Pub / Sub完全是一個為了重用舊的客戶端實現(xiàn)的技巧,但實際上并未真正發(fā)送并被所有訂閱該頻道的客戶所接收。只有我們在CLIENT命令的REDIRECT參數(shù)中指定的連接才會實際收到Pub / Sub消息,從而使此功能具有更大的可伸縮性。

如果改用RESP3,則將無效消息作為推送消息發(fā)送(在同一連接中,或者在使用重定向時在輔助連接中)(請參閱RESP3規(guī)范以獲取更多信息)。

追蹤用來追蹤什么

如上面例子可以看到,默認(rèn)情況下,客戶端不需要告訴服務(wù)器它們正在緩存哪些鍵。服務(wù)器會追蹤只讀命令上下文中提到的每個鍵,因為它可能會被緩存。

這具有明顯的優(yōu)點,即不需要客戶端告訴服務(wù)器它正在緩存什么。此外,在許多客戶端實現(xiàn)中,這就是你想要的,因為一個好的解決方案可能是使用先進(jìn)先出的方法僅緩存尚未緩存的所有內(nèi)容:我們可能希望緩存固定數(shù)量的對象,每個對象我們檢索到新數(shù)據(jù)后,就可以對其進(jìn)行緩存,丟棄最早的緩存對象。更高級的實現(xiàn)可能會刪除最不常用的對象或類似對象。

請注意,無論如何,如果服務(wù)器上有寫流量,則緩存槽將在一段時間內(nèi)失效。通常,當(dāng)服務(wù)器假設(shè)我們得到的東西也緩存時,我們就要進(jìn)行權(quán)衡:

1. 當(dāng)客戶端傾向于使用歡迎新對象的策略來緩存更多內(nèi)容時,這樣做會更有效率。

2. 服務(wù)器將被迫保留有關(guān)客戶端鍵的更多數(shù)據(jù)。

3. 客戶端將收到有關(guān)其未緩存的對象的無效消息。

因此,下一節(jié)將介紹另一種方法

OPT-IN 模式

客戶端實現(xiàn)可能只希望緩存選定的鍵,并明確地與服務(wù)器通信它們將緩存的內(nèi)容和不緩存的內(nèi)容:緩存新對象時,這將需要更多的帶寬,但同時會減少服務(wù)器需要記住的數(shù)據(jù)量,以及客戶端收到的無效消息數(shù)量。

為此,必須使用OPTIN選項啟用跟蹤:

image

在這種模式下,默認(rèn)情況下,不應(yīng)緩存讀取查詢中的鍵,而是當(dāng)客戶端要緩存某些內(nèi)容時,它必須在實際命令檢索數(shù)據(jù)之前立即發(fā)送一個特殊命令CACHING:

image

為了使協(xié)議更有效率,可以使用NOREPLY選項發(fā)送CACHING命令:在這種情況下,CACHING命令中客戶端不會收到服務(wù)器返回的信息:

image

CACHING命令會影響隨后執(zhí)行的命令,但是,如果下一個命令是MULTI,則將跟蹤事務(wù)中的所有命令。同樣,對于Lua腳本,將跟蹤該腳本執(zhí)行的所有命令。

廣播模式

到目前為止,我們描述了Redis實現(xiàn)的第一個客戶端緩存模型。還有一個稱為廣播模式,它從另一個折衷的角度來看問題,它不消耗服務(wù)器端的任何內(nèi)存,而是向客戶端發(fā)送更多的無效消息。在這種模式下,我們有以下主要行為:

客戶端使用BCAST選項啟用客戶端緩存,并使用PREFIX選項指定一個或多個前綴。例如:CLIENT TRACKING on REDIRECT 10 BCAST PREFIX object:PREFIX user:。如果根本沒有指定任何前綴,則假定該前綴為空字符串,因此客戶端將會接收每個被修改的鍵的無效消息。相反,如果使用一個或多個前綴,則僅在失效消息中發(fā)送與指定前綴之一匹配的鍵。

服務(wù)器未在失效表中存儲任何內(nèi)容。相反,它僅使用不同的前綴表,其中每個前綴都與客戶端列表相關(guān)聯(lián)。

每次修改與任何前綴匹配的鍵時,所有訂閱該前綴的客戶端都將收到無效消息。

服務(wù)器的CPU消耗與注冊前綴數(shù)量成正比。如果只有幾個,幾乎看不出任何區(qū)別。使用大量前綴,CPU成本可能變得非常高。

在這種模式下,服務(wù)器可以優(yōu)化為訂閱給定前綴的所有客戶端創(chuàng)建單個回復(fù)的過程,并將相同的回復(fù)發(fā)送給所有客戶端。這有助于降低CPU使用率。

避免競爭條件

在實施客戶端緩存以將無效消息重定向到其他連接時,你應(yīng)該意識到存在競爭狀況。請參見以下示例交互,在此我們將數(shù)據(jù)連接稱為“ D”,并將失效連接稱為“ I”

image

如上所示,由于對GET的回復(fù)返回給客戶端會有較長延時,因此在已經(jīng)不再有效的實際數(shù)據(jù)之前,我們收到了無效消息。因此,我們將繼續(xù)提供舊版本的foo鍵的值信息。為避免此問題,當(dāng)我們使用占位符發(fā)送命令時,填充緩存是一個好主意:

image

當(dāng)對數(shù)據(jù)和無效消息使用單個連接時,這種競爭條件是不可能的,因為在這種情況下消息的順序始終是已知的。

與服務(wù)器斷開連接時該怎么辦

同樣,如果丟失與套接字的連接以獲取無效消息,則可能會以客戶端收到陳舊數(shù)據(jù)結(jié)束。為了避免這個問題,我們需要做以下事情:

1. 確保如果連接丟失,則刷新本地緩存。

2. 在將RESP2與Pub / Sub一起使用時,或者在RESP3上,都定期對無效訂閱連接進(jìn)行ping操作(即使連接處于Pub / Sub模式下,也可以發(fā)送PING命令!)。如果連接看起來斷開并且我們無法接收ping回復(fù),請在設(shè)定的最長時間后關(guān)閉連接并刷新緩存。

什么需要緩存

客戶可能希望運行有關(guān)給定緩存的鍵在實際請求中被調(diào)用的次數(shù)的內(nèi)部統(tǒng)計信息,以了解以后對哪些鍵使用客戶端緩存。一般來說:

· 我們不想緩存很多不斷變化的鍵。

· 我們不想緩存很多很少被請求的鍵。

· 我們希望緩存經(jīng)常請求的鍵并以合理的速率進(jìn)行更改。有關(guān)鍵沒有以合理的速度更改的示例,請考慮一個不斷增加的全局計數(shù)器。

但是,更簡單的客戶端可能只是使用一些隨機采樣來逐出數(shù)據(jù),只是記住最后一次被查詢的特定鍵值,從而試圖逐出最近未被查詢的鍵。

有關(guān)客戶端庫實現(xiàn)的建議

· 處理TTL:如果要支持帶TTL的緩存鍵,請確保查詢鍵的TTL值并在本地緩存中設(shè)置TTL。

· 即使沒有TTL,在每個鍵中都放置一個最大TTL是一個比較好的做法。這是個很好的保護(hù)措施,可避免可能導(dǎo)致客戶端在本地副本中包含舊數(shù)據(jù)的錯誤或連接問題。

· 絕對需要限制客戶端使用的內(nèi)存量。添加新鍵時,必須有一種方法可以將舊鍵逐出。

限制Redis使用的內(nèi)存量

只需確保為Redis記住的最大鍵數(shù)配置一個合適的值,或者使用BCAST模式,該模式在Redis端根本不占用任何內(nèi)存。請注意,當(dāng)不使用BCAST時,Redis消耗的內(nèi)存與跟蹤的鍵數(shù)量以及請求此類鍵的客戶端數(shù)量成正比。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 230,563評論 6 544
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 99,694評論 3 429
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 178,672評論 0 383
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經(jīng)常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,965評論 1 318
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 72,690評論 6 413
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 56,019評論 1 329
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 44,013評論 3 449
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 43,188評論 0 290
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 49,718評論 1 336
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 41,438評論 3 360
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,667評論 1 374
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 39,149評論 5 365
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 44,845評論 3 351
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 35,252評論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,590評論 1 295
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 52,384評論 3 400
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 48,635評論 2 380