一個(gè)良好的緩存系統(tǒng)亭敢,往往在處理一下幾個(gè)關(guān)鍵問題上有獨(dú)到之處:緩存穿透、擊穿图筹、雪崩帅刀、熱點(diǎn)让腹、大Value等
基本位置
緩存穿透
概念
查詢肯定不存在的數(shù)據(jù),緩存不命中扣溺,懟到DB查詢骇窍。
風(fēng)險(xiǎn)
流量大時(shí),可能導(dǎo)致DB掛掉
黑客利用肉雞大規(guī)模攻擊锥余,DB掛掉
思路
BloomFilter攔截
將所有可能存在的Key Hash到一個(gè)足夠大的bitmap中腹纳,不存在的Key會(huì)被bitmap攔截,從而避免懟DB驱犹。
BloomFilter
- 有誤判率嘲恍,會(huì)將不存在的誤判為存在,Guava BloomFilter默認(rèn)0.03
- 但對(duì)于存在的key雄驹,一定不會(huì)誤判為不存在
- 則發(fā)生誤判(不存在的誤判為存在)佃牛,則去查庫,這時(shí)量已降低很多
很高效管用医舆,但數(shù)據(jù)量大了也費(fèi)內(nèi)存俘侠,增加維護(hù)成本;除非要解決特定的高并發(fā)場景蔬将,不建議使用爷速。
// 1億
private final static int SIZE = 100000000;
private static BloomFilter<Integer> bf =BloomFilter.create(Funnels.integerFunnel(), SIZE);
@Test
public void testBloomFilter(){
for (int i = 0; i < SIZE; i++) {
bf.put(i);
}
long timeStart = System.nanoTime();
if (bf.mightContain(8888)) {
System.out.println("hit");
}
long timeEnd = System.nanoTime();
System.out.println(String.format(" time = %f mm" , (timeEnd - timeStart) * 1.0/1000/1000));
}
// 1千萬 1億級(jí)別判斷的時(shí)間基本沒差別
// 但是分配的內(nèi)存會(huì)幾乎線性變化
hit
time = 0.138511 mm
緩存空結(jié)果+過期(jetcache采用方式)
- Key查DB返回的數(shù)據(jù)為空,仍然緩存Key : DefaultValue
- 設(shè)置一個(gè)可容忍的過期時(shí)間
雖然過期后會(huì)重新查庫霞怀,但很大程度上能降低查庫壓力遍希,而且實(shí)現(xiàn)簡單、易維護(hù)里烦,jetcache采用的方法。
雪崩
概念
雪崩往往是由于緩存的多Key設(shè)置了相同的過期時(shí)間禁谦,某一時(shí)刻緩存多Key同時(shí)失效胁黑,所有請求全部打到DB,DB瞬時(shí)壓力過重州泊,導(dǎo)致Halt住或奔潰丧蘸。
思路
加鎖查庫(jetcache使用方式)
- 查Cache,Key Miss遥皂,先Lock
- 獲取到鎖的線程進(jìn)行查庫力喷,放入Cache,釋放鎖演训,喚醒其他線程
- 其他線程等待弟孟,直到被喚醒,再查Cache(Double check)
- 失敗样悟,則重試拂募,或返回一個(gè)默認(rèn)值(推薦)
整流隊(duì)列
- 并發(fā)流量特別大時(shí)
- 查DB前先通過整流器庭猩,可按key的總要程度劃分多個(gè)優(yōu)先級(jí)隊(duì)列,重要的獲取令牌百分比大
- 獲取到令牌的進(jìn)入消費(fèi)線程池隊(duì)列
- 多個(gè)消費(fèi)線程讀DB
- 查DB限流
- 線程數(shù)可控
- 增加開發(fā)成本
- Guava 有RateLimiter限速工具類陈症,實(shí)現(xiàn)了令牌桶算法蔼水,以一定的頻率往桶里扔令牌,線程拿到令牌才能執(zhí)行录肯。
錯(cuò)開過期時(shí)間(RD自定義)
- 在設(shè)置過期值趴腋,在基礎(chǔ)過期時(shí)間上增加一個(gè)隨機(jī)值(如1-5分鐘隨機(jī))
熱點(diǎn)擊穿
概念
- 某些時(shí)間點(diǎn),某些Key會(huì)被高并發(fā)地訪問论咏,形成熱點(diǎn)Key
- 熱點(diǎn)Key恰好在這時(shí)過期了优炬,大量并發(fā)打到DB
- 或是超高并發(fā)單Key(一般是hash到單臺(tái)機(jī)上),打滿單機(jī)的網(wǎng)卡/cpu
思路
加鎖
每個(gè)熱key的多個(gè)請求潘靖,只放過1個(gè)請求去查DB
預(yù)判超時(shí)穿剖,緩存舊值(Guava Cache的RefreshAfterWrite)
- 在value內(nèi)部設(shè)置1個(gè)超時(shí)值t1, t1比實(shí)際的超時(shí)時(shí)間t2小
- 當(dāng)讀時(shí)發(fā)現(xiàn)t1已過期,先加鎖延長t1并重新設(shè)置到cache, 同時(shí)通知查庫
- 查庫期間Cache返回舊值
- 有一個(gè)線程從數(shù)據(jù)庫加載數(shù)據(jù)并設(shè)置到cache中
- 此后Cache返回新值
預(yù)熱(12306驗(yàn)證碼)
- 將熱數(shù)據(jù)先加載到緩存系統(tǒng)
- 請求直接查詢
- 后臺(tái)線程異步同步/定時(shí)更新
- ScheduledRefresh(定時(shí)刷新 jetcache)
- 支持很高的并發(fā)(一般是OS系統(tǒng)層面限制卦溢、網(wǎng)卡帶寬限制)
- 費(fèi)內(nèi)存(熱點(diǎn)過期時(shí)間很長糊余,甚至永不過期)
多級(jí)緩存
- 熱點(diǎn)Key放在本地緩存
- 遠(yuǎn)程緩存定時(shí)從DB更新
- 遠(yuǎn)程緩存進(jìn)行主動(dòng)推送更新本緩存
- 應(yīng)用節(jié)點(diǎn)不可以也是熱點(diǎn)
熱點(diǎn)動(dòng)態(tài)散列(tair熱點(diǎn)處理方式)
- 每個(gè)HotZone都存儲(chǔ)相同的讀熱點(diǎn)數(shù)據(jù)
- 客戶端對(duì)熱點(diǎn)數(shù)據(jù)Key的請求會(huì)隨機(jī)到任意一臺(tái)DataServer的HotZone區(qū)域
- 單點(diǎn)的熱點(diǎn)請求就被散列到多個(gè)節(jié)點(diǎn)乃至整個(gè)集群。
- 采用動(dòng)態(tài)散列技術(shù)单寂,在存儲(chǔ)服務(wù)端實(shí)現(xiàn)了熱點(diǎn)的再散列
- 客戶端對(duì)熱點(diǎn)實(shí)現(xiàn)了一套單獨(dú)的邏輯