目录

一、使用场景

二、redis分布式锁

1.执行流程

三、redisson实现的分布式锁

1.看门狗机制

2.可重入锁

3.trylock细节

四、redisson分布式锁的使用场景案例

1.秒杀商品抢购

五、相关面试问题

1.redis的分布式锁是如何实现的?

2.你是如何控制redis分布式锁的有效时长呢?

3.redisson实现的分布式锁是可重入的吗?

4.redisson实现的分布式锁能解决主从一致问题吗?


  前言:

        我觉得无论做什么还是要知行合一,至少对于我来说只看理论是记忆不长久的,底层的细节还是无法真正体会的。前些日子也是还在焦虑实习的事情,但是也是看到了一句话:“让他们卷去吧!我躺平了,至少以后是饿不死的。”我在想确实是饿不死的,我卷也是为了以后生活质量的提高,但是无论干什么事情还是自己心里喜欢,自己愿意才能事半功倍,我喜欢坐在电脑桌前写着这些代码,与其焦虑,不如真正的放慢速度,好好的去干一些自己喜欢的事情...

        于是我便想着,反正要背八股,不如真正的放慢速度,把每一章的知识重新复习一遍,其中的场景都实现一遍,每一个知识点都去做个笔记,每一个面试题都用自己的话说一遍,真正的去享受知识...

        以下笔记都是基于黑马程序员的面试题写的:

Redis中关于缓存三兄弟(穿透、击穿、雪崩)和数据一致性问题的解决方案实例-CSDN博客

Redis 持久化机制_redis save 机制-CSDN博客

Redis数据过期策略_redis 如何解决数据过期-CSDN博客

Redis数据淘汰策略-CSDN博客

Redis分布式锁_redis锁-CSDN博客

Docker搭建主从同步集群-CSDN博客

Redis集群方案——主从复制-CSDN博客

Docker搭建Redis哨兵集群-CSDN博客

Redis集群方案——哨兵机制-CSDN博客

Docker搭建Redis分片集群-CSDN博客

Redis集群方案——Redis分片集群-CSDN博客

Redis网络模型-CSDN博客

Redis面试相关问题总结-CSDN博客

一、使用场景

Redis分布式锁适用于需要强一致性和互斥访问的场景

  • 场景1: 防止重复操作(如提交订单或支付)

  • 场景2:资源访问控制(如库存扣减或秒杀商品抢购)

  • 场景3: 分布式任务调度(如集群环境防止定时任务重复执行)

二、redis分布式锁

        Redis实现分布式锁主要利用Redis的setnx命令。setnx是SET if not exists(如果不存在,则 SET)的简写。

获取锁:

# 添加锁,NX是互斥、EX是设置超时时间

SET lock value NX EX 10

释放锁:

# 释放锁,删除即可

DEL key

1.执行流程

        redis分布式锁的执行流程如下:

        当一个线程尝试获取锁失败后,就会进入阻塞等待状态;获取成功后就开始执行任务,执行完成后自动释放锁,期间若业务超时或者服务宕机,锁也会在指定的超时时间后自动释放锁,从而避免了死锁的发生。


三、redisson实现的分布式锁

        在上述redis的setnx指令中,我们并不好控制其中锁的有效时长,当业务执行过长,超过了锁的持有时间,那么就会对业务的原子性造成影响。

        Redisson 提供了更完善的分布式锁实现:

1.看门狗机制

        redis中提供了看门狗机制,很好的解决了业务执行时间大于锁的持有时间问题:

        当线程1加锁成功之后,会单独开启一个线程用于锁的续期(Watch dog),默认锁的持有时间是30s,也就是每隔10s做一次锁的续期。且redisson提供了锁的重试机制,也就是持锁期间有其他线程来请求,那么其他的线程获取锁失败后就会一直循环获取,当然会有一个循环的阈值,这样的好处就是在高并发情况下,能够很好的提高分布式锁的使用性能。

2.可重入锁

        redisson中提供了可重入锁机制,同一线程可多次获取锁,这样避免了死锁的发生。其中底层采用的是如下所示的hash数据结构,记录线程id和重入次数:

3.trylock细节

        对于trylock()方法,有以下的关于看门狗机制的细节:

RLock seckillLock = redissonClient.getLock("seckill");
// 等待时间为10s,默认持有时间为30s,启用看门狗机制
seckillLock.tryLock(10, TimeUnit.SECONDS);
// 等待时间为10s,自设置持有时间为30s,不启用看门狗机制
seckillLock.tryLock(10, 30, TimeUnit.SECONDS);

