分布式锁解决方案之Reids实现分布式锁,完成秒杀功能

前言

当数据量大,并发量高时,我们需要使用分布式锁,比如互联网秒杀商品,抢优惠卷、接口幂等性校验等。
分布式锁-Redisson

一、分布式锁有哪些解决方案?

1.1 基于Reids实现分布式锁

① setnx key value
②Redisson

1.2 基于Zookeeper实现分布式锁

主要是利用顺序节点或临时节点。
临时节点:我的客户端和zookeeper建立连接之后,这个节点一直生效,那么别人来建立连接,这个连接已经有了,必须等我断开连接。当客户端和zookeeper的连接断开了之后,这个节点就自动消失了。
顺序节点:A用户来和Zookeeper建立连接,就是A节点;B用户来,就是B节点。A节点拿到了锁,B节点要等A节点执行完业务释放锁之后才能拿到分布式锁。

1.3 基于数据库实现分布式锁

比如Mysql,利用主键或唯一索引的唯一性。

二、Reids实现之setnx key value 10s

2.1原理

setnx的作用是存入一个不存在的字符串键值对。即:如果原来有这个key,就存入失败。
当有多个用户来获取这个锁,可以保证锁的互斥性,当我拿到这个锁了之后,别人就拿不到这个锁。

Boolean result = stringRedisTemplate.opsForValue().setIfAbsent(lockKey, clientId);//setnx key value

2.2 可能会出现的问题和解决方案

1)分布式锁失效的问题
我们设置一个锁,执行完业务逻辑,使用完之后,一定要释放锁,不然别人拿不到这个锁。

场景一:我执行完业务逻辑,还没执行到释放锁的时候,程序挂了,那么这个锁就一直存在redis,别人就拿不到这个锁。
解决方案:我们就要给key设置过期时间,一般设置30s,30s之后就过期。这样别人就可以拿到这个锁了。

  stringRedisTemplate.expire(lockKey,30,TimeUnit.SECONDS);//expire key 30s

2)过期时间问题
场景二:使用expire key 30s来设置时间是不行的,如果程序在设置锁之后和加过期时间之前挂了,这个过期时间就没设置上。
解决方案:我们要把setnx和expire key做成原子性的。

    Boolean result = stringRedisTemplate.opsForValue().setIfAbsent(lockKey, clientId, 30, TimeUnit.SECONDS);//setnx key value ex 30 s

3)删除锁问题
场景三:删除锁的时候需要判断是不是我的锁,首先getkey的value是唯一的,用uuid来做就可以了。

场景四:设置锁的过期时间是30s,我要执行35s,在程序的最后5s内,别人也成功设置了一个锁。然后我在第5s就把别人的锁删除了,这样删除的锁就是不是我拿到的那个锁了。

场景五:我设置的key过期时间是30s,结果我程序执行了35s,程序还没执行完,锁就释放了,别人就可以拿到锁。那么两个程序都进入到了同一个方法中,会导致数据不一致的问题,可能会超卖。
解决方案:看门狗机制,watch dog :设置key的过期时间是30s,每10s判断key在不在,如果在就续时长到30s。
这样就可以防止,我的程序还没执行完而我的key就过期了,导致别人拿到我的锁。

    @Autowired
    private StringRedisTemplate stringRedisTemplate;

    public String deductStock() {
        String lockKey = "lockKey";
        String clientId = UUID.randomUUID().toString();
        Boolean result = stringRedisTemplate.opsForValue().setIfAbsent(lockKey, clientId, 30, TimeUnit.SECONDS);
  
        if (!result) {
            return "error";//没有拿到锁就返回
        }
        try {
            int stock = Integer.parseInt(stringRedisTemplate.opsForValue().get("stock"));
            if (stock > 0){
                int realStock = stock - 1;
                stringRedisTemplate.opsForValue().set("stock",realStock+"");
                System.out.println("扣减商品库存成功,剩余库存"+realStock);
            } else {
                System.out.println("扣减商品库存失败,库存不足");
            }
        }finally {
            if (clientId.equals(stringRedisTemplate.opsForValue().get(lockKey))) {
                stringRedisTemplate.delete(lockKey);
            }

        }
        return "end";
    }

三、Reids实现之Redisson

Redisson是Redis官方推荐的Java版的Redis客户端。它主要应用于分布式场景,此处我们只用它的分布式锁功能。
我们主要是利用lock和unlock。
在这里插入图片描述

    @Autowired
    private Redisson redisson;

    public String deductStock(){
        String lockKey = "lockKey";
        RLock redissonLock = redisson.getLock(lockKey);//拿到锁的对象
        try {
            redissonLock.lock();//加锁
            int stock = Integer.parseInt(stringRedisTemplate.opsForValue().get("stock"));//拿库存
            if (stock > 0){
                int realStock = stock - 1;
                stringRedisTemplate.opsForValue().set("stock",realStock+"");
                System.out.println("扣减商品库存成功,剩余库存"+realStock);
            } else {
                System.out.println("扣减商品库存失败,库存不足");
            }
        }finally {
            redissonLock.unlock();//释放锁
        }
        return "end";
    }
  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

爱喝皮蛋瘦肉粥的小饶

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值