大家好,我是袁庭新。使用Redis作为缓存数据库时,在高并发场景下有可能出现缓存雪崩、缓存穿透、缓存击穿等问题。
1.缓存穿透介绍
Redis的缓存穿透是指客户端请求的数据在缓存中不存在,并且在数据库中也不存在,导致大量请求直接穿透缓存直接打到数据库。如果这个查询频繁发生,那么每次都会直接访问数据库,导致数据库压力增大。更糟糕的是,如果攻击者利用这一点进行恶意请求,可能会对系统造成严重的性能问题甚至服务不可用。
2.解决方案
企业为了避免Redis的缓存穿透,可以采取以下几种策略:
1.缓存空对象:当从数据库中查询不到数据时,仍然将空对象缓存到Redis中,并设置一个较短的过期时间。这样下次再有相同的请求时就可以直接从缓存返回,而不需要再次查询数据库。
以下是使用Spring Boot框架结合Redis实现缓存空对象的示例代码:
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;
import javax.annotation.Resource;
import java.util.concurrent.TimeUnit;
@Service
public class UserService {
// 空对象缓存过期时间,单位秒
private static final long NULL_CACHE_EXPIRE_TIME = 60;
@Resource
private RedisTemplate<String, Object> redisTemplate;
@Resource
private UserMapper userMapper;
public User getUserById(Long id) {
String key = "user:" + id;
User user = (User) redisTemplate.opsForValue().get(key);
if (user == null) {
user = userMapper.selectById(id);
if (user == null) {
// 将空对象缓存到Redis中,并设置过期时间
redisTemplate.opsForValue().set(key, "", NULL_CACHE_EXPIRE_TIME, TimeUnit.SECONDS);
} else {
redisTemplate.opsForValue().set(key, user);
}
}
return user;
}
}
2.使用布隆过滤器:布隆过滤器是一种基于位图的数据结构,用于快速判断一个元素是否存在于一个集合中。在缓存系统中,可以将数据库中存在的所有key预先加载到布隆过滤器中。当有请求到来时,先通过布隆过滤器进行判断,如果元素不存在于布隆过滤器中,则直接返回,不再查询缓存和数据库。布隆过滤器可能会产生误判(即认为某个元素存在但实际上不存在),但绝对不会漏判。
以下是使用Google Guava库中的布隆过滤器的示例代码:
import com.google.common.hash.BloomFilter;
import com.google.common.hash.Funnels;
import java.nio.charset.Charset;
public class BloomFilterExample {
private static final int EXPECTED_INSERTIONS = 1000000;
private static final double FPP = 0.01;
public static void main(String[] args) {
// 创建布隆过滤器
BloomFilter<String> bloomFilter = BloomFilter.create(
Funnels.stringFunnel(Charset.forName("UTF-8")),
EXPECTED_INSERTIONS,
FPP
);
// 模拟将数据库中的key加载到布隆过滤器中
for (int i = 0; i < EXPECTED_INSERTIONS; i++) {
String key = "key" + i;
bloomFilter.put(key);
}
// 模拟请求判断
String testKey = "key999999";
if (bloomFilter.mightContain(testKey)) {
System.out.println("可能存在");
} else {
System.out.println("一定不存在");
}
}
}
别忘了还需要在项目的pom.xml文件中添加Google Guava的依赖。
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
<version>33.4.0-jre</version>
</dependency>
3.接口限流与熔断:在客户端请求到达应用服务器之前,通过限流算法对请求进行限制,如限制每秒的请求次数、限制并发请求数等。当请求量超过设定的阈值时,直接拒绝多余的请求,返回友好的提示信息。同时,结合熔断机制,当发现缓存穿透的情况较为严重时,自动熔断对该接口的访问,一段时间内直接返回错误信息,避免大量请求继续穿透到数据库。
以下是使用Spring Cloud Gateway实现接口限流和熔断的示例配置:
spring:
cloud:
gateway:
routes:
- id: user-service
uri: lb://user-service
predicates:
- Path=/user/**
filters:
- name: RequestRateLimiter
args:
key-resolver: "#{@userKeyResolver}"
redis-rate-limiter.replenishRate: 100
redis-rate-limiter.burstCapacity: 200
- name: Hystrix
args:
name: userFallback
fallbackUri: forward:/fallback/user
4.缓存预热:在系统启动或业务低峰期,提前将可能会被频繁访问的数据加载到缓存中,这样可以避免在业务高峰期首次访问数据时出现缓存穿透的情况。
以下是一个简单的缓存预热示例代码:
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;
import javax.annotation.Resource;
import java.util.List;
@Component
public class DataPreheat {
@Resource
private RedisTemplate<String, Object> redisTemplate;
@Resource
private UserMapper userMapper;
public void preheatUserCache() {
// 查询数据库中所有用户数据
List<User> userList = userMapper.selectList(null);
for (User user : userList) {
String key = "user:" + user.getId();
redisTemplate.opsForValue().set(key, user);
}
}
}
5.实时监控与告警:通过对缓存系统和数据库的实时监控,及时发现缓存穿透的异常情况。一旦发现异常,立即发出告警通知,以便运维人员和开发人员及时采取措施进行处理。可以使用Prometheus结合Grafana进行监控和告警配置。通过在应用程序中集成Prometheus客户端,暴露缓存命中率、数据库查询次数等指标,然后在Grafana中配置监控面板和告警规则。