简单的复习:

1、为什么要使用redis

  • 避免大量的请求直接访问到数据库,导致数据库宕机
  • 内存访问,速度非常快
  • 多种数据结构直接可以使用,提高开发效率
  • 分布式锁可以用redis来做、用户token、加热的数据

2、redis有哪些持久化方式(把文件保存到磁盘上)

  • AOF

    优缺点:每一秒保存;日志文件追加的形式;文件大,恢复更慢

  • RDB

    优缺点:默认每15分钟,全量保存,有可能导致数据丢失;二进制的存储紧凑,恢复数据速度快

3、redis高可用

  • 集群部署 ,读写分离,主多从
  • redis哨兵模式,一主二从三哨兵

4、哨兵的原理

  • 监听redis主库是否活着 ,用Ping的方式确认是否存活;
  • 一旦有一个哨兵发现redis主不通了(主观下线)
  • 只有多个哨兵同时认定某一个redis(主)下线,才是真下线(客观下线)
  • 客观下线以后就会进行选举,重从库中选举一个升级为主库(投票的数据要大于配置票数),策略数据多的有限

5、redis的淘汰策略

全部键

  • 不淘汰

  • LRU 时间最远

  • LFU 最近使用最少

  • 随机

    有设置过期时间

  • LRU 时间最远

  • LFU 最近使用最少

  • 随机淘汰

  • TTL最短

课堂目标

  1. 理解缓存击穿、缓存雪崩、缓存穿透
  2. 如何解决缓存穿透的问题
  3. 理解布隆过滤器的原理
  4. Spring Boot集成Redisson
  5. Redisson 布隆过滤器的使用

一、数据加热的访问流程

image-20251209113301914

二、缓存击穿

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

    image-20251106164936170

    会变化的数据(设置过期时间):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,导致都访问了数据库去查询

image-20251106165951577

  • 如何解决:

    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的时候,直接打到了数据库上

image-20250909141523213

  • 恶意攻击

    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过滤器的缺点:有可能误判;误判的原则:如果判断有不一定有没有就真没有

  • 实现原理

image-20250910100921203

计算公式:

m = - (n * ln(p)) / (ln(2))²

n代表的是 槽位数量

p代表的误判比例

ln自然对数

m就是布隆过滤器大小

image-20250824205018490

说明:

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、启动的时候需要初始化布隆过滤器吗?

image-20251106164649383