今天給大家介紹一下如何在SpringBoot中解決Redis的緩存穿透、緩存擊穿、緩存雪崩的問題。
緩存穿透
什么是緩存穿透
緩存穿透指的是一個(gè)緩存系統(tǒng)無(wú)法緩存某個(gè)查詢的數(shù)據(jù),從而導(dǎo)致這個(gè)查詢每一次都要訪問數(shù)據(jù)庫(kù)。
常見的Redis緩存穿透場(chǎng)景包括:
- 查詢一個(gè)不存在的數(shù)據(jù):攻擊者可能會(huì)發(fā)送一些無(wú)效的查詢來(lái)觸發(fā)緩存穿透。
- 查詢一些非常熱門的數(shù)據(jù):如果一個(gè)數(shù)據(jù)被訪問的非常頻繁,那么可能會(huì)導(dǎo)致緩存系統(tǒng)無(wú)法處理這些請(qǐng)求,從而造成緩存穿透。
- 查詢一些異常數(shù)據(jù):這種情況通常發(fā)生在數(shù)據(jù)服務(wù)出現(xiàn)故障或異常時(shí),從而造成緩存系統(tǒng)無(wú)法訪問相關(guān)數(shù)據(jù),從而導(dǎo)致緩存穿透。
如何解決
我們可以使用Guava在內(nèi)存中維護(hù)一個(gè)布隆過濾器。具體步驟如下:
- 添加Guava和Redis依賴:
<dependency>
<groupId>com.google.guava<span class="hljs-name"groupId>
<artifactId>guava<span class="hljs-name"artifactId>
<version>29.0-jre<span class="hljs-name"version>
<span class="hljs-name"dependency>
<dependency>
<groupId>org.springframework.boot<span class="hljs-name"groupId>
<artifactId>spring-boot-starter-data-redis<span class="hljs-name"artifactId>
<span class="hljs-name"dependency>
- 創(chuàng)建一個(gè)BloomFilterUtil類,用于在緩存中維護(hù)Bloom Filter。
public class BloomFilterUtil {
// 布隆過濾器的預(yù)計(jì)容量
private static final int expectedInsertions = 1000000;
// 布隆過濾器誤判率
private static final double fpp = 0.001;
private static BloomFilter<String> bloomFilter = BloomFilter.create(Funnels.stringFunnel(Charset.defaultCharset()), expectedInsertions, fpp);
/**
* 向Bloom Filter中添加元素
*/
public static void add(String key){
bloomFilter.put(key);
}
/**
* 判斷元素是否存在于Bloom Filter中
*/
public static boolean mightContain(String key){
return bloomFilter.mightContain(key);
}
}
- 在Controller中查詢數(shù)據(jù)時(shí),先根據(jù)請(qǐng)求參數(shù)進(jìn)行Bloom Filter的過濾
@Autowired
private RedisTemplate
緩存擊穿
什么是緩存擊穿
緩存擊穿指的是在一些高并發(fā)訪問下,一個(gè)熱點(diǎn)數(shù)據(jù)從緩存中不存在,每次請(qǐng)求都要直接查詢數(shù)據(jù)庫(kù),從而導(dǎo)致數(shù)據(jù)庫(kù)壓力過大,并且系統(tǒng)性能下降的現(xiàn)象。
緩存擊穿的原因通常有以下幾種:
- 緩存中不存在所需的熱點(diǎn)數(shù)據(jù):當(dāng)系統(tǒng)中某個(gè)熱點(diǎn)數(shù)據(jù)需要被頻繁訪問時(shí),如果這個(gè)熱點(diǎn)數(shù)據(jù)最開始沒有被緩存,那么就會(huì)導(dǎo)致系統(tǒng)每次請(qǐng)求都需要直接查詢數(shù)據(jù)庫(kù),造成數(shù)據(jù)庫(kù)負(fù)擔(dān)。
- 緩存的熱點(diǎn)數(shù)據(jù)過期:當(dāng)一個(gè)熱點(diǎn)數(shù)據(jù)過期并需要重新緩存時(shí),如果此時(shí)有大量請(qǐng)求,那么就會(huì)導(dǎo)致所有請(qǐng)求都要直接查詢數(shù)據(jù)庫(kù)。
如何解決
主要思路 : 在遇到緩存擊穿問題時(shí),我們可以在查詢數(shù)據(jù)庫(kù)之前,先判斷一下緩存中是否已有數(shù)據(jù),如果沒有數(shù)據(jù)則使用Redis的單線程特性,先查詢數(shù)據(jù)庫(kù)然后將數(shù)據(jù)寫入緩存中。
- 添加Redis依賴
<dependency>
<groupId>org.springframework.boot<span class="hljs-name"groupId>
<artifactId>spring-boot-starter-data-redis<span class="hljs-name"artifactId>
<span class="hljs-name"dependency>
- 在Controller中查詢數(shù)據(jù)時(shí),先從緩存中查詢數(shù)據(jù),如果緩存中無(wú)數(shù)據(jù)則進(jìn)行鎖操作
@Autowired
private RedisTemplate<String, Object> redisTemplate;
@GetMapping("/user/{id}")
public User getUserById(@PathVariable Long id){
// 先從緩存中獲取值
String userKey = "user_"+id.toString();
User user = (User) redisTemplate.opsForValue().get(userKey);
if(user == null){
// 查詢數(shù)據(jù)庫(kù)之前加鎖
String lockKey = "lock_user_"+id.toString();
String lockValue = UUID.randomUUID().toString();
try{
Boolean lockResult = redisTemplate.opsForValue().setIfAbsent(lockKey, lockValue, 60, TimeUnit.SECONDS);
if(lockResult != null && lockResult){
// 查詢數(shù)據(jù)庫(kù)
user = userRepository.findById(id).orElse(null);
if(user != null){
// 將查詢到的數(shù)據(jù)加入緩存
redisTemplate.opsForValue().set(userKey, user, 300, TimeUnit.SECONDS);
}
}
}finally{
// 釋放鎖
if(lockValue.equals(redisTemplate.opsForValue().get(lockKey))){
redisTemplate.delete(lockKey);
}
}
}
return user;
}
緩存雪崩
什么是緩存雪崩
指緩存中大量數(shù)據(jù)的失效時(shí)間集中在某一個(gè)時(shí)間段,導(dǎo)致在這個(gè)時(shí)間段內(nèi)緩存失效并額外請(qǐng)求數(shù)據(jù)庫(kù)查詢數(shù)據(jù)的請(qǐng)求大量增加,從而對(duì)數(shù)據(jù)庫(kù)造成極大的壓力和負(fù)荷。
常見的Redis緩存雪崩場(chǎng)景包括:
- 緩存服務(wù)器宕機(jī):當(dāng)緩存服務(wù)器宕機(jī)或重啟時(shí),大量的訪問請(qǐng)求將直接命中數(shù)據(jù)庫(kù),并在同一時(shí)間段內(nèi)導(dǎo)致大量的數(shù)據(jù)庫(kù)查詢請(qǐng)求,從而將數(shù)據(jù)庫(kù)壓力大幅提高。
- 緩存數(shù)據(jù)同時(shí)失效:在某個(gè)特定時(shí)間點(diǎn),緩存中大量數(shù)據(jù)的失效時(shí)間集中在一起,這些數(shù)據(jù)會(huì)在同一時(shí)間段失效,并且這些數(shù)據(jù)被高頻訪問,將導(dǎo)致大量的訪問請(qǐng)求去查詢數(shù)據(jù)庫(kù)。
- 緩存中數(shù)據(jù)過期時(shí)間設(shè)計(jì)不合理:當(dāng)緩存中的數(shù)據(jù)有效時(shí)間過短,且數(shù)據(jù)集中在同一時(shí)期失效時(shí),就容易導(dǎo)致大量的請(qǐng)求直接查詢數(shù)據(jù)庫(kù),加劇數(shù)據(jù)庫(kù)壓力。
- 波動(dòng)式的訪問過程:當(dāng)數(shù)據(jù)的訪問存在波動(dòng)式特征時(shí),例如輸出某些活動(dòng)物品或促銷商品時(shí),將會(huì)帶來(lái)高頻的查詢請(qǐng)求訪問,導(dǎo)致緩存大量失效并產(chǎn)生緩存雪崩。
如何解決
在遇到緩存雪崩時(shí),我們可以使用兩種方法:一種是將緩存過期時(shí)間分散開,即為不同的數(shù)據(jù)設(shè)置不同的過期時(shí)間;另一種是使用Redis的多級(jí)緩存架構(gòu),通過增加一層代理層來(lái)解決。具體步驟如下:
- 添加相關(guān)依賴
<dependency>
<groupId>org.springframework.boot<span class="hljs-name"groupId>
<artifactId>spring-boot-starter-data-redis<span class="hljs-name"artifactId>
<span class="hljs-name"dependency>
<dependency>
<groupId>net.sf.ehcache<span class="hljs-name"groupId>
<artifactId>ehcache<span class="hljs-name"artifactId>
<version>2.10.6<span class="hljs-name"version>
<span class="hljs-name"dependency>
- 在application.properties中配置Ehcache緩存
spring.cache.type=ehcache
- 創(chuàng)建一個(gè)CacheConfig類,用于配置Ehcache:
@Configuration
@EnableCaching
public class CacheConfig {
@Bean
public EhCacheCacheManager ehCacheCacheManager(CacheManager cm){
return new EhCacheCacheManager(cm);
}
@Bean
public CacheManager ehCacheManager(){
EhCacheManagerFactoryBean cmfb = new EhCacheManagerFactoryBean();
cmfb.setConfigLocation(new ClassPathResource("ehcache.xml"));
cmfb.setShared(true);
return cmfb.getObject();
}
}
- 在ehcache.xml中添加緩存配置
<ehcache xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="http://ehcache.org/ehcache.xsd"
updateCheck="true"
monitoring="autodetect"
dynamicConfig="true">
<cache name="userCache" maxEntriesLocalHeap="10000" timeToLiveSeconds="60" timeToIdleSeconds="30"/>
<span class="hljs-name"ehcache>
- 在Controller中查詢數(shù)據(jù)時(shí),先從Ehcache緩存中獲取,如果緩存中無(wú)數(shù)據(jù)則再?gòu)腞edis緩存中獲取數(shù)據(jù)
@Autowired
private RedisTemplate
以上就是使用SpringBoot時(shí)如何解決Redis的緩存穿透、緩存擊穿、緩存雪崩的常用方法。
-
spring
+關(guān)注
關(guān)注
0文章
340瀏覽量
14343 -
Boot
+關(guān)注
關(guān)注
0文章
149瀏覽量
35839 -
Redis
+關(guān)注
關(guān)注
0文章
375瀏覽量
10877 -
SpringBoot
+關(guān)注
關(guān)注
0文章
173瀏覽量
179
發(fā)布評(píng)論請(qǐng)先 登錄
相關(guān)推薦
評(píng)論