5.1 緩存擊穿
緩存擊穿是指緩存中沒有但數據庫中有的數據(一般是緩存時間到期),這時由於 併發用戶特別多,同時讀緩存沒讀到數據,又同時去數據庫去取數據,引起數據庫壓力 瞬間增大,造成過大壓力。
有些數據是錯誤數據沒有必要查庫的數據,例如,有些瞎搞的人輸入錯誤的訂單號,這種情況下我們可以採用布隆過濾來做驗證,防止擊穿,去查庫。
guava工具包中提供了布隆過濾的方法。
布隆過濾技術主要是利用hash去做映射,具體的細節,就不在這細說了。
接下來看一下簡單的代碼應用:
@PostConstruct //對象創建後,自動調用本方法
public void init(){//在bean初始化完成後,實例化bloomFilter,並加載數據
List<Provinces> provinces = this.list();
//當成一個SET----- 佔內存,比hashset佔得小很多
bf = BloomFilter.create(Funnels.stringFunnel(Charsets.UTF_8), provinces.size());// 32個
for (Provinces p : provinces) {
bf.put(p.getProvinceid());
}
}
@Cacheable(value = "province")
public Provinces detail(String provinceid) {
//先判斷布隆過濾器中是否存在該值,值存在才允許訪問緩存和數據庫
if(!bf.mightContain(provinceid)){
System.out.println("非法訪問--------"+System.currentTimeMillis());
return null;
}
System.out.println("數據庫中得到數據--------"+System.currentTimeMillis());
Provinces provinces = super.detail(provinceid);
return provinces;
}
5.2 緩存雪崩
緩存雪崩是指緩存中數據大批量到過期時間,而查詢數據量巨大,引起數據庫壓 力過大甚至 down 機。和緩存擊穿不同的是,緩存擊穿指併發查同一條數據,緩存雪崩 是不同數據都過期了,很多數據都查不到從而查數據庫。
可以簡單的理解爲:大量的擊穿造成雪崩。
解決方案:
對操作數據庫枷鎖,只讓一個線程去查庫,查到數據後返回更新到緩存redis中,然後其他人就可以從緩存中獲取,無需再去查庫。
此時需要對數據庫的查詢操作,加鎖 ---- lock (因考慮到是對同一個參數數值上 一把鎖,此處 synchronized 機制無法使用) 加鎖的標準流程代碼如下(一樣解決擊穿的問題):
/**
* 緩存雪崩
*/
//@Service("provincesService")
public class ProvincesServiceImpl3 extends ProvincesServiceImpl implements ProvincesService{
private static final Logger logger = LoggerFactory.getLogger(ProvincesServiceImpl3.class);
@Resource
private CacheManager cm;
private ConcurrentHashMap<String, Lock> locks = new ConcurrentHashMap<>();//線程安全的
private static final String CACHE_NAME = "province";
public Provinces detail(String provinceid) {
// 1.從緩存中取數據
Cache.ValueWrapper valueWrapper = cm.getCache(CACHE_NAME).get(provinceid);
if (valueWrapper != null) {
logger.info("緩存中得到數據");
return (Provinces) (valueWrapper.get());
}
//2.加鎖排隊,阻塞式鎖---100個線程走到這裏---同一個sql的取同一把鎖
doLock(provinceid);//32個省,最多隻有32把鎖,1000個線程
try{//第二個線程進來了
// 一次只有一個線程
//雙重校驗,不加也沒關係,無非是多刷幾次庫
valueWrapper = cm.getCache(CACHE_NAME).get(provinceid);//第二個線程,能從緩存裏拿到值?
if (valueWrapper != null) {
logger.info("緩存中得到數據");
return (Provinces) (valueWrapper.get());//第二個線程,這裏返回
}
Provinces provinces = super.detail(provinceid);
// 3.從數據庫查詢的結果不爲空,則把數據放入緩存中,方便下次查詢
if (null != provinces){
cm.getCache(CACHE_NAME).put(provinceid, provinces);
}
return provinces;
}catch(Exception e){
return null;
}finally{
//4.解鎖
releaseLock(provinceid);
}
}
private void releaseLock(String userCode) {
ReentrantLock oldLock = (ReentrantLock) locks.get(userCode);
if(oldLock !=null && oldLock.isHeldByCurrentThread()){
oldLock.unlock();
}
}
private void doLock(String lockcode) {//給一個搜索條件,對應一個鎖
//provinceid有不同的值,參數多樣化
//provinceid相同的,加一個鎖,---- 不是同一個key,不能用同一個鎖
ReentrantLock newLock = new ReentrantLock();//創建一個鎖
Lock oldLock = locks.putIfAbsent(lockcode, newLock);//若已存在,則newLock直接丟棄
if(oldLock == null){
newLock.lock();
}else{
oldLock.lock();
}
}
}