1. 程式人生 > 程式設計 >【譯】antirez:Redis6將支援客戶端快取

【譯】antirez:Redis6將支援客戶端快取

本文翻譯自Redis作者antirez的一篇部落格,原文地址是:antirez.com/news/130

紐約Redis日已經結束了,我仍然與義大利時區同步,早上5點30起床,並立即走上了曼哈頓的街道,我很喜歡這裡的風景,並且享受著成為這裡的一部分。當時我正在考慮釋出Redis 6的release版本,這是在未來一段時間最重要的事了。新版本的Redis協議(RESP3)推進得還很慢,如果沒有一個好的理由,明智的人是不會更換工具的。但我為什麼要堅持提升協議呢?有兩個主要的原因,一是需要給客戶端提供更加具有語義的回覆,二是提供一箇舊版本不能實現的新功能:客戶端快取。

時間倒回一年前,我到達聖安東尼奧的Redis Conf 2018。當時公司就有一個共識是客戶端快取是Redis在未來非常重要的事情。如果我們需要更快的儲存和更快的快取,我們就需要在客戶端儲存一部分資訊。這是提供低延遲和大規模資料服務的很自然的想法。事實上,基本上每個大公司也都是這樣做的,因為這是唯一的辦法。然而Redis沒有辦法在這一過程中協助客戶。一個巧合讓Ben Malec想要在Redis Conf上做一些關於客戶端快取的演講,他只使用Redis提供的工具和一些非常聰明的想法。

作者注:演講地址是https://www.youtube.com/watch?v=kliQLwSikO4

Ben的演講啟發了我,為了實現Ben的設計,其中有兩個關鍵點。第一個是使用Redis Cluster的“hash slot”的概念,把key分成了16k個組。採用這種方式使得客戶端不需要追蹤每一個key的位置,可以使用一個簡單的元資料來定位key所在的group。Ben使用Pub/Sub模式來通知key的改變,所以他需要應用程式的一些幫助,然而這種模式是很固定的。要修改一個key?還需要釋出失效訊息。在客戶端是否快取了key呢?要記住快取每個key和收到失效訊息時的時間戳,記住每個slot的失效時間。當使用一個快取的key時,先做一個懶清除,通過檢查快取key的時間戳是否早於slot收到失效資訊的時間戳。這種情況下,這個key就是過時的資料,你可以再次訪問伺服器。

在看完演講之後,我意識到這是一個在伺服器內使用的好主意,為了讓Redis能夠為客戶端做一部分工作,是客戶端快取更加簡單高效,所以我回到家寫下了我的設計檔案:groups.google.com/d/msg/redis…

但為了實現我的設計,我必須專注於修改Redis協議使它變得更加完善,所以我開始編寫RESP3和Redis 6的其他特性(比如ACL)的規範和程式碼,客戶端快取是Redis許多迭代想法中的一種,有些想法因為時間不夠放棄了。

當時我在紐約街頭思考這個想法。後來和一些朋友去吃午飯喝咖啡。當我返回酒店房間後,距離第二天起飛還有一整晚的時間,所以我開始按照一年前寫的提案來寫Redis 6的客戶端快取的實現。

Redis伺服器助理客戶端快取,最終叫做“tracking”(我也可能改主意),是一個由幾個關鍵想法組成的非常簡單功能。

key空間被分割到”caching slots“,但他們比Ben使用的hash slots要多得多。我們使用CRC64的24位輸出,所以有超過1600萬個不同的slot。為什麼這麼多呢?因為我認為你想要有一個1億key的伺服器。然而一個失效資訊影響的key不應該多於客戶端快取中的key。Redis中失效表佔用130M的記憶體:8位元組的指標指向16M的條目。這對我來說是可以接受的,如果你想要使用新功能,你將充分利用你在客戶端的所有記憶體,所以使用130MB在伺服器端是好的,你可以獲得更細粒度的失效。

