近日 Redis 6.0.0 GA 版本發佈,這是 Redis 歷史上最大的一次版本更新,包括了客戶端緩存 (Client side caching)、ACL、Threaded I/O 和 Redis Cluster Proxy 等諸多更新。

我們今天就依次聊一下客戶端緩存的必要性、具體使用、原理分析和實現。

爲什麼需要客戶端緩存

我們都知道,使用 Redis 進行數據的緩存主要目的是減少對 MySQL 等數據庫的訪問,提供更快的訪問速度,畢竟 《Redis in Action》中提到的, Redis 的性能大致是普通關係型數據庫的 10 ~ 100 倍。

所以,如下圖所示,Redis 用來存儲熱點數據,Redis 未命中,再去訪問數據庫,這樣可以應付大多數情況下的性能要求。

但是,Redis 也有其性能上限,並且訪問 Redis 必然有一定的網絡 I/O 以及序列化反序列化損耗。所以,往往會引入進程緩存,將最熱的數據存儲在本地,進一步加快訪問速度。

如上圖所示(示意圖,細節不必過度在意,下同),Guava Cache 等進程緩存作爲一級緩存,Redis 作爲二級緩存:

  • 先去 Guava Cache 中查詢數據,如果命中則直接返回。

  • Guava Cache 中未命中,則再去 Redis 中查詢,如果命中則返回數據,並在 Guava Cache 中設置此數據。

  • Redis 也未命中的話,只有去 MySQL 中查詢,然後依次將數據設置到 Redis 和 Guava Cache 中。

只使用 Redis 分佈式緩存時,遇到數據更新時,應用程序更新完 MySQL 中的數據,可以直接將 Redis 中對應緩存失效掉,保持數據的一致性。

而進程內緩存的數據一致性比分佈式的緩存面臨更大的挑戰。數據更新的時候,如何通知其他進程也更新自己的緩存呢?

如果按照分佈式緩存的思路,我們可以設置極短的緩存失效時間,這樣不必實現複雜的通知機制。

但是不同進程內的數據依然會面臨不一致的問題,並且不同進程緩存失效時間不統一,同一個請求到了不同的進程,可能出現反覆幻讀的情況。

Ben 在 RedisConf18 給出了一個方案(視頻和 PPT 鏈接在文末),通過 Redis 的 Pub/Sub,可以通知其他進程緩存對此緩存進行刪除。如果 Redis 掛了或者訂閱機制不靠譜,依靠超時設定,依然可以做兜底處理。

Antirez(Redis 的作者)也正是聽取 Ben 這個方案後,才決定在 Redis Server 支持客戶端緩存的,因爲在有服務端參與的情況下可以更好的處理上述這些問題。

功能介紹和演示

下面使用 Docker 安裝 Redis 6.0.1,然後使用 telnet 來簡單演示一下 Redis 6.0 的客戶端緩存功能。所有相關的功能如下圖所示,分別是使用RESP3 協議版本的普通模式和廣播模式,以及使用 RESP2 協議版本的轉發模式。我們先來看普通模式。

普通模式

先使用 redis-cli 設置緩存值 test=111,使用 telnet 連接上 Redis,然後發送 hello 3 開啓 RESP3 協議。

這裏需要注意, Redis 服務端只會 track 客戶端在一個連接生命週期內的獲取的只讀命令的 key值。Redis 客戶端默認不開啓 track 模式 ,需要使用命令開啓,然後必須要先獲取一次 test 的值,這樣 Redis 服務器纔會記錄它。

當鍵被修改,或者因爲失效時間(expire time)和內存上限 maxmemory 策略被驅除時,Redis 服務端會通知這些客戶端。 我們這裏簡單地更新 test 的值,telnet 則會收到如下通知:

如果你再一次更新 test 值,這次 telnet 就不會再收到失效(invalidate)消息。除非 telnet 再進行一次 get 操作,重新 tracking 對應的鍵值。

也就是說 Redis 服務端記錄的客戶端 track 信息只生效一次,發送過失效消息後就會刪除,只有下次客戶端再次執行只讀命令被 track,纔會進行下一次消息通知 。

取消 tracking 的命令如下所示:

廣播模式

Redis 還提供了一種廣播模式(BCAST),它是另外一種客戶端緩存的實現方式。這種方式下 Redis 服務端不再消耗過多內存存儲信息,而是發送更多的失效消息給客戶端。

這是服務端存儲過多數據,消耗內存和客戶端收到過多消息,消耗網絡帶寬之間的權衡(tradeoff)。

