文章目錄
- 緩存全景圖
- Pre
- 問題描述
- 解決思路
- 一、管道(Pipelining)替代多線程
- 二、使用 Hash Tag 保證數據同槽
- 三、用 Hash 結構一次性批量取值
- 四、把數據直接存進 ZSET(或用 RedisJSON)
- 小結
緩存全景圖
Pre
分布式緩存:緩存設計三大核心思想
分布式緩存:緩存的三種讀寫模式及分類
分布式緩存:緩存架構設計的“四步走”方法
分布式緩存:緩存設計中的 7 大經典問題_緩存失效、緩存穿透、緩存雪崩
分布式緩存:緩存設計中的 7 大經典問題_數據不一致與數據并發競爭
分布式緩存:緩存設計中的 7 大經典問題_Hot Key和Big Key
問題描述
使用 Redis Cluster 時,經常會碰到 ZSET、MGET
跨槽(cross‐slot)或多線程大量并發 GET
導致的延遲和 CPU 問題, 下面說一下解決思路
解決思路
一、管道(Pipelining)替代多線程
-
問題:用 20 個線程并發
GET
,線程切換和上下文開銷很大,而且大量線程會競爭 Tomcat 線程池資源。 -
建議:在單個線程里,對每個分片的若干 key 使用 Redis Pipeline。Pipeline 可以把多條命令打包,一次性發給同一個 Redis 節點,極大減少網絡往返(RTT)和線程切換開銷。
- 拿到分頁用的 ID 列表后,根據 ID 分別算出它們在 Cluster 中各自的 slot。
- 按 slot 分組,把屬于同一 slot 的 ID 列表用一次 pipeline 批量
GET
。 - 不同 slot 的分組各自 pipeline,最終合并結果即可。
-
效果:比單條
GET
快,且不必啟 20 個線程,CPU 和線程池壓力都會大幅下降。
二、使用 Hash Tag 保證數據同槽
Redis Cluster 要求一次多鍵操作(如 MGET、Lua 腳本)只能作用于同一個 slot 才不會報錯。
-
做法:給你的 ZSET key 和各個 ID key 都加上相同的Hash Tag,例如:
ZSET key: "user:123:{timeline}" ID key: "user:123:{timeline}:item:456""user:123:{timeline}:item:789"
這樣 Redis 會只對
{timeline}
這部分計算哈希槽,保證所有相關 key 都在同一個槽里。 -
好處:
- 就可以直接用一條
MGET user:123:{timeline}:item:456 user:123:{timeline}:item:789 …
,或者一段 Lua 腳本EVAL
,一次性拉取多條數據。 - 避免了跨槽錯誤,也不需要分組 pipeline,代碼更簡單、性能更優。
- 就可以直接用一條
三、用 Hash 結構一次性批量取值
如果每條“內容”可以看成一個小對象(多個字段),還可以:
-
把所有 ID 對應的內容都存到一個 Hash,Hash 名稱同樣帶上 Hash Tag:
HSET user:123:{timeline}:items 456 "{…json…}" 789 "{…json…}"
-
查詢時,
HKEYS → 拿到所有 field(ID) HGETALL 或 HMGET → 一次性批量獲取分頁這 20 條 content
- 優點:單個 key、單條命令就能拿到所有需要的 20 條數據,Redis 端沒有多次網絡往返。
四、把數據直接存進 ZSET(或用 RedisJSON)
如果對象比較小,也可考慮:
- Option A:把內容序列化后直接當 ZSET 的 member 存儲,Score 用時間戳或自增序號。
ZRANGE … WITHSCORES
一次性既拿 ID(member)又拿內容。 - Option B:使用 RedisJSON 模塊,把每條對象存為 JSON,Key 同樣加 Hash Tag,ZSET 只存 ID,然后用
JSON.MGET
(同槽下可跨多個 JSON key 一次取)來批量拉取。
小結
- Pipeline:最簡單,不改數據模型,按 slot 分組即可。
- Hash Tag:相同槽內可直接 MGET 或 Lua,一條命令搞定。
- Hash 結構:把多條對象聚到一個 key,HMGET 一次性取完。
- 直接存 ZSET 或 JSON 模塊:簡化訪問路徑,避免額外的 GET。
以上方案可以各取所長,或組合使用:比如既用 Hash Tag,又用 Pipeline;或直接把熱點數據存在 ZSET member 里。