緩存的受益與成本 1.受益 加速讀寫 CPU L1/L2/L3 Cache、瀏覽器緩存、Ehcache緩存資料庫結果 降低後端負載 後端伺服器通過前端緩存降低負載:業務端使用Redis降低後端MySQL的負載 加速讀寫 CPU L1/L2/L3 Cache、瀏覽器緩存、Ehcache緩存資料庫結果 ...
1.受益
-
加速讀寫
-
CPU L1/L2/L3 Cache、瀏覽器緩存、Ehcache緩存資料庫結果
-
-
降低後端負載
-
後端伺服器通過前端緩存降低負載:業務端使用Redis降低後端MySQL的負載
-
2.成本
-
數據不一致:緩存層和數據層有時間視窗不一致問題,和更新策略有關
-
代碼維護成本:多了一層緩存邏輯
-
3.使用場景
-
降低後端負載
-
對高消耗的SQL:join結果集/分組統計結果緩存
-
-
加速請求響應
-
利用Redis/Memcache優化IO響應時間
-
-
大量寫合併為批量寫
-
入計數器先Redis累加再批量寫DB
-
緩存的更新策略
###1.LRU等演算法剔除:例如 maxmemory-policy
淘汰策略 | 含義 |
---|---|
noeviction | 當記憶體使用達到閾值的時候,所有引起申請記憶體的命令會報錯 |
allkeys-lru | 在主鍵空間中,優先移除最近未使用的key |
volatile-lru | 在設置了過期時間的鍵空間中,優先移除最近未使用的key |
allkeys-random | 最主鍵空間中,隨機移除某個key |
volatile-random | 在設置了過期的鍵空間中,隨機移除某個key |
volatile-ttl | 在設置了過期時間的鍵空間中,具有更早過期時間的key優先移除 |
2.超時剔除:例如expire
###3.主動更新:開發控制生命周期
4.兩條建議
-
低一致性數據:最大記憶體和淘汰策略
-
高一致性:超時剔除和主動更新結合,最大記憶體和淘汰策略兜底
緩存粒度問題
-
通用性:全量屬性更好
-
占用空間:部分屬性更好
-
代碼維護:錶面上全量屬性更好
緩存穿透優化
-
含義:查詢一個不存在的數據,由於緩存是不命中時需要從資料庫查詢,查不到數據則不寫入緩存,這將導致這個不存在的數據每次請求都要到資料庫去查詢
-
產生原因
-
業務代碼自身問題
-
惡意攻擊、爬蟲
-
-
發現問題
-
業務的響應時間,受到惡意攻擊時,普遍請求被打到存儲層,必會引起響應時間提高,可通過監控發現。
-
業務本身問題
-
相關指標:總調用數、緩存層命中數、存儲層命中數
-
###解決方法1:緩存空對象(設置過期時間)
-
含義:當存儲層查詢不到數據後,往cache層中存儲一個null,後期再被查詢時,可以通過cache返回null。
-
缺點
-
cache層需要存儲更多的key
-
緩存層和數據層數據“短期”不一致
-
-
示例代碼
public String getPassThrough(String key) {
String cacheValue = cache.get(key);
if( StringUtils.isEmpty(cacheValue) ) {
String storageValue = storage.get(key);
cache.set(key , storageValue);
if( StringUtils.isEmpty(storageValue) ) {
cache.expire(key , 60 * 5 );
}
return storageValue;
} else {
return cacheValue;
}
}
###解決方法2:布隆過濾器攔截(適合固定的數據)
-
將所有可能存在的數據哈希到一個足夠大的bitmap中,一個不存在的數據會被這個bitmap攔截掉,從而避免了對底層存儲系統的查詢壓力
緩存雪崩優化
-
含義:由於cache伺服器承載大量的請求,當cache服務異常離線,流量直接壓向後端組件,造成級聯故障。或者緩存集中在一段時間內失效,發生大量的緩存穿透
解決方法1:保證緩存高可用性
-
做到緩存多節點、多機器、甚至多機房。
-
Redis Cluster、Redis Sentinel
-
做二級緩存
解決方法2:依賴隔離組件為後端限流
-
使用Hystrix做服務降級
解決方法3:提前演練(壓力測試)
解決方法4:對不同的key隨機設置過期時間
無底洞問題
-
問題描述:添加機器時,客戶端的性能不但沒提升,反而下降
-
問題關鍵點
-
更多的機器 != 更高的性能
-
更多的機器 = 數據增長與水平擴展
-
批量介面需求:一次mget隨著機器增多,網路節點訪問次數更多。網路節點的時間複雜度由O(1) -> O(node)
-
-
優化IO的方法
-
命令本身優化:減少慢查詢命令:keys、hgetall、查詢bigKey併進行優化
-
減少網路通信次數
-
mget由O(keys),升級為O(node),O(max_slow(node)) , 甚至是O(1)
-
-
降低接入成本:例如客戶端長連接/連接池、NIO等
-
熱點Key的重建優化
-
熱點Key(訪問量比較大) + 較長的重建時間(重建過程中的API或者介面比較費時間)
-
導致的問題:有大量的線程會去查詢數據源並重建緩存,對存儲層造成了巨大的壓力,響應時間會變得很慢
1.三個目標
-
減少重建緩存的次數
-
數據儘可能一致
-
減少潛在危險:例如死鎖、線程池大量被hang住(懸掛)
2.兩種解決方案
-
互斥鎖(分散式鎖)
-
第一個線程需要重建時候,對這個Key的重建加入分散式鎖,重建完成後進行解鎖
-
這個方法避免了大量的緩存重建與存儲層的壓力,但是還是會有大量線程的阻塞
jedis.set(lockKey, requestId, SET_IF_NOT_EXIST, SET_WITH_EXPIRE_TIME, expireTime)
String get(String key) {
String SET_IF_NOT_EXIST = "NX";
String SET_WITH_EXPIRE_TIME = "PX";
String value = jedis.get(key);
if( null == value ) {
String lockKey = "lockKey:" + key;
if( "OK".equals(jedis.set(lockKey , "1" , SET_IF_NOT_EXIST ,
SET_WITH_EXPIRE_TIME , 180)) ) {
value = db.get(key);
jedis.set(key , value);
jedis.delete(lockKey);
} else {
Thread.sleep(50);
get(key);
}
}
return value;
} -
-
永遠不過期
-
緩存層面:不設置過期時間(不使用expire)
-
功能層面:為每個value添加邏輯過期時間,單發現超過邏輯過期時間後,會使用單獨的線程去重建緩存。
-
還存在一個數據不一致的情況。可以將邏輯過期時間相對實際過期時間相對減小
-
1.受益
-
加速讀寫
-
CPU L1/L2/L3 Cache、瀏覽器緩存、Ehcache緩存資料庫結果
-
-