如果你不想所有的鍵值的失效消息都收到,則可以限制 key 的前綴,如下命令則表示只關注前綴爲 test 的鍵值的消息。一般來說,業務的緩存 key 都是根據業務擁有統一的前綴,所以這一特性十分方便。

與普通模式必須獲取一次鍵的規則不同, 廣播模式下,只要鍵被修改或刪除,符合規則的客戶端都會收到失效消息,而且是可以多次獲取的。

與普通模式相比,雖然少存儲了一些數據,但是由於需要對前綴規則進行匹配,會消耗一定的 CPU 資源,所以 注意別使用過長的前綴。

轉發模式

上述操作時客戶端都需要先開啓 RESP3,Redis 爲了兼容 RESP2 協議提供了轉發(Redirect)模式,不再使用 RESP3 原生支持 PUSH 消息,而是將消息通過 Pub/Sub 通知給另外一個客戶端,具體流程如下圖所示:

這裏需要兩個 telnet,其中一個 telnet 需要訂閱 _redis_:invalidate 信道。然後另一個 telnet 開啓 Redirect 模式,並制定將失效消息通過訂閱信道發送給第一個 telnet。

你會發現,轉發模式和文章開始提到的多級緩存中的更新機制很類似了,只不過那個方案中是業務系統修改完 key 後發送消息通知,而這裏是 Redis 服務端代替業務系統發送消息通知。

OPTIN和OPTOUT選項

使用 OPTIN 可以選擇性的開啓 tracking。只有你發送 client caching yes (Redis 文檔中是 CACHING 命令,但是實驗時發現無效)之後的下一條的只讀命令的 key 纔會 tracking,否則其他的只讀命令的 key 不會被 tracking。

而 OPTOUT 參數與之相反,你可以有選擇的退出 tracking。發送 client caching off 之後的下一條只讀命令的 key 不會被 tracking,其他只讀命令都會被 tracking。

OPTIN 和 OPTOUT 是針對非 BCAST 模式,也就是隻有發送了某個 key 的只讀命令後,纔會追蹤相應的 key。而 BCAST 模式是無論你是否發送某個 key 的只讀命令,只有 Redis 修改了 key,都會發送相應的 key 的失效消息(前綴匹配的)。

NOLOOP選項

默認情況下,失效消息會發送給所有需要的 Redis 客戶端,但是有些情況下觸發失效消息也就是更新 key 的客戶端不需要收到該消息。

設置 NOLOOP,可以避免這種情況,更新 Key 的客戶端將不再收到消息,該選項在普通模式和廣播模式下都適用。

trackingtablemax_keys

最大 tracking 上限 trackingtablemax_keys。

由上文可以知道,普通模式下需要存儲大量的被 tracking 的 key 和客戶端信息(具體存儲的數據下文中會講解),所以當 10k 客戶端使用該模式處理百萬個鍵時,會消耗大量的內存空間,所以 Redis 引入了 trackingtablemax_keys 配置,默認爲無,不限制。

當有一個新的鍵被 tracking 時,如果當前 tracking 的 key 的數量大於 trackingtablemax_keys,則會隨機刪除之前 tracking 的 key,並且向對應的客戶端發送失效消息。

原理和源碼實現

普通模式原理

我們也先講解普通模式的原理,Redis 服務端使用 TrackingTable 存儲普通模式的客戶端數據,它的數據類型是基數樹(radix tree)。

基數樹是針對稀疏的長整型數據查找的多叉搜索樹,能快速且節省空間的完映射,一般用於解決 Hash衝突和 Hash表大小的設計問題,Linux 的內存管理就使用了它。

Redis 用它存儲鍵的指針和客戶端 ID 的映射關係。因爲鍵對象的指針就是內存地址,也就是長整型數據。客戶端緩存的相關操作就是對該數據的增刪改查:

  • 當開啓 track 功能的客戶端獲取某一個鍵值時,Redis 會調用 enableTracking 方法使用基數樹記錄下該 key 和 clientId 的映射關係。

  • 當某一個 key 被修改或刪除時,Redis 會調用 trackingInvalidateKey 方法根據 key 從 TrackingTable 中查找所有對應的客戶端ID,然後調用 sendTrackingMessage 方法發送失效消息給這些客戶端(會檢查 CLIENT_TRACKING 相關標誌位是否開啓和是否開啓了 NOLOOP)。

  • 發送完失效消息後,根據鍵的指針值將映射關係從 TrackingTable中刪除。

  • 客戶端關閉 track 功能後,因爲刪除需要進行大量操作,所以 Redis 使用懶刪除方式,只是將該客戶端的 CLIENT_TRACKING 相關標誌位刪除掉。

廣播模式原理

