redis數據失效導致的雪崩
因爲緩存失效,從而導致大量的請求沒有命中緩存,導致請求全部打到數據庫。
1.大量請求,導致數據庫處理不過來,整個系統依賴數據庫的功能全部崩潰。
2.單系統掛掉,其它依賴於該系統的應用也會出現不穩定甚至崩潰。
redis數據失效的場景
1.因爲打到內存閥值,採用數據淘汰策略(LRU/LFU)導致數據失效。
2.數據設置了過期時間,達到過期時間後,數據失效。
3.因爲故障或者宕機/升級,導致服務重啓,數據失效。
防止redis數據失效方法
1.保證內存充足,淘汰的數據都是非熱點的數據,不會導致系統崩潰;可以採用redis集羣分片部署。
2.對於大量的過期數據,設置過期錯開,不要設置同一個過期時間,導致某一時刻,大量數據不命中。
3.做好數據備份,做好主從複製。
緩存雪崩解決方案
1.對數據庫訪問進行限流,保證不會因爲緩存雪崩導致,數據庫故障——使用信號量控制併發。
2.容錯降級——返回指定的異常碼。
代碼實例:
// 數據庫限流,根據數據庫連接大小定義
Semaphore semaphore = new Semaphore(30);
public Object queryStock(String goodsId) {
String cacheKey = "goodsStock-" + goodsId;
// 1.先從Redis裏面獲取數據
String value = mainRedisTemplate.opsForValue().get(cacheKey);
// 2.緩存裏面沒有數據,從數據庫取
if (value != null) {
logger.warn(Thread.currentThread().getName() + "緩存中獲得數據+++++++++++++++++++++++++++");
return value;
}
// 3.去請求數據庫,需要進行控制,根據連接數進行控制
try {
// 同一時間,只能有30個請求去數據庫獲取數據,並且重構緩存
boolean acquire = semaphore.tryAcquire(5, TimeUnit.SECONDS);
if (acquire) {
// 再次從Reids中獲取數據
value = mainRedisTemplate.opsForValue().get(cacheKey);
if (value != null) {
logger.warn(" 緩存中獲得數據+++++++++++++++++++++++++++");
return value;
}
value = databaseService.queryFromDatabase(goodsId);
System.err.println(" 數據庫中獲得數據==============================");
// 3.塞到緩存,過期時間
String v = value;
mainRedisTemplate.execute((RedisCallback<Boolean>) conn ->{
return conn.setEx(cacheKey.getBytes(), 120, v.getBytes());
});
} else {
// 等待時間
// 定義錯誤信息返回指定錯誤碼
}
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
// 釋放信號量
semaphore.release();
}
return value;
}
布隆過濾器——過濾不必要的查詢
對於有些場景,當訪問一個本就不存的數據時,這時我們不需要去走redis和數據庫查詢,應該直接返回一個錯誤的提示,這樣可以防止惡意訪問,導致服務器癱瘓。
布隆過濾器(Bloom Filter):是1970年布隆提出的,它實際上是一個很長的二進制數組和一系列hash函數,布隆過濾器可以用於檢索一個元素是否在一個集合中;它的優點是空間效率和查詢時間都比一般的算法要好的多,缺點是有一定的誤識別率和刪除困難。
布隆過濾器的構建過程:
1.加載符合條件的記錄
2.計算每條元素的hash值
3.計算hash值對應二進制數據的位置
4.將對應位置的值改爲1
查找元素是否存在的過程:
1.計算元素的hash值
2.計算hash值對應二進制數組的位置
3.找到數組中對應的位置的值,0代表不存在,1代表存在。
代碼實例:
@Service
// 構建一個布隆過濾器
public class RedisBloomFilter {
// redis連接信息
@Value("${spring.redis.main.hostName}")
private String redisHost;
@Value("${spring.redis.main.port}")
private int redisPort;
// bloomfilter 客戶端
private Client client;
@PostConstruct
public void init() {
// bloomfilter 客戶端
client = new Client(redisHost, redisPort);
}
/**
* 創建一個自定義的過濾器
* @param filterName
*/
public void createFilter(String filterName) {
// 創建一個容量10萬,誤判率0.01%的布隆過濾器
client.createFilter(filterName, 1000, 0.1);
}
/**
* 添加元素
* @param filterName
* @param value
* @return
*/
public boolean addElement(String filterName, String value) {
return client.add(filterName, value);
}
/**
* 判斷元素是否存在
* @param filterName
* @param value
* @return
*/
public boolean exists(String filterName, String value) {
return client.exists(filterName, value);
}
}
public Object queryStock(final String goodsId) {
String cacheKey = "goodsStock-"+ goodsId;
// 增加一個布隆過濾器的篩選
boolean exists = filter.exists("goodsBloomFilter", cacheKey);
if(!exists) {
logger.warn(Thread.currentThread().getName()+" 您需要的商品不存在+++++++++++++++++++++++++++");
return "您需要的商品不存在";
}
public Object queryStock(String goodsId) {
String cacheKey = "goodsStock-" + goodsId;
// 1.先從Redis裏面獲取數據
String value = mainRedisTemplate.opsForValue().get(cacheKey);
// 2.緩存裏面沒有數據,從數據庫取
if (value != null) {
logger.warn(Thread.currentThread().getName() + "緩存中獲得數據+++++++++++++++++++++++++++");
return value;
}
// 3.去請求數據庫,需要進行控制,根據連接數進行控制
try {
// 同一時間,只能有30個請求去數據庫獲取數據,並且重構緩存
boolean acquire = semaphore.tryAcquire(5, TimeUnit.SECONDS);
if (acquire) {
// 再次從Reids中獲取數據
value = mainRedisTemplate.opsForValue().get(cacheKey);
if (value != null) {
logger.warn(" 緩存中獲得數據+++++++++++++++++++++++++++");
return value;
}
value = databaseService.queryFromDatabase(goodsId);
System.err.println(" 數據庫中獲得數據==============================");
// 3.塞到緩存,過期時間
String v = value;
mainRedisTemplate.execute((RedisCallback<Boolean>) conn ->{
return conn.setEx(cacheKey.getBytes(), 120, v.getBytes());
});
} else {
// 等待時間
// 定義錯誤信息返回指定錯誤碼
}
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
// 釋放信號量
semaphore.release();
}
return value;
}