Redis的并发问题

回顾Redis 的使用

Redis 是一种 key-value 的 nosql 数据库,基于内存

使用场景:

  1. 缓存
  2. 分布式锁
  3. 分布式 session
  4. 消息队列
  5. 计数器
  6. 社交网络
  7. 排行榜

缓存使用过程

一、Redis 的并发问题

问题

说明

解决方法

雪崩

1.大量热点key同时失效,导致大量请求直接访问数据库,导致数据库宕机

2. Redis服务器重启或出现问题

1. 将热点的key过期时间设置随机的

2. 搭建Redis集群

击穿

大量线程并发访问,没等前面线程访问后存到Redis,其它线程就直接访问数据库

使用双检锁机制,保存读取缓存,读取数据和保存缓存原子执行

穿透

大量线程查询数据库中没有的数据,Redis不能保存,数据库压力过大

1. 在Redis保存空对象,设置过期时间

2. 使用布隆过滤器排除数据库不存在的数据

解决 Redis 并发代码

@Service
public class StudentServiceImpl extends ServiceImpl<StudentMapper, Student> implements StudentService {

    //  缓存中学生信息的key前缀
    public static final String PREFIX = "Student:";

    @Autowired
    private StudentMapper studentMapper;

    // Redis操作魔板,用于操作缓存
    // RedisTemplate  它封装了 Redis 连接的创建、资源管理、以及对 Redis 数据结构的操作
    @Autowired
    private RedisTemplate<String, Object> redisTemplate;

    // Redis布隆过滤器,用于判断数据是否存在于缓存
    @Autowired
    private RBloomFilter bloomFilter;

    @Override
    public Student getStudentById(Long id) {
        ValueOperations<String, Object> ops = redisTemplate.opsForValue();
        Student student = (Student) ops.get(PREFIX + id);

        if (student == null) {
            synchronized (this) {
                student = (Student) ops.get(PREFIX + id);

                if (student == null) {

                    System.err.println("Redis为空,访问数据库:" + Thread.currentThread().getName());
                    Student selectById = studentMapper.selectById(id);
                    if (selectById != null) {
                        System.err.println("数据库不为空,保存到数据库:" + Thread.currentThread().getName());
                        ops.set(PREFIX + id, selectById);
                        return selectById;
                    } else {
                        System.err.println("数据库为空,返回空值:" + Thread.currentThread().getName());
                        ops.set(PREFIX + id, new Student(), 10, TimeUnit.SECONDS);
                    }
                    return null;
                }
            }
        }
        System.err.println("Redis存在就直接访问数据:" + Thread.currentThread().getName());
        return student;
    }
}

二、布隆过滤器

布隆过滤器:一系列二进制数列和哈希函数组成

是很长的数组(向量),每个元素就是0或1。

用于判断某个数据是否存在于某个集合中,向集合添加数据后,会将数据进行一系列哈希运算,将计算出的位置上的值设置1。

进行对象判断时,重复进行哈希运算,判断该位置上是否都是1。

存在的问题:能准确判断数据在集合中是否不存在,判断数据存在一定的误差(向量越长,哈希运算越多,误差率越低,内存消耗越大)。

实现步骤:

1) 使用Redis实现布隆过滤器(Redisson)

2) 将数据库中所有数据的id保存到布隆过滤器中

3) 数据库查询之前,使用布隆过滤器排除掉不存在的id

1)Redisson 依赖

<dependency>
    <groupId>org.redisson</groupId>
    <artifactId>redisson-spring-boot-starter</artifactId>
    <version>3.17.0</version>
</dependency>

2)配置类返回布隆过滤器

@Configuration
public class RedissonConfig {
    @Bean
    //创建并配置Redisson布隆过滤器的 Bean   RBloomFilter 是Redisson框架中提供的布隆过滤器的接口
    public RBloomFilter<String> bloomFilter() {
        //创建Redisson配置对象
        Config config = new Config();
        // 设置IO模式   NIO表示非阻塞IO
        config.setTransportMode(TransportMode.NIO);
        // 设置单服务器配置
        SingleServerConfig singleServerConfig = config.useSingleServer();
        //  配置 Redis 服务器地址,可以有“rediss://”来启用SSL连接
        singleServerConfig.setAddress("redis://127.0.0.1:6379");
        //  创建redisson客户端
        RedissonClient redisson = Redisson.create(config);
        //  获取或创建名为"student-filter" 的布隆过滤器
        RBloomFilter<String> bloomFilter = redisson.getBloomFilter("student-filter");
        //  初始化布隆过滤器,参数1为向量长度,参数2为错误率
        bloomFilter.tryInit(10000000L, 0.03);
        // 返回配置好的布隆过滤器 Bean
        return bloomFilter;
    }
}

3)将存在的数据存入布隆过滤器

@RestController
public class StudentController {

    @Autowired
    private StudentService studentService;

