緩存擊穿
什麼是緩存擊穿?數據庫裏面數據存在,緩存數據因某種原因不存在,導致大量請求到數據庫獲取數據現象。這種現象有可能會導致數據庫connections數耗盡,嚴重會導致數據庫服務停止。
分佈式鎖解決方案
防止請求穿透到數據庫,可以使用分佈式鎖方式實現,例如查詢商品數據;
public Product getProductById(Long productId){
log.debug("查詢商品信息id:{}", productId);
//1、從本地緩存獲取
Product product = ehcache.get(Constants.CACHE_PRODUCT_PREFIX + productId, Product.class);
if (product != null){
return product;
}
//2、從redis緩存獲取
product = redis.get(Constants.CACHE_PRODUCT_PREFIX + productId, Product.class);
if (product != null){
return product;
}
//3、從db獲取,防止【緩存擊穿】
String uuid = UUID.randomUUID().toString();
boolean lockFlag = false;
try {
//獲取分佈式鎖
lockFlag = redis.lock(Constants.LOCK_PRODUCT_PREFIX + productId, uuid);
if (lockFlag) {
//再次查詢緩存,確保下次進入線程從緩存中獲取到
product = redis.get(Constants.CACHE_PRODUCT_PREFIX + productId, Product.class);
if (product != null) {
return product;
}
product = productService.findByProductId(productId);
if (product != null) {
redis.setex(Constants.CACHE_PRODUCT_PREFIX + productId, EXPIRE_TIME, product);
}
}
}catch (Exception e){
log.error("分佈式加鎖異常", e);
return null; //具體返回編碼時候確認
}finally {
if (lockFlag){
redis.unlock(Constants.LOCK_PRODUCT_PREFIX + productId, uuid);
}
}
return product;
}
分佈式鎖方案優化
分佈式會存在一個問題,未能獲取到分佈式鎖的請求,會返回null空數據,可以對返回null數據進行封裝,也可能採用while休眠方式等待從緩存redis中獲取數據,下面以休眠方式等待爲例,只對try部分代碼進行重構。
try {
//獲取分佈式鎖
lockFlag = redis.lock(Constants.LOCK_PRODUCT_PREFIX + productId, uuid);
if (lockFlag) {
//再次查詢緩存,確保下次進入線程從緩存中獲取到
product = redis.get(Constants.CACHE_PRODUCT_PREFIX + productId, Product.class);
if (product != null) {
return product;
}
product = productService.findByProductId(productId);
if (product != null) {
redis.setex(Constants.CACHE_PRODUCT_PREFIX + productId, EXPIRE_TIME, product);
}
}else{
long endTime = 0L;
long waitTime = 0L;
while (true) {
// 一般情況下,面向用戶的讀請求控制在200ms
if (waitTime > 200) {
break;
}
// 嘗試再去redis中讀取商品
product = redis.get(Constants.CACHE_PRODUCT_PREFIX + productId, Product.class);
if (product != null) {
return product ;
} else {
// 如果沒有讀取到結果,那麼等待一段時間
Thread.sleep(20);
endTime = System.currentTimeMillis();
waitTime = endTime - startTime;
}
}
}catch (Exception e){
log.error("分佈式加鎖異常", e);
return null; //具體返回編碼時候確認
}finally {
if (lockFlag){
redis.unlock(Constants.LOCK_PRODUCT_PREFIX + productId, uuid);
}
}