简单的复习:
1、为什么要使用redis
- 避免大量的请求直接访问到数据库,导致数据库宕机
- 内存访问,速度非常快
- 多种数据结构直接可以使用,提高开发效率
- 分布式锁可以用redis来做、用户token、加热的数据
2、redis有哪些持久化方式(把文件保存到磁盘上)
-
AOF
优缺点:每一秒保存;日志文件追加的形式;文件大,恢复更慢
-
RDB
优缺点:默认每15分钟,全量保存,有可能导致数据丢失;二进制的存储紧凑,恢复数据速度快
3、redis高可用
- 集群部署 ,读写分离,主多从
- redis哨兵模式,一主二从三哨兵
4、哨兵的原理
- 监听redis主库是否活着 ,用Ping的方式确认是否存活;
- 一旦有一个哨兵发现redis主不通了(主观下线)
- 只有多个哨兵同时认定某一个redis(主)下线,才是真下线(客观下线)
- 客观下线以后就会进行选举,重从库中选举一个升级为主库(投票的数据要大于配置票数),策略数据多的有限
5、redis的淘汰策略
全部键
-
不淘汰
-
LRU 时间最远
-
LFU 最近使用最少
-
随机
有设置过期时间
-
LRU 时间最远
-
LFU 最近使用最少
-
随机淘汰
-
TTL最短
课堂目标
- 理解缓存击穿、缓存雪崩、缓存穿透
- 如何解决缓存穿透的问题
- 理解布隆过滤器的原理
- Spring Boot集成Redisson
- Redisson 布隆过滤器的使用
一、数据加热的访问流程

二、缓存击穿
-
某一个key在redis里过期了,这个Key比较热(会有大量的用户去查询这个key),导致大量请求直接连接到数据库