    @Autowired
    private RBloomFilter<String> bloomFilter;
    @GetMapping("init-bloomfilter")
    // 初始化布隆过滤器
    public ResponseResult<String> initBloomFilter() {
        // 查询所有学生的ID列表
        List<Student> list = studentService.list();
        // 将学生ID添加到布隆过滤器中
        list.forEach(student -> {
            bloomFilter.add(StudentServiceImpl.PREFIX + student.getId());
        });
        // 返回初始化布隆过滤器操作的响应结果
        return ResponseResult.ok("ok");
    }
}

4)查询数据库之前,用布隆过滤器过滤掉不存在的key

@Service
public class StudentServiceImpl extends ServiceImpl<StudentMapper, Student> implements StudentService {

    //  缓存中学生信息的key前缀
    public static final String PREFIX = "Student:";

    @Autowired
    private StudentMapper studentMapper;

    // Redis操作魔板,用于操作缓存
    // RedisTemplate  它封装了 Redis 连接的创建、资源管理、以及对 Redis 数据结构的操作
    @Autowired
    private RedisTemplate<String, Object> redisTemplate;

    // Redis布隆过滤器,用于判断数据是否存在于缓存
    @Autowired
    private RBloomFilter bloomFilter;

    @Override
    public Student getStudentById(Long id) {
        // 获取操作字符串类型数据的操作对象
        ValueOperations<String, Object> ops = redisTemplate.opsForValue();
        // 尝试从Redis缓存中获取指定ID对应的学生信息
        Student student = (Student) ops.get(PREFIX + id);
        // 使用DCL双检锁提升性能
        if (student == null) {
            synchronized (this) {
                // 再次查询,防止多个线程同时通过第一次判断
                student = (Student) ops.get(PREFIX + id);
                if (student == null) {
                    System.err.println("Redis查询Student为空,现在访问数据库:" + Thread.currentThread().getName());
                    // 使用布隆过滤器判断数据库没有的id
                    if (!bloomFilter.contains(PREFIX + id)) {
                        System.err.println("数据库为空,返回空值 " + Thread.currentThread().getName());
                        return null;
                    }
                    // 从数据库查询ID对应的学生信息
                    Student selectById = studentMapper.selectById(id);
                    //查询数据库不为空,就保存到redis中
                    if (selectById != null) {
                        System.err.println("数据库不为空,保存到数据库:" + Thread.currentThread().getName());
                        ops.set(PREFIX + id, selectById);
                        return selectById;
                    }
                    return null;
                }
            }
        }
        System.err.println("redis里面的Student存在,直接返回数据" + Thread.currentThread().getName());
        return student;
    }
}

三、Redis 的集群

Redis 集群提高 Redis 的可以性,不会因为单点故障导致 Redis 不可用

Redis 集群的基础:主从架构

一台 Redis 服务器做主机(Master),两台 Redis 做从机(Slave)

读写分离:主机负责写,从机负责读

主从复制:主机数据修改,会将数据同步到所有的从机上

为什么最少上台机器?

涉及 Master 选举,必须达到半数以上

常见集群方案:

1)哨兵架构

由多个主从架构和哨兵机器组成,哨兵监视主从架构运行的情况,在主机出现问题时,让从机成为主机,保证集群的正常运行

优势:可用性高

缺点:哨兵资源有一定浪费

2)集群架构

由 N 台 Redis 服务器组成,把所有的机器分为 16384 个槽 (slot),每个 key 保存时进行哈希运算,存入槽中,每个槽再路由到集群中的每一台机器。

优势:资源利用和可用性都比较高

搭建伪集群

Redis并发问题(雪崩、击穿、穿透)-CSDN博客

创建集群出现问题:

1) xxx not empty ....

停掉每个Redis服务

到每一个redis01~redis06/src 删除 appendonly.aof 和 dump.rdb

再启动start.sh

2) slot not cover .. 槽没有覆盖完整

修复集群

redis-cli --cluster fix IP地址:端口

四 、Redis 和数据库的同步问题

几种同步方式:

1)SpringCache 申明式注解

@CachePut @CachEvit

代码侵入性比较低

2)Canal 主键监听 Mysql 的修改,进行同步

3)手动完成同步,延迟双删

一般情况下有两种方法:

1、先删缓存,再更新数据库

问题:

2、选更新数据库,在删缓存

问题:

延迟双删解决上面存在的问题,更新数据库执行删除一次,更新数据库,更新后再删除一次缓存。

延迟多久?

一般不会超过 2s,看网络和业务复杂情况

面试题:

1) 缓存的雪崩、击穿和穿透是什么原因,解决方案?

2) 什么是布隆过滤器,有什么用

3) Redis集群有哪些架构?各自的特点是?

4) 介绍Redis的数据库同步方案,延迟双删

  • 16
    点赞
  • 19
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值