1、緩存處理流程
接收到查詢數(shù)據(jù)請(qǐng)求時(shí)阳柔,優(yōu)先從緩存中查詢焰枢,若緩存中有數(shù)據(jù),則直接返回,若緩存中查不到則從DB中查詢济锄,將查詢的結(jié)果更新到緩存中暑椰,并返回查詢結(jié)果,若DB中查不到拟淮,則返回空數(shù)據(jù)
2干茉、緩存穿透
當(dāng)緩存與數(shù)據(jù)庫(kù)中都不存在該數(shù)據(jù)時(shí),由于當(dāng)數(shù)據(jù)庫(kù)查詢不到數(shù)據(jù)就不會(huì)寫入緩存很泊,這個(gè)時(shí)候如果用戶不斷的惡意發(fā)起請(qǐng)求角虫,就會(huì)導(dǎo)致這個(gè)不存在的數(shù)據(jù)每次請(qǐng)求都會(huì)查詢DB,請(qǐng)求量大的情況下委造,就會(huì)導(dǎo)致DB壓力過(guò)大戳鹅,直接掛掉。
解決方案:
1昏兆、當(dāng)查詢返回一個(gè)空數(shù)據(jù)時(shí)枫虏,直接將這個(gè)空數(shù)據(jù)存到緩存中,過(guò)期時(shí)間不宜設(shè)置過(guò)長(zhǎng)爬虱,建議不超過(guò)5分鐘
2隶债、采用布隆過(guò)濾器:將所有可能存在數(shù)據(jù),分別通過(guò)多個(gè)哈希函數(shù)生成多個(gè)哈希值跑筝,然后將這些哈希值存到一個(gè)足夠大的bitmap中死讹,此時(shí)一個(gè)一定不存在的數(shù)據(jù)就會(huì)被這個(gè)bitmap攔截,從而減少了數(shù)據(jù)庫(kù)的查詢壓力曲梗。
參考鏈接:http://www.reibang.com/p/2104d11ee0a2
3赞警、緩存擊穿
某一個(gè)數(shù)據(jù)緩存中沒(méi)有但數(shù)據(jù)庫(kù)中有的數(shù)據(jù)(一般是緩存時(shí)間到期),這時(shí)由于并發(fā)用戶特別多虏两,同時(shí)讀緩存沒(méi)讀到數(shù)據(jù)愧旦,又同時(shí)去數(shù)據(jù)庫(kù)去取數(shù)據(jù),引起數(shù)據(jù)庫(kù)壓力瞬間增大定罢,嚴(yán)重情況下會(huì)直接掛掉笤虫。
解決方案:
1、添加互斥鎖:
- ReentrantLock公平鎖
- 根據(jù)key值加鎖引颈,這樣線程之間會(huì)不影響耕皮,不會(huì)因?yàn)槟骋粋€(gè)線程獲取了鎖,其它線程就處于等待時(shí)間蝙场,也就是線程A從數(shù)據(jù)庫(kù)取key1的數(shù)據(jù)并不妨礙線程B取key2的數(shù)據(jù)
2凌停、設(shè)置熱點(diǎn)數(shù)據(jù)永不過(guò)期(物理上的不過(guò)期、“邏輯上”的不過(guò)期(緩存到期動(dòng)態(tài)構(gòu)建緩存))
簡(jiǎn)單的互斥鎖例子:
@Autowired
private StringRedisTemplate stringRedisTemplate;
@Autowired
private Jedis jedis;
private final String MUTEX_KEY = "MUTEX_";
public String getData(String key) throws InterruptedException {
String value = stringRedisTemplate.opsForValue().get(key);
//緩存失效
if (StringUtils.isBlank(value)) {
//設(shè)置分布式鎖售滤,只允許一個(gè)線程去查詢DB罚拟,同時(shí)指定過(guò)期時(shí)間為1min台诗,防止del操作失敗,導(dǎo)致死鎖赐俗,緩存過(guò)期無(wú)法加載DB數(shù)據(jù)
if (tryLock(MUTEX_KEY + key, 60L)) {
//從數(shù)據(jù)庫(kù)查詢數(shù)據(jù),將查詢的結(jié)果緩存起來(lái)
value = getValueFromDB();
stringRedisTemplate.opsForValue().set(key, value);
//釋放分布式鎖
stringRedisTemplate.delete(MUTEX_KEY + key);
} else {
//當(dāng)鎖被占用時(shí)拉队,睡眠5s繼續(xù)調(diào)用獲取數(shù)據(jù)請(qǐng)求
Thread.sleep(5000);
getData(key);}
}
return value;
}
/**
* redis實(shí)現(xiàn)分布式事務(wù)鎖 嘗試獲取鎖
*
* @param lockName 鎖
* @param expireTime 過(guò)期時(shí)間
* @return
*/
public Boolean tryLock(String lockName, long expireTime) {
//RedisCallback redis事務(wù)管理,將redis操作命令放到事務(wù)中處理阻逮,保證執(zhí)行的原子性
String result = stringRedisTemplate.opsForValue().getOperations().execute(new RedisCallback<String>() {
/**
* @param key 使用key來(lái)當(dāng)鎖粱快,因?yàn)閗ey是唯一的。
* @param value 請(qǐng)求標(biāo)識(shí)叔扼,可通過(guò)UUID.randomUUID().toString()生成,解鎖時(shí)通value參數(shù)可識(shí)別出是哪個(gè)請(qǐng)求添加的鎖
* @param nx 表示SET IF NOT EXIST事哭,即當(dāng)key不存在時(shí),我們進(jìn)行set操作瓜富;若key已經(jīng)存在鳍咱,則不做任何操作
* @param ex 表示過(guò)期時(shí)間的單位是秒
* @param time 表示過(guò)期時(shí)間
*/
@Override
public String doInRedis(RedisConnection connection) throws DataAccessException {
return jedis.set(lockName, UUID.randomUUID().toString(), "NX", "EX", expireTime);
}
});
if ("OK".equals(result)) {
return true;
}
return false;
}
public String getValueFromDB() {
return "";
}
3、緩存雪崩
緩存中大批量的數(shù)據(jù)都到了過(guò)期時(shí)間与柑,從而導(dǎo)致查詢數(shù)據(jù)量巨大谤辜,引起數(shù)據(jù)庫(kù)壓力過(guò)大甚至down機(jī)。和緩存擊穿不同价捧,緩存擊穿是指某一條數(shù)據(jù)到了過(guò)期時(shí)間丑念,大量的并發(fā)請(qǐng)求都來(lái)查詢這一條數(shù)據(jù),緩存雪崩是不同數(shù)據(jù)都過(guò)期了结蟋,很多數(shù)據(jù)都查不到從而查數(shù)據(jù)庫(kù)
解決方案:
1渠欺、設(shè)置熱點(diǎn)數(shù)據(jù)永不過(guò)期
2、緩存數(shù)據(jù)的過(guò)期時(shí)間設(shè)置隨機(jī)椎眯,可以在原有的過(guò)期時(shí)間上加上一個(gè)隨機(jī)值,比如1-3min胳岂,防止同一時(shí)間大量緩存數(shù)據(jù)集體失效编整,導(dǎo)致數(shù)據(jù)庫(kù)壓力過(guò)大。
3乳丰、如果是分布式部署緩存數(shù)據(jù)庫(kù)掌测,可將熱點(diǎn)數(shù)據(jù)分別存放到不同的緩存數(shù)據(jù)庫(kù)中,避免某一點(diǎn)由于壓力過(guò)大而down掉产园。