一默色、緩存的受益與成本
1. 受益
(1)加速讀寫(xiě)
- 通過(guò)緩存加速讀寫(xiě)速度:CPU L1/L2/L3 Cache谎势、Linux Page Cache加速硬盤(pán)讀寫(xiě)、瀏覽器緩存虐沥、Ehcache緩存等
(2)降低后端負(fù)載(MySQL)
2.成本
(1)數(shù)據(jù)不一致:緩存層和數(shù)據(jù)層有時(shí)間窗口不一致熊经,和更新策略有光
(2)代碼維護(hù)成本:多了一層緩存邏輯
(3)運(yùn)維成本:例如Redis Cluster
3.場(chǎng)景
-
降低后端負(fù)載
對(duì)高消耗的SQL: join結(jié)果集 / 分組統(tǒng)計(jì)結(jié)果緩存 -
加速請(qǐng)求相應(yīng)
利用Redis / Memcache 優(yōu)化IO響應(yīng)時(shí)間 -
大量寫(xiě)合并為批量寫(xiě)
如計(jì)數(shù)器先Redis累加再批量寫(xiě)DB
二、緩存更新策略
-
LRU / LFU / FIFO 算法剔除:例如maxmemory-policy
先刪掉 - 超時(shí)剔除:例如:expire
- 主動(dòng)更新:開(kāi)發(fā)控制key的生命周期
LUR/LIRS算法剔除 | 最差 | 低 |
超時(shí)剔除 | 較差 | 低 |
主動(dòng)更新 | 高 | 高 |
兩條建議
- 低一致性:最大內(nèi)存和淘汰策略
-
高一致性:超時(shí)剔除和主動(dòng)更新結(jié)合欲险,最大內(nèi)存和淘汰策略兜底
無(wú)法保證將來(lái)某一天內(nèi)存增上去了奈搜,而你監(jiān)控又沒(méi)到位,這個(gè)時(shí)候需要一個(gè)最大內(nèi)存和查詢策略進(jìn)行兜底盯荤,保證緩存依舊可用了,直接就OOM焕盟。
三秋秤、緩存粒度控制
1. 方式
(1)緩存全部屬性:select * from table
(2)緩存部分屬性:select column1, column2... from table
2. 粒度控制的三個(gè)角度
(1)通用性:全屬性最好
(2)占用空間:部分屬性最好
(3)代碼維護(hù):表面上全屬性最好
3.思考
??真的需要緩存全量屬性嗎?有必要考慮拓展性嗎脚翘?像user表灼卢、字典表 緩存全量屬性很好,但是其他場(chǎng)景的
四来农、緩存穿透優(yōu)化
大量請(qǐng)求不命中鞋真,返回null
1. 產(chǎn)生原因
(1)業(yè)務(wù)代碼自身問(wèn)題
(2)惡意攻擊、爬蟲(chóng)
- eg:視頻網(wǎng)站回顯到HTML中的URL頁(yè)面的內(nèi)容加密沃于,防止爬蟲(chóng)涩咖。獲取到爬蟲(chóng)或攻擊程序調(diào)用url,但是參數(shù)加密它不知道怎么傳繁莹,此時(shí)就獲取不到數(shù)據(jù)檩互,即可能產(chǎn)生緩存穿透危害。
2. 如何發(fā)現(xiàn)
(1)業(yè)務(wù)的響應(yīng)時(shí)間
(2) 業(yè)務(wù)本身問(wèn)題
(3)監(jiān)控系統(tǒng)
(4) 相關(guān)指標(biāo):總調(diào)用數(shù)咨演、緩存層命中數(shù)闸昨、存儲(chǔ)層命中數(shù)
3. 解決方案
方案一:緩存空對(duì)象
示例代碼:
public String getPassThrough(Map<String,String> records,String key) {
Jedis jedis = new JedisPool().getResource();
String cacheValue = jedis.get(key);
if(null == cacheValue || ("").equals(cacheValue)) {
String storageValue = records.get(key);
if(null == storageValue || ("").equals(storageValue))
jedis.setex(key, 5, storageValue);
else
jedis.set(key, storageValue);
return storageValue;
} else {
return cacheValue;
}
}
兩個(gè)問(wèn)題:
產(chǎn)生大量的空值鍵
如果產(chǎn)生了大量的 { nullKey : null } ,那么也會(huì)對(duì)Redis的訪問(wèn)產(chǎn)生影響,所以一般設(shè)置一個(gè)過(guò)期時(shí)間薄风。緩存層和存儲(chǔ)層 “短期” 數(shù)據(jù)不一致饵较。(記得更新緩存)
方案二:布隆過(guò)濾器攔截(額外的代碼,可能引申出新問(wèn)題)
五遭赂、無(wú)底洞問(wèn)題優(yōu)化
-
問(wèn)題來(lái)源
2010年facebook有3000個(gè) Memcache節(jié)點(diǎn)循诉,增加新緩存機(jī)器,性能不升反降嵌牺。
-
問(wèn)題關(guān)鍵點(diǎn):
1)更多機(jī)器 不代表性能提升
2)批量接口需求(mget、mset)
3)數(shù)據(jù)增長(zhǎng)與水平拓展需求
-
優(yōu)化IO的集中方法
1)命令本身優(yōu)化:例如慢查詢keys募疮、hgetall bigkey
2)減少網(wǎng)絡(luò)通信次數(shù)
3)優(yōu)化SQL
4)降低客戶端接入成本炫惩,例如客戶端長(zhǎng)連接 、連接池阿浓、NIO等
-
四種優(yōu)化方法(參見(jiàn)上節(jié))
1) 串行mget
2)串行IO
3)并行IO
4)hash_tag
六他嚷、緩存雪崩優(yōu)化
七、熱點(diǎn)key重建優(yōu)化
1. 三個(gè)方案
1)減少重緩存次數(shù)
2)數(shù)據(jù)盡可能一致
3)減少潛在危險(xiǎn)
2. 兩個(gè)解決方案
1)互斥鎖(mutex key)
public String get(String key) {
String value = jedis.get(key);
if(value == null) {
String mutexKey = "mutex:key:"+key;
if(jedis.set(mutexKey,"1","ex 180","nx")) {
value = db.get(key);
jedis.set(key,value);
jedis.delete(mutexKey);
} else {
//其它線程休息50秒后重試
TimeUnit.SECONDS.sleep(50);
get(key);
}
}
return value;
}
2)永不過(guò)期
-
緩存層面:
沒(méi)有設(shè)置過(guò)期時(shí)間(沒(méi)用expire) -
功能層面:
為每個(gè)value添加
當(dāng)發(fā)現(xiàn)超過(guò)邏輯過(guò)期時(shí)間后芭毙,會(huì)使用單獨(dú)的線程去構(gòu)建緩存筋蓖。
優(yōu)點(diǎn): 相比互斥??而言,不會(huì)等待退敦,而且只有一個(gè)獨(dú)立線程去重建
問(wèn)題: 可能存在數(shù)據(jù)不一致粘咖。重建過(guò)程進(jìn)行中,拿了老的值侈百。
public String get2(final String key) {
V v = jedis.get(key);
String value = v.getValue();
long logicTimeout = v.getTimeout();
if(logicTimeout > System.currentTimeMillis()) {
String mutexKey = "mutex:key:"+key;
if(jedis.set(mutexKey,"1","ex 180","nx")) {
threadPool.execute(new Runnable() {
@Override
public void run() {
String dbValue = db.get(key);
jedis.set(key,dbValue);
jedis.delete(mutexKey);
}
});
}
}
return value;
}
3)兩種方案對(duì)比
方案 | 優(yōu)點(diǎn) | 缺點(diǎn) |
---|---|---|
互斥鎖 | - 思路簡(jiǎn)單 - 保證一致性 |
- 代碼復(fù)雜度增加 - 存在 |
永不過(guò)期 | - 基本杜絕熱點(diǎn)key問(wèn)題 | - 不保證一致性 - 邏輯過(guò)期時(shí)間增加維護(hù)成本和內(nèi)存成本 - 策略:讓logicTimeout < realTimeout瓮下, - 策略考量:為緩存重建提供寬裕的時(shí)間 |
八、本章總結(jié)
? 緩存收益:加速讀寫(xiě)钝域、降低后端存儲(chǔ)負(fù)載讽坏。
? 緩存成本:緩存和存儲(chǔ)數(shù)據(jù)不一致性、代碼維護(hù)成本例证、運(yùn)維成本路呜。
? 推薦方案:結(jié)合剔除、超時(shí)织咧、主動(dòng)更新三種方案共同完成胀葱。
? 穿透問(wèn)題:使用緩存空對(duì)象和布隆過(guò)濾器來(lái)解決,注意它們各自的使用場(chǎng)
景和局限性笙蒙。
? 無(wú)底洞問(wèn)題:分布式緩存中巡社,有更多的機(jī)器不保證有更高的性能。
有四種 批量操作方式:串行命令手趣、串行10晌该、并行1〇、hashjag绿渣。
? 雪崩問(wèn)題:緩存層高可用朝群、客戶端降級(jí)、提前演練是解決雪崩問(wèn)題的重要
方法中符。
? 熱點(diǎn)key問(wèn)題:互斥鎖姜胖、"7卞遠(yuǎn)不過(guò)期"能夠在一定程度上解決熱點(diǎn)key問(wèn)
題,開(kāi)發(fā)人員在使用時(shí)要了解它們各自的使用成本淀散。