廣播模式與普通模式類似,Redis 同樣使用 PrefixTable 存儲廣播模式下的客戶端數據,它存儲前綴字符串指針和(需要通知的key和客戶端ID)的映射關係。它和廣播模式最大的區別就是真正發送失效消息的時機不同:

  • 當客戶端開啓廣播模式時,會在 PrefixTable的前綴對應的客戶端列表中加入該客戶端ID。

  • 當某一個 key 被修改或刪除時,Redis 會調用 trackingInvalidateKey 方法, trackingInvalidateKey 方法中如果發現 PrefixTable 不爲空,則調用 trackingRememberKeyToBroadcast 依次遍歷所有前綴,如果key 符合前綴規則,則記錄到 PrefixTable 對應的位置。

  • 在 Redis 的事件處理週期函數 beforeSleep 函數里會調用 trackingBroadcastInvalidationMessages 函數來真正發送消息。

處理最大tracking上限

Redis 會在每次執行過命令後(processCommand方法)調用 trackingLimitUsedSlots 來判斷是否需要進行清理:

  • 判斷 TrackingTable 中鍵的數量是否大於 trackingtablemax_keys;

  • 在一定時間段內(不能太長,阻塞主流程),隨機從 TrackingTable 中選出一個鍵刪除,直到數量小於或者時間用完爲止。

具體源碼

關於源碼,在 tracking.c 文件下,我們這裏只看一下最爲關鍵的 trackingInvalidateKey 函數和 sendTrackingMessage 函數,理解了這兩個函數,廣播模式和處理最大 tracking 上限等相關函數都與之類似。

源碼如上所示,trackingInvalidateKey 方法主要做了 7 件事情:

  • 根據鍵的指針去 TrackingTable 查找客戶端ID列表;

  • 使用迭代器遍歷列表;

  • 根據 clientId 查找 client 實例;

  • 如果 client 實例未開啓 track 或者是廣播模式則跳過;

  • 如果 client 實例開啓了 NOLOOP 並且是導致key發生變化的client則跳過;

  • 調用 sendTrackingMessage 方法發送失效消息;

  • 減少數據統計,根據sdskey刪除對應的記錄

下面來看真正發送消息的 sendTrackingMessage 函數,它主要做了6件事:

  • 如果 clienttrackingredirection 不爲空,則開啓了轉發模式;

  • 找到轉發的客戶端實例;

  • 如果轉發客戶端關閉了,則必須通知原客戶端;

  • 如果是客戶端使用 RESP3 則發 PUSH 消息;

  • 如果是轉發模式,往 TrackingChannelName 也就是 _redis_:invalidate 信道中發送失效消息的頭部信息;

  • 發送鍵等信息。

> > > >

參考資料

  • https://redis.io/topics/client-side-caching

  • https://tech.meituan.com/2017/03/17/cache-about.html

  • https://cloud.tencent.com/developer/article/1005399

  • https://juejin.im/post/5b849878e51d4538c77a974a

  • https://www.kawabangga.com/posts/3590

  • https://www.slideshare.net/RedisLabs/redisconf18-techniques-for-synchronizing-inmemory-caches-with-redis

  • https://www.bilibili.com/video/BV1qe411p7v9/

作者丨歷小冰

來源丨 程序員歷小冰 (ID:gh_a1d0b50d8f0a)

dbaplus社羣歡迎廣大技術人員投稿,投稿郵箱: [email protected]

從過去40年至今,數據庫的形態基本經歷了傳統商業數據庫、開源數據庫到雲原生數據庫的演進過程。雲時代下數據庫將如何革新與創變?企業核心數據庫遷移與建設如何安全平穩展開?來 Gdevops全球敏捷運維峯會北京站 尋找答案:

  • 《All in Cloud 時代,下一代雲原生數據庫技術與趨勢》 阿里巴巴集團副總裁/達摩院首席數據庫科學家 李飛飛(飛刀)

  • 《AI和雲原生時代的數據庫進化之路》騰訊數據庫產品中心總經理 林曉斌(丁奇)

  • 《ICBC的MySQL探索之路》 工商銀行軟件開發中心 魏亞東

  • 《民生銀行在SQL審覈方面的探索和實踐》 民生銀行資深數據庫專家 李寧寧

  • 《OceanBase分佈式數據庫在西安銀行的落地和實踐》 螞蟻金服P9資深專家/ OceanBase 核心負責人 蔣志勇

  • 《金融行業MySQL高可用實踐》 愛可生技術總監 明溪源

讓我們 9月11日在北京 共同眺望數據庫發展變革更長遠的未來!

相關文章