目錄
- **如何解決 Redis 的熱 Key(Hot Key)問題?**
- **解決方案**
- **1. 使用多級緩存**
- **方案**
- **2. 進行 Key 預分片(Key Sharding)**
- **方案**
- **3. 使用 Redis 復制機制(主從復制或集群)**
- **方案**
- **4. 采用批量 Key 輪換(Consistent Hash)**
- **方案**
- **5. 采用異步更新策略**
- **方案**
- **6. 限流和降級**
- **方案**
- **7. 結合 MQ 做異步削峰**
- **方案**
- **總結**
- **面試標準回答**
如何解決 Redis 的熱 Key(Hot Key)問題?
熱 Key(Hot Key)是指訪問頻率極高的鍵,在高并發場景下可能會造成:
- 單個 Redis 節點壓力過大(大量請求命中一個 Key)。
- CPU 過載,響應變慢(甚至影響整個 Redis 集群)。
- 緩存失效后大量請求直接打到數據庫,導致數據庫崩潰(緩存擊穿)。
解決方案
針對不同場景,解決方案主要包括 “分散請求” 和 “降低 Redis 負載” 兩個方向。
1. 使用多級緩存
核心思路:在 Redis 之前增加一級緩存,減少 Redis 訪問壓力。
方案
-
本地緩存(L1 Cache)
- 在應用服務器上存儲熱點數據(如 Guava Cache, Caffeine)。
- 優點:訪問速度更快,避免 Redis 過載。
- 缺點:如果多個應用服務器同時緩存相同 Key,可能會有數據一致性問題。
-
CDN 緩存(L0 Cache)
- 適用于靜態資源或熱點數據(如商品詳情頁)。
- 優點:減少數據庫、Redis 訪問壓力。
示例:使用 Guava 本地緩存
LoadingCache<String, String> localCache = CacheBuilder.newBuilder().maximumSize(1000).expireAfterWrite(10, TimeUnit.MINUTES).build(new CacheLoader<String, String>() {public String load(String key) throws Exception {return redis.get(key); // 從 Redis 取數據}});// 讀取緩存
String value = localCache.get("hot_key");
2. 進行 Key 預分片(Key Sharding)
核心思路:將單個熱 Key 拆分成多個 Key,讓不同的 Redis 節點存儲不同的副本,從而分散壓力。
方案
- 存儲時:寫入多個不同的 Key,例如
hot_key_1, hot_key_2, hot_key_3
。 - 查詢時:隨機訪問
hot_key_n
,或使用一致性 Hash 計算 Key。
示例
// 寫入時分片
for (int i = 0; i < 3; i++) {redis.set("hot_key_" + i, value);
}// 讀取時隨機選一個
String key = "hot_key_" + (rand() % 3);
String value = redis.get(key);
適用場景
- 高并發計數(如熱點直播間點贊)。
- 高 QPS 的熱點數據(如秒殺商品庫存)。
3. 使用 Redis 復制機制(主從復制或集群)
核心思路:通過 Redis 讀寫分離,多個節點分擔讀取壓力。
方案
- 主節點(Master)處理寫請求,多個從節點(Slave)處理讀請求。
- 結合 客戶端負載均衡,將
get
請求分發到不同的從節點。
示例(配置 Redis 讀從庫)
slaveof master_ip master_port
適用場景
- 適用于 Redis 集群模式,大規模熱 Key 分布式緩存場景。
4. 采用批量 Key 輪換(Consistent Hash)
核心思路:通過一致性哈希(Consistent Hashing)降低熱 Key 訪問壓力。
方案
- 將一個 Key 拆分成多個時間窗口 Key。
- 訪問時隨機選擇一個 Key,確保熱點數據均勻分布。
- 定期清理過時的 Key。
示例
String key = "hot_key:" + (time(nullptr) % 5); // 輪換 Key
redis.set(key, value);
適用場景
- 防止緩存擊穿(熱點數據定期輪換)。
- 適用于短周期熱點數據(如秒殺、短時間訪問高峰)。
5. 采用異步更新策略
核心思路:緩存失效后,先返回舊值,同時異步更新緩存,避免大量請求瞬間打到數據庫。
方案
- 采用 雙緩存(Double Cache) 機制:
- 用戶查詢時返回舊緩存。
- 后臺異步更新新數據。
示例
String value = redis.get("hot_key");
if (value == null) {value = localCache.get("hot_key"); // 先從本地緩存讀取asyncUpdateRedis(); // 后臺線程更新 Redis
}
return value;
適用場景
- 避免緩存擊穿問題(如商品價格、秒殺庫存)。
6. 限流和降級
核心思路:如果 Redis 無法支撐高并發請求,可以限制請求頻率,或者直接返回默認值。
方案
-
限流(使用令牌桶 / 滑動窗口)
- 限制相同 Key 的訪問頻率。
- 避免短時間內 Redis 負載過高。
-
降級(請求超時時返回默認值)
- 如果 Redis 繁忙,則返回本地默認值,減少 Redis 壓力。
示例(限流)
local key = KEYS[1]
local limit = tonumber(ARGV[1])
local current = redis.call('incr', key)if current == 1 thenredis.call('expire', key, 60) -- 60s 過期
endif current > limit thenreturn 0 -- 限流失敗
end
return 1
適用場景
- API 訪問頻率控制(如搶購、直播點贊)。
7. 結合 MQ 做異步削峰
核心思路:將高并發請求寫入消息隊列(如 Kafka / RabbitMQ),異步處理,降低 Redis 訪問壓力。
方案
- 將請求寫入 Kafka,批量處理。
- 后端定期刷新緩存,避免 Redis 承擔高并發壓力。
示例
// 生產者:將查詢請求寫入 MQ
kafkaProducer.send("hotKeyTopic", "hot_key");// 消費者:異步更新緩存
kafkaConsumer.onMessage(msg -> {String value = queryDatabase("hot_key");redis.set("hot_key", value);
});
適用場景
- 適用于秒殺、短時熱點數據(如搶購、熱點新聞)。
總結
方法 | 核心思路 | 適用場景 |
---|---|---|
多級緩存 | L1(本地緩存)+ L2(Redis 緩存) | 低延遲讀取,熱點數據 |
Key 預分片 | 拆分熱 Key,分散訪問壓力 | 高并發計數(直播點贊、熱點商品) |
主從復制 | 讀寫分離,提高讀性能 | Redis 集群,讀多寫少 |
輪換 Key | 使用 Hash 輪轉 Key | 秒殺庫存、短時間熱點數據 |
異步更新 | 先返回舊緩存,后臺更新 | 價格、秒殺商品庫存 |
限流和降級 | 限制訪問頻率,防止 Redis 過載 | 高 QPS 接口,秒殺搶購 |
MQ 削峰 | 通過 Kafka / RabbitMQ 處理請求 | 高并發訂單、熱點數據 |
面試標準回答
解決 Redis 熱 Key 主要有 3 類方法:
- 減少 Redis 訪問壓力(本地緩存、CDN、讀寫分離)。
- 分散 Key 訪問(Key 預分片、輪換 Key)。
- 限制并發(限流、降級、MQ 削峰)。
最推薦的方案是:本地緩存 + Key 預分片 + Redis 讀寫分離,結合業務需求選擇最優方案!🚀