客戶端使用“opt in”方法開啟這個功能,只需要一個簡單的命令:

CLIENT TRACKING on
複製程式碼

伺服器總是返回+OK,從這時起,每個命令都在命令表中被標記為“只讀”,不再給呼叫者返回keys,並記住客戶端請求的所有的key。儲存這種資訊時非常簡單的,每個Redis客戶端都有自己的唯一ID,所以如果ID是123的客戶端傳送了MGET命令,需要從slot 1,2和5獲取key,那麼失效表中我們就需要記錄如下資訊:

1 -> [123]
2 -> [123]
5 -> [123]
複製程式碼

接著,ID為444的客戶端也需要到slot5請求key了,那麼表資訊將變成:

5 -> [123,444]
複製程式碼

現在其他客戶端修改了slot 5中的某個key,Redis將會檢查失效表,發現客戶端123和444都快取了這個slot上的key。我們將會給這些客戶端傳送失效資訊,然後會記錄下slot最後的失效時間戳,並在以後懶檢查快取物件的時間戳,並對照後判斷是否失效。此外,客戶端可以回收表中快取的指定slot的物件。這種具有24位hash函式的方法不是問題,因為我們即使快取幾千萬的key,也不會有很長的列表。傳送了失效資訊後,我們就可以刪除失效表中的項,這樣直到這些客戶端不再讀這些slot的key,我們就不再向他們傳送失效訊息。

需要注意的是,客戶端不必強制使用24位hash函式。也可能使用20位,然後移動Redis傳送的失效訊息的slot。不確定是否有很多很好的理由這樣做,但是記憶體受限時,這可能是一種想法。

如果你密切關注我說的話,你會開始考慮同一連線既會接收到正常的客戶端回覆,又會接收失效訊息。這可以通過RESP3實現,因為失效作為“推送”訊息型別傳送。如果客戶端是一個阻塞型別的,並且不是事件驅動型別的客戶端,就會變得比較複雜:

應用程式需要一些方法來不時讀取新資料,這看起來既複雜又脆弱。在這種情況下,為了接收失效訊息,使用另一個應用程式執行緒和不同的客戶端可能會更好。所以你可以使用以下命令來允許這樣的操作:

CLIENT TRACKING on REDIRECT 1234
複製程式碼

基本上我們可以說我們使用當前連線獲得的所有key,並希望失效訊息傳送到客戶端1234。在連線池的情況下多個客戶端可能會要求將失效訊息重定向到單個客戶端。你需要做的就是建立特殊連線以接收失效訊息,呼叫CLIENT ID以瞭解此客戶端連線哪個ID,然後啟用跟蹤。

現在只剩下一個問題了:如果我們失去了失效連線怎麼辦?我們可能因為不能接收到失效訊息而陷入麻煩。通常,應用會檢測連線,嘗試重連,並清除快取。為了確保失效連線處於連線狀態,不時地向伺服器傳送ping請求可能是一個更好的主意。然而,為了降低過期資料的風險,Redis也將開始通知客戶端將失效訊息重定向到其他客戶端,只要使用特殊的推送訊息:下一個請求就會使客戶端知道連線已經斷開。

我剛才描述的已經合併到Redis的unstable分支。可能不是最終的處理方法,但是在第一個Redis 6釋出版本之前還有幾個月的時間,我們還有時間修改所有的事情:可以告訴我你的反饋。我也會再尋找其他RESP2可行的方法。這隻有在重定向開啟時才有效,並且客戶端要進入Pub/Sub模式監聽訊息。通過這種方式,完全可以複用舊客戶端。

我希望這足以刺激你的胃口:如果我們在Redis中執行的很好,然後記錄下來,讓客戶端作者知道該如何支援,資料可能比以往更接近應用程式,甚至在小型團隊執行的應用程式中,到目前為止還沒有嘗試客戶端快取。對於正在準備做的大型團隊和非常大的應用程式,降低實現成本和複雜性。