## 什么是熱點key重建
* 開發人員使用“緩存+過期時間”的策略既可以加速數據讀寫,又保證數據的定期更新,這種模式基本能夠滿足絕大部分需求。但是**有兩個問題如果同時出現,可能就會對應用造成致命的危害:**
* 當前key是一個**熱點key**(例如一個熱門的娛樂新聞),并發量非常 大
* 重建緩存**不能在短時間完成,可能是一個復雜計算**,例如復雜的SQL、多次IO、多個依賴等
* **在緩存失效的瞬間,有大量線程來重建緩存**(如下圖所示),造成 后端負載加大,甚至可能會讓應用崩潰

要解決這個問題也不是很復雜,但是不能為了解決這個問題給系統帶來更多的麻煩,**所以需要制定如下目標:**
## 三個目標
* **減少重建緩存的次數**
* **數據盡可能一致**
* **較少的潛在危險**
## 互斥鎖
* 此方法**只允許一個線程重建緩存,其他線程等待重建緩存的線程執行完**,重新從緩存獲取數據即可,整個過程如下圖所示

**下面代碼使用Redis的setnx命令實現上述功能:**
* 1)從Redis獲取數據,如果值不為空,則直接返回值;否則執行下面的2.1)和2.2)步驟
* 2.1)如果set(nx和ex)結果為true,說明此時沒有其他線程重建緩存, 那么當前線程執行緩存構建邏輯
* 2.2)如果set(nx和ex)結果為false,說明此時已經有其他線程正在執 行構建緩存的工作,那么當前線程將休息指定時間(例如這里是50毫秒,取 決于構建緩存的速度)后,重新執行函數,直到獲取到數據
```
String get(String key) {
// 從Redis中獲取數據
String value = redis.get(key);
// 如果value為空,則開始重構緩存
if (value == null) {
// 只允許一個線程重構緩存,使用nx,并設置過期時間ex
String mutexKey = "mutext:key:" + key;
if (redis.set(mutexKey, "1", "ex 180", "nx")) { //鎖
// 從數據源獲取數據
value = db.get(key);
// 回寫Redis,并設置過期時間
redis.setex(key, timeout, value);
// 刪除key_mutex
redis.delete(mutexKey);
}
// 其他線程休息50毫秒后重試
else {
Thread.sleep(50);
get(key);
}
}
return value;
}
```
## 永遠不過期
* **“永遠不過期”包含兩層意思:**
* **從緩存層面來看**,確實沒有設置過期時間,所以不會出現熱點key過期 后產生的問題,也就是“物理”不過期;
* **從功能層面來看**,為每個value設置一個邏輯過期時間,當發現超過邏 輯過期時間后,會使用單獨的線程去構建緩存;
* **整個過程如下圖所示:**

* 從實戰看,此方法有效杜絕了熱點key產生的問題,但**唯一不足的就是重構緩存期間,會出現數據不一致的情況**,這取決于應用方是否容忍這種不 一致
```
String get(final String key) {
V v = redis.get(key);
String value = v.getValue();
// 邏輯過期時間
long logicTimeout = v.getLogicTimeout();
// 如果邏輯過期時間小于當前時間,開始后臺構建
if (v.logicTimeout <= System.currentTimeMillis()) {
String mutexKey = "mutex:key:" + key;
if (redis.set(mutexKey, "1", "ex 180", "nx")) {
// 重構緩存
threadPool.execute(new Runnable() {
public void run() {
String dbValue = db.get(key);
redis.set(key, (dbvalue,newLogicTimeout));
redis.delete(mutexKey);
}
});
}
}
return value;
}
```
## 總結
* **作為一個并發量較大的應用,在使用緩存時有三個目標:**
* 第一,加快用戶訪問速度,提高用戶體驗
* 第二,降低后端負載,減少潛在的風險,保證系統平穩
* 第三,保證數據“盡可能”及時更新
* **下面將按照這三個維度對上 述兩種解決方案進行分析:**
* **互斥鎖(mutex key)**:這種方案思路比較簡單,但是存在一定的隱患,如果構建緩存過程出現問題或者時間較長,可能會存在死鎖和線程池阻塞的風險,但是這種方法能夠較好地降低后端存儲負載,并在一致性上做得比較好
* **“永遠不過期”**:這種方案由于沒有設置真正的過期時間,實際上已經 不存在熱點key產生的一系列危害,但是會存在數據不一致的情況,同時代碼復雜度會增大
* **兩種解決方法對比如下圖所示:**

- Redis簡介
- 簡介
- 典型應用場景
- Redis安裝
- 安裝
- redis可執行文件說明
- 三種啟動方法
- Redis常用配置
- API的使用和理解
- 通用命令
- 數據結構和內部編碼
- 單線程
- 數據類型
- 字符串
- 哈希
- 列表
- 集合
- 有序集合
- Redis常用功能
- 慢查詢
- Pipline
- 發布訂閱
- Bitmap
- Hyperloglog
- GEO
- 持久化機制
- 概述
- snapshotting快照方式持久化
- append only file追加方式持久化AOF
- RDB和AOF的抉擇
- 開發運維常見問題
- fork操作
- 子進程外開銷
- AOF追加阻塞
- 單機多實例部署
- Redis復制原理和優化
- 什么是主從復制
- 主從復制配置
- 全量復制和部分復制
- 故障處理
- 開發運維常見問題
- Sentinel
- 主從復制高可用
- 架構說明
- 安裝配置
- 客戶端連接
- 實現原理
- 常見開發運維問題
- 高可用讀寫分離
- 故障轉移client怎么知道新的master地址
- 總結
- Sluster
- 呼喚集群
- 數據分布
- 搭建集群
- 集群通信
- 集群擴容
- 集群縮容
- 客戶端路由
- 故障轉移
- 故障發現
- 故障恢復
- 開發運維常見問題
- 緩存設計與優化
- 緩存收益和成本
- 緩存更新策略
- 緩存粒度控制
- 緩存穿透優化
- 緩存雪崩優化
- 無底洞問題優化
- 熱點key重建優化
- 總結
- 布隆過濾器
- 引出布隆過濾器
- 布隆過濾器基本原理
- 布隆過濾器誤差率
- 本地布隆過濾器
- Redis布隆過濾器
- 分布式布隆過濾器
- 開發規范
- 內存管理
- 開發運維常見坑
- 實戰
- 對文章進行投票
- 數據庫的概念
- 啟動多實例