四、redisson分布式锁的使用场景案例

1.秒杀商品抢购

场景说明:对于秒杀商品的抢购实现一人一单且无超卖问题

流程详解:

首先,通过布隆过滤器过滤掉不存在的商品。

// 1.布隆过滤器过滤
        RBloomFilter<Long> bloomFilter = redissonClient.getBloomFilter("product");
        if (!bloomFilter.contains(productId)) {
            throw new RuntimeException("秒杀商品抢购:商品不存在");
        }

然后,判断用户是否已经参与抢购。

// 2.查询用户是否已经抢购了该商品
        Boolean isSeckill = redisTemplate.opsForSet().isMember("seckill:" + productId, userId);
        // 2.1 已经抢购则直接返回
        if (isSeckill) {
            return "用户已参与该商品的抢购";
        }

其次,判断库存是否充裕。

// 3.判断库存是否充足
        Product product = productMapper.selectById(productId);
        if (product.getStock() <= 0) {
            return "商品已售罄";
        }

接着,使用分布式锁防止业务并发执行,并进行双重校验用户是否已经参与抢购,若仍未抢购,则扣减库存并创建订单。

// 4.扣减库存并创建订单
        RLock seckillLock = redissonClient.getLock("seckill" + productId);
        try {
            // 4.1 获取锁
            Boolean flag = seckillLock.tryLock(10, TimeUnit.SECONDS);
            if (!flag) {
                return "系统繁忙,请稍后重试...";
            }
            // 4.2 双重检测,查询用户是否已经抢购了该商品
            isSeckill = redisTemplate.opsForSet().isMember("seckill:" + productId, userId);
            if (isSeckill) {
                return "用户已参与该商品的抢购";
            }
            // 4.2 扣减库存
            LambdaUpdateWrapper<Product> updateWrapper = new LambdaUpdateWrapper<>();
            updateWrapper
                    .set(Product::getStock, product.getStock() - 1)
                    .eq(Product::getProductId, productId)
                    .gt(Product::getStock, 0);
            productMapper.update(updateWrapper);
            // 4.3 创建订单
            redisTemplate.opsForSet().add("seckill:" + productId, userId);
            return "抢购成功";
        } catch (InterruptedException e) {
            throw new RuntimeException("系统繁忙,请稍后重试...");
        } finally {
            seckillLock.unlock();
        }

运行测试:我们使用jemeter来进行高并发的测试,1s内发送100次请求来测试。

查看数据库,一人只抢购了一单:

查看结果树,只有一个请求抢购成功:

五、相关面试问题

1.redis的分布式锁是如何实现的?

        在redis中提供了一个命令setnx,由于redis是单线程的,当一个客户端对某一个key使用了命令后,在相应的key没有过期或删除时,其余的客户端是无法设置该key的。

2.你是如何控制redis分布式锁的有效时长呢?

        redis的setnx指令并不好控制锁的有效时长,我最上使用的是redisson框架来实现这一问题的。

        redisson框架中提供了一种锁的续期机制——看门狗机制。

        首先,当一个线程获取分布式锁成功后,锁的默认持有时间为30秒,看门狗机制会每隔锁的持有时间%3,也就是10s来检测当前业务是否还持有锁,若持有锁则继续增加锁的持有时间,完成业务后会自动释放锁。

        然后,若其他线程获取锁失败,则会一直循环获取,直到成功获取锁,当然这种循环是有一个阈值的。这样的好处就是在高并发情况下,能够很好的提高分布式锁的使用性能。

3.redisson实现的分布式锁是可重入的吗?

        redisson实现的分布式锁是可重入的,它的底层使用的是hash数据结构,记录的是线程的id和重入的次数。这个重入在内部就是判断是否是当前线程持有的锁,若是则重入次数+1,释放锁则-1,这样做是为了避免死锁的发生。

4.redisson实现的分布式锁能解决主从一致问题吗?

        不能解决,因为会有这种问题:线程1加锁成功后master节点宕机,线程2在新的master节点再次加锁,导致两个节点共持一把锁的问题。

        但是可以使用redisson提供的红锁来解决,但是这样的话,性能就太低了,如果业务中非要保证数据的强一致性,建议采用zookeeper实现的分布式锁

更多推荐