1. Redis 數(shù)據(jù)失效導(dǎo)致的雪崩
因?yàn)?strong>緩存失效李滴,從而導(dǎo)致大量請求導(dǎo)向數(shù)據(jù)庫呢蛤。
- 大量請求黑毅,導(dǎo)致數(shù)據(jù)庫處理不過來祭衩,整個(gè)系統(tǒng)依賴數(shù)據(jù)庫的功能全部崩潰
- 單系統(tǒng)掛掉灶体,其他依賴于該系統(tǒng)的應(yīng)用也會(huì)出現(xiàn)不穩(wěn)定甚至崩潰
2. Redis數(shù)據(jù)失效的場景
- 最大內(nèi)存控制
maxmemory 最大內(nèi)存閾值
maxmemory-policy 到達(dá)閾值的執(zhí)行策略
3. 緩存雪崩解決方案
3.1 Semaphore信號量限流
-
J.U.C包重要的并發(fā)編程工具類
又稱“信號量”,控制多個(gè)線程爭搶許可汪厨。
核心方法
- acquire:獲取一個(gè)許可赃春,如果沒有就等待愉择,
- release:釋放一個(gè)許可劫乱。
典型場景∶
1织中、代碼并發(fā)處理限流;例子
package cn.lazyfennec.cache.redis.service;
import cn.lazyfennec.cache.redis.annotations.NeteaseCache;
import cn.lazyfennec.cache.redis.dao.UserDao;
import cn.lazyfennec.cache.redis.pojo.User;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cache.annotation.CacheEvict;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Service;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.Semaphore;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.ReentrantLock;
@Service // 默認(rèn) 單實(shí)例
public class UserService2 {
@Autowired
UserDao userDao;
@Autowired
RedisTemplate redisTemplate; // spring提供的一個(gè)redis客戶端,底層封裝了jedis等客戶端
// userId ---> lock 記錄每一個(gè)userId當(dāng)前的查詢情況
static Map<String, ReentrantLock> mapLock = new ConcurrentHashMap<>();
static Semaphore semaphore = new Semaphore(50); // 信號量 50 -- 類似車票
/**
* 根據(jù)ID查詢用戶信息 (redis緩存衷戈,用戶信息以json字符串格式存在(序列化))
*/
public User findUserById(String userId) throws Exception {
// 1. 先讀取緩存
Object cacheValue = redisTemplate.opsForValue().get(userId); // redisTemplate是spring提供的redis客戶端
if (cacheValue != null) {
System.out.println("###緩存命中:" + ((User) cacheValue).getUname());
return (User) cacheValue;
}
// ---------------緩存miss之后流程--------------
ReentrantLock reentrantLock = new ReentrantLock();
try {
if (mapLock.putIfAbsent(userId, reentrantLock) != null) { // 有返回值代表存在鎖
reentrantLock = mapLock.get(userId);
}
Thread.sleep(3000);// TODO 停頓3秒狭吼,等下一個(gè)線程過來,模擬多個(gè)用戶同時(shí)并發(fā)請求的場景
reentrantLock.lock(); // 爭搶鎖,搶不到的排隊(duì)---1個(gè)請求查詢數(shù)據(jù)庫 --- 599個(gè)等待
Thread.sleep(3000);// TODO 停頓3秒殖妇,模擬lock獲取之后業(yè)務(wù)處理時(shí)間
// 再次查詢緩存 -- 避免大量重復(fù)數(shù)據(jù)庫查詢
cacheValue = redisTemplate.opsForValue().get(userId); // redisTemplate是spring提供的redis客戶端
if (cacheValue != null) {
System.out.println("###緩存命中:" + ((User) cacheValue).getUname());
return (User) cacheValue;
}
semaphore.acquire(); // 獲取信號量 刁笙,沒有獲取到
// 2. 如果緩存miss,則查詢數(shù)據(jù)庫
User user = userDao.findUserById(userId);
System.out.println("***緩存miss:" + user.getUname());
// 3. 設(shè)置緩存(重建緩存) // 主播信息查詢緩存
redisTemplate.opsForValue().set(userId, user);// set key value
redisTemplate.expire(userId, 100, TimeUnit.SECONDS); // 需要手動(dòng)設(shè)
semaphore.release(); // 釋放信號量
return user;
} finally {
if (!reentrantLock.hasQueuedThreads()) { // 當(dāng)鎖最后一個(gè)釋放的時(shí)候谦趣,刪除掉
mapLock.remove(userId);
}
reentrantLock.unlock();
}
}
@CacheEvict(value = "user", key = "#user.uid") // 方法執(zhí)行結(jié)束疲吸,清除緩存
public void updateUser(User user) {
String sql = "update tb_user_base set uname = ? where uid=?";
jdbcTemplate.update(sql, new String[]{user.getUname(), user.getUid()});
}
/**
* 根據(jù)ID查詢用戶名稱
*/
// 我自己實(shí)現(xiàn)一個(gè)類似的注解
@NeteaseCache(value = "uname", key = "#userId") // 緩存
public String findUserNameById(String userId) {
// 查詢數(shù)據(jù)庫
String sql = "select uname from tb_user_base where uid=?";
String uname = jdbcTemplate.queryForObject(sql, new String[]{userId}, String.class);
return uname;
}
@Autowired
JdbcTemplate jdbcTemplate; // spring提供jdbc一個(gè)工具(mybastis類似)
}
3.2 容錯(cuò)降級
如果覺得有收獲就點(diǎn)個(gè)贊吧,更多知識前鹅,請點(diǎn)擊關(guān)注查看我的主頁信息哦~