文章目录
- 一、介绍
- 二、方案介绍
- 三、Redis Docker部署
- 四、SpringBoot3 Base代码
- 1. 依赖配置
- 2. 基本代码
- 五、缓存优化代码
- 1. 校验机制
- 2. 布隆过滤器
- 3. 逻辑优化
一、介绍
当一种请求,总是能越过缓存,调用数据库,就是缓存穿透。
比如当请求一个数据库没有的数据,那么缓存也不会有,然后就一直请求,甚至高并发去请求,对数据库压力会增大。
二、方案介绍
- 如果
key
具有某种规则,那么可以对key增加校验机制,不符合直接返回。 -
Redisson
布隆过滤器 - 逻辑修改,当数据库没有此数据,以
null
为value
,也插入redis
缓存,但设置较短的过期时间。
三、Redis Docker部署
docker-compose示例如下,redis.conf从这里下载
redis:
container_name: redis
image: redis:7.2
volumes:
- ./redis/redis.conf:/usr/local/etc/redis/redis.conf
ports:
- "6379:6379"
command: [ "redis-server", "/usr/local/etc/redis/redis.conf" ]
四、SpringBoot3 Base代码
1. 依赖配置
<!-- redis -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<!-- redis 连接线程池 -->
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-pool2</artifactId>
<version>2.11.1</version>
</dependency>
<!-- redisson -->
<dependency>
<groupId>org.redisson</groupId>
<artifactId>redisson</artifactId>
<version>3.24.3</version>
</dependency>
spring:
data:
redis:
host: 192.168.101.65 # Redis服务器的主机名或IP地址
port: 6379 # Redis服务器的端口号
password: # 用于连接Redis服务器的密码
database: 0 # 要连接的Redis数据库的索引号
lettuce:
pool:
max-active: 20 # 连接池中最大的活跃连接数
max-idle: 10 # 连接池中最大的空闲连接数
min-idle: 0 # 连接池中最小的空闲连接数
timeout: 10000 # 连接超时时间(毫秒)
lock-watchdog-timeout: 100 # Redisson的分布式锁的看门狗超时时间(毫秒)
2. 基本代码
要演示的代码很简单,就是一个携带courseId
请求过来,调用下面的service
函数,然后查询数据库。
@Override
public CoursePublish getCoursePublish(Long courseId) {
return coursePublishMapper.selectById(courseId);
}
当我们使用redis改造时,基本代码如下
@Override
public CoursePublish getCoursePublishCache(Long courseId) {
String key = "content:course:publish:" + courseId;
//先查询redis
Object object = redisTemplate.opsForValue().get(key);
if (object != null){
String string = object.toString();
CoursePublish coursePublish = JSON.parseObject(string, CoursePublish.class);
return coursePublish;
}else {
//后查询数据库
CoursePublish coursePublish = getCoursePublish(courseId);
if (coursePublish != null){
redisTemplate.opsForValue().set(key, JSON.toJSONString(coursePublish));
}
return coursePublish;
}
}
五、缓存优化代码
1. 校验机制
我这里的id
没规则,所以加不了,跳过。
2. 布隆过滤器
读取yaml
配置
@Data
@Component
@ConfigurationProperties(prefix = "spring.data.redis")
public class RedisProperties {
private String host;
private int port;
private String password;
private int database;
private int lockWatchdogTimeout;
}
配置RedissonClient
@Slf4j
@Configuration
public class RedissionConfig {
@Autowired
private RedisProperties redisProperties;
@Bean
public RedissonClient redissonClient() {
RedissonClient redissonClient;
Config config = new Config();
//starter依赖进来的redisson要以redis://开头,其他不用
String url = "redis://"+ redisProperties.getHost() + ":" + redisProperties.getPort();
config.useSingleServer().setAddress(url)
//.setPassword(redisProperties.getPassword())
.setDatabase(redisProperties.getDatabase());
try {
redissonClient = Redisson.create(config);
return redissonClient;
} catch (Exception e) {
log.error("RedissonClient init redis url:[{}], Exception:", url, e);
return null;
}
}
}
把布隆过滤器加到service
,如下
private RBloomFilter<String> bloomFilter;
@PostConstruct
public void init(){
//初始化布隆过滤器
bloomFilter = redissonClient.getBloomFilter("bloom-filter");
bloomFilter.tryInit(100, 0.003);
List<CoursePublish> coursePublishList = coursePublishMapper.selectList(new LambdaQueryWrapper<CoursePublish>());
coursePublishList.forEach(coursePublish -> {
String key = "content:course:publish:" + coursePublish.getId();
bloomFilter.add(key);
});
}
@Override
public CoursePublish getCoursePublishCache(Long courseId) {
String key = "content:course:publish:" + courseId;
//布隆过滤器
boolean contains = bloomFilter.contains(key);
if (!contains){
return null;
}
//先查询redis
Object object = redisTemplate.opsForValue().get(key);
if (object != null){
String string = object.toString();
CoursePublish coursePublish = JSON.parseObject(string, CoursePublish.class);
return coursePublish;
}else {
//后查询数据库
CoursePublish coursePublish = getCoursePublish(courseId);
if (coursePublish != null){
bloomFilter.add(key);
redisTemplate.opsForValue().set(key, JSON.toJSONString(coursePublish));
}
return coursePublish;
}
}
3. 逻辑优化
当数据库没有此数据,以null
为value
,也插入redis
缓存,但设置较短的过期时间。
//后查询数据库
CoursePublish coursePublish = getCoursePublish(courseId);
if (coursePublish != null) {
bloomFilter.add(key);
redisTemplate.opsForValue().set(key, JSON.toJSONString(coursePublish));
}else {
redisTemplate.opsForValue().set(key, JSON.toJSONString(coursePublish), 10, TimeUnit.SECONDS);
}
return coursePublish;