本文正在参与「金石计划」
大家好,我是飘渺!今日给大家介绍一下如安在SpringBoot中处理Redis的缓存穿透、缓存击穿、缓存雪崩的问题。
缓存穿透
什么是缓存穿透
缓存穿透指的是一个缓存体系无法缓存某个查询的数据,然后导致这个查询每一次都要拜访数据库。
常见的Redis缓存穿透场景包含:
- 查询一个不存在的数据:攻击者可能会发送一些无效的查询来触发缓存穿透。
- 查询一些非常热门的数据:假如一个数据被拜访的非常频频,那么可能会导致缓存体系无法处理这些恳求,然后形成缓存穿透。
- 查询一些反常数据:这种情况通常产生在数据服务呈现毛病或反常时,然后形成缓存体系无法拜访相关数据,然后导致缓存穿透。
怎么处理
咱们能够运用Guava在内存中保护一个布隆过滤器。具体步骤如下:
- 增加Guava和Redis依靠:
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
<version>29.0-jre</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
- 创立一个BloomFilterUtil类,用于在缓存中保护Bloom Filter。
public class BloomFilterUtil {
// 布隆过滤器的估计容量
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中查询数据时,先依据恳求参数进行Bloom Filter的过滤
@Autowired
private RedisTemplate<String, Object> redisTemplate;
@GetMapping("/user/{id}")
public User getUserById(@PathVariable Long id){
// 先从布隆过滤器中判别此id是否存在
if(!BloomFilterUtil.mightContain(id.toString())){
return null;
}
// 查询缓存数据
String userKey = "user_"+id.toString();
User user = (User) redisTemplate.opsForValue().get(userKey);
if(user == null){
// 查询数据库
user = userRepository.findById(id).orElse(null);
if(user != null){
// 将查询到的数据参加缓存
redisTemplate.opsForValue().set(userKey, user, 300, TimeUnit.SECONDS);
}else{
// 查询结果为空,将恳求记录下来,并在布隆过滤器中增加
BloomFilterUtil.add(id.toString());
}
}
return user;
}
缓存击穿
什么是缓存击穿
缓存击穿指的是在一些高并发拜访下,一个热点数据从缓存中不存在,每次恳求都要直接查询数据库,然后导致数据库压力过大,而且体系功能下降的现象。
缓存击穿的原因通常有以下几种:
- 缓存中不存在所需的热点数据:当体系中某个热点数据需求被频频拜访时,假如这个热点数据最开端没有被缓存,那么就会导致体系每次恳求都需求直接查询数据库,形成数据库负担。
- 缓存的热点数据过期:当一个热点数据过期并需求重新缓存时,假如此时有很多恳求,那么就会导致所有恳求都要直接查询数据库。
怎么处理
首要思路 : 在遇到缓存击穿问题时,咱们能够在查询数据库之前,先判别一下缓存中是否已有数据,假如没有数据则运用Redis的单线程特性,先查询数据库然后将数据写入缓存中。
- 增加Redis依靠
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
- 在Controller中查询数据时,先从缓存中查询数据,假如缓存中无数据则进行锁操作
@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){
// 查询数据库之前加锁
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){
// 查询数据库
user = userRepository.findById(id).orElse(null);
if(user != null){
// 将查询到的数据参加缓存
redisTemplate.opsForValue().set(userKey, user, 300, TimeUnit.SECONDS);
}
}
}finally{
// 释放锁
if(lockValue.equals(redisTemplate.opsForValue().get(lockKey))){
redisTemplate.delete(lockKey);
}
}
}
return user;
}
缓存雪崩
什么是缓存雪崩
指缓存中很多数据的失效时刻会集在某一个时刻段,导致在这个时刻段内缓存失效并额定恳求数据库查询数据的恳求很多增加,然后对数据库形成极大的压力和负荷。
常见的Redis缓存雪崩场景包含:
- 缓存服务器宕机:当缓存服务器宕机或重启时,很多的拜访恳求将直接命中数据库,并在同一时刻段内导致很多的数据库查询恳求,然后将数据库压力大幅提高。
- 缓存数据一起失效:在某个特定时刻点,缓存中很多数据的失效时刻会集在一起,这些数据会在同一时刻段失效,而且这些数据被高频拜访,将导致很多的拜访恳求去查询数据库。
- 缓存中数据过期时刻设计不合理:当缓存中的数据有用时刻过短,且数据会集在同一时期失效时,就容易导致很多的恳求直接查询数据库,加剧数据库压力。
- 动摇式的拜访过程:当数据的拜访存在动摇式特征时,例如输出某些活动物品或促销商品时,将会带来高频的查询恳求拜访,导致缓存很多失效并产生缓存雪崩。
怎么处理
在遇到缓存雪崩时,咱们能够运用两种办法:一种是将缓存过期时刻分散开,即为不同的数据设置不同的过期时刻;另一种是运用Redis的多级缓存架构,通过增加一层代理层来处理。具体步骤如下:
- 增加相关依靠
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<dependency>
<groupId>net.sf.ehcache</groupId>
<artifactId>ehcache</artifactId>
<version>2.10.6</version>
</dependency>
- 在application.properties中装备Ehcache缓存
spring.cache.type=ehcache
- 创立一个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中增加缓存装备
<?xml version="1.0" encoding="UTF-8"?>
<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"/>
</ehcache>
- 在Controller中查询数据时,先从Ehcache缓存中获取,假如缓存中无数据则再从Redis缓存中获取数据
@Autowired
private RedisTemplate<String, Object> redisTemplate;
@Autowired
private CacheManager ehCacheManager;
@GetMapping("/user/{id}")
@Cacheable(value = "userCache", key = "#id")
public User getUserById(@PathVariable Long id){
// 先从Ehcache缓存中获取
String userKey = "user_"+id.toString();
User user = (User) ehCacheManager.getCache("userCache").get(userKey).get();
if(user == null){
// 再从Redis缓存中获取
user = (User) redisTemplate.opsForValue().get(userKey);
if(user != null){
ehCacheManager.getCache("userCache").put(userKey, user);
}
}
return user;
}
以上就是运用SpringBoot时怎么处理Redis的缓存穿透、缓存击穿、缓存雪崩的常用办法。