会变化的数据(设置过期时间):session、实时监控功能相关的数据、产品信息..
基本不变的数据(永不过期):常规的配置、枚举、地区、权限...
一般解决办法:
1、刷新key的过期时间(可以用定时任务com:woniu:good:*)
2、永不过期
刷新redis的流程:
-
当有过期的key出现的时候,第一个请求进来,查一次数据库,并更新redis;下一次查询可以直接从redis获取数据
关键点:增一个同步锁(就算是分布式系统,同步锁一般也能抗住)
flag = restTemplate.optForValue.get("1000"); if(flag==null){ syc{ if(restTemplate.optForValue.get("1000")==null){ obj = mapper.getById(); //查询数据库 restTemplate.optForValue.set("1000",obj,60,TimeUnit.MINUTES); }else{ return restTemplate.optForValue.get("1000"); } } }
-
三、缓存雪崩
1、大量的、多个key同时过期,虽然每一个key的访问量不大,但是多
在请求的时候,多个key没有命中redis,导致都访问了数据库去查询

-
如何解决:
1、可以设置永不过期,但是也有可能导致内存过高(OOM)
2、监控提前刷新这些key
3、设置这些key的过期时间,用随机或者其他逻辑;不要在某一个时间里让这些key同时过期;这样就可以避免雪崩。
代码优化:给这些热key,一个随机的过期时间,不要用固定一个时间;
如果有redis过期情况,第一个请求进来的时候,先查询数据库——>然后更新redis;
后面请求就可以直接从redis获取。
伪代码如下:
flag = restTemplate.optForValue.get("1");
if(flag==null){
syc{ //加同步有什么好处?
if(restTemplate.optForValue.get("1")){
obj = mapper.getById();
restTemplate.optForValue.set("1",random(1,60),TimeUnit.MINUTES) //不要固定过期时间,给一个随机值
}
}
}
- 多级缓存:本地缓存
- 熔断降级:fallback
- 缓存预热:启动时候初始化redis;定期轮询redis数据续期
四、缓存穿透(恶意攻击)
什么叫缓存穿透?
请求中的参数,比如id,数据库和redis里根本就没有。
少量无所谓,当大量无效ID,穿过redis的时候,直接打到了数据库上

-
恶意攻击
null ""
如何解决?
-
根据实际情况,把恶意的key也放在redis里,避免打在数据库上。
处理流程:如果redis没有这一个key的数据,就直接set,下一次再访问同一个key的数据时,就直接从redis返回。
就算是数据库没有也放在redis里(可根据高频出现的实际情况来决定,比如null、或者反复出现某一类key值的请求):
request(10001)-->
flag = redisTemplate.opsForValue.get("10001");
if(flag){
return value;
}else{
redisTemplate.opsForValue.set("10001",60s);
Mapper.getById("10001");
}
- 增加查询前的验证,比如在contoller层进行必要的校验
查询数据库先进行check() ,筛选掉无效的参数字符(数字->字符, 长度等)
name = request.getPar("id")
if(id.lenth) return false;
if(id. /d+) return false;
if(id.contains(xxxx)) return false;
mapper.getById("id") ok
- 恶意的ip设置黑名单
在nginx、网关、其他安全组件里设置ip黑名单
- 布隆过滤器
五、怎么解决?
1、布隆过滤器
(一种数据结构)bitmap 位结构数据:
bit结构就是每一个位只能放0或者1;
优点:占用空间小、速度比较快
bloom过滤器的缺点:有可能误判;误判的原则:如果判断有不一定有,没有就真没有
- 实现原理

计算公式:
m = - (n * ln(p)) / (ln(2))²
n代表的是 槽位数量
p代表的误判比例
ln自然对数
m就是布隆过滤器大小

说明:
1、用一个bitmap记录key对应的hash槽位
2、具容错率大小,来确认一个key用多少个hash值来判重
3、布隆过滤器特点:没有就真没有,有不一定有
4、因为有可能不同的key,hash运算后有可能值一样
优点:
- 快速搜索
- 内存占用较小
缺点:
- 存在误判性(没有就真没有,有却不一定有)
2、如何使用布隆过滤器
- 加依赖
<!--redisson-->
<dependency>
<groupId>org.redisson</groupId>
<artifactId>redisson-spring-boot-starter</artifactId>
<version>3.17.5</version>
</dependency>
-
加配置 application.yaml
spring: data: redis: host: localhost port: 6379 password: 123456 database: 15 lettuce: pool: max-active: 8 #最大连接 max-idle: 8 #最大空闲连接 min-idle: 0 #最小空闲连接 max-wait: 100 #最大等待时间 -
加一个配置类RedissonClient类纳入容器管理,才能注入使用
package com.dw.config;
import org.redisson.Redisson;
import org.redisson.api.RedissonClient;
import org.redisson.config.Config;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class RedissonConfig {
@Value("${spring.data.redis.host}")
private String host;
@Value("${spring.data.redis.port}")
private int port;
@Value("${spring.data.redis.password}")
private String password;
@Value("${spring.data.redis.database}")
private int database;
@Bean(destroyMethod = "shutdown")
public RedissonClient redissonClient() {
Config config = new Config();
// 单节点配置
config.useSingleServer()
.setAddress("redis://" + host + ":" + port)
.setDatabase(database)
.setPassword(password)
.setConnectionPoolSize(20)
.setConnectionMinimumIdleSize(5)
.setTimeout(3000);
// 可选:配置序列化方式
// config.setCodec(new JsonJacksonCodec());
return Redisson.create(config);
}
}
- 写代码
@Autowired
private RedissonClient redissonClient;
bloomFilter = redissonClient.getBloomFilter(BLOOM_FILTER_NAME);
// 如果布隆过滤器尚未初始化,则进行初始化
if (!bloomFilter.isExists()) {
// 初始化布隆过滤器
boolean initialized = bloomFilter.tryInit(1000000, 0.01);
if (initialized) {
System.out.println("布隆过滤器初始化成功");
// 可以在这里预加载一些数据
// preloadData();
} else {
System.out.println("布隆过滤器初始化失败");
}
} else {
System.out.println("布隆过滤器已存在,直接使用");
}
bloom过滤器常用的方法
//是否初始化
bloomFilter.isExists()
// 添加测试数据
bloomFilterService.add("test1");
// 验证已添加的元素
bloomFilterService.contains("test1");
//返回元素数量
bloomFilterService.count();
//查看误判率
bloomFilterService.getFalseProbability();
1、测试一下,看看不同过的长度和误差率内存占用大小
2、你觉得数据库新增加一条数据的时候,是否需要去同步在bloom过滤器中加数据?如何什么方式加?
3、启动的时候需要初始化布隆过滤器吗?

17-3阶内容-1.7.5-布隆过滤器
https://xiaochenblog.icu/archives/17-3jie-nei-rong-1.7.5-bu-long-guo-lu-qi
评论