Redis分布式锁

1 篇文章 0 订阅

一 前言:

在同一个JVM中多个线程争抢同一个资源时可以使用JUC提供的一些锁或者JDK自带的Lock,synchronized关键字等解决并发多线程问题. 但是在多JVM情况下,这些东东都无力回天啦!  这个时候是不是想到要用分布式锁来解决问题了.

二 简介:

分布式锁一般分为以下三类

  1. 基于数据库原子性做分布式锁
  2. 基于redis做分布式锁
  3. 基于zookeeper做分布式锁

三 多线程例子

假设我们要多个线程扣减库存时 --->咋们起始的代码 也算是咋们的业务逻辑:

@RequestMapping(value = "/stock_deduct")
    public String deductStockDeduct() {
        String bestpayLockKey = "bestpayLock";
            //库存数量
            int stock = Integer.parseInt(stringRedisTemplate.opsForValue().get("bestpay"));
            log.info("redisStock: {}", stock);
            if (stock > 0) {
                //如果库存大于0则进行减1操作
                int realStock = stock - 1;
                stringRedisTemplate.opsForValue().set("bestpay", realStock + "");
                log.info("扣减成功,剩余库存:{}", realStock);
            } else {
                log.info("扣减失败,库存不足!");
            }
        return "success";
    }

这时我们库存设为50 并用JMeter压测200个线程并发时,发现库存还有45个,这时并发线程安全的问题就来了.

为啥? --->主要原因我们redis处理速度太快了,导致没有锁的情况下很多进程争抢一个资源时出现无序混乱的情况.

 

四  基于MYSQL实现分布式锁

引荐:<<基于mysql实现分布式锁>>

基于mysql数据库实现分布式锁主要有两种方式:一种是基于数据库表实现的乐观锁和悲观锁,另一种是基于Mysql自带的悲观锁;

Mysql实现分布式悲观锁:直接创建一张锁表,然后通过操作该表中的数据来实现了。当我们要锁住某个方法或资源时,我们就在该表中增加一条记录,想要释放锁的时候就删除这条记录。

利用for update加显式的行锁,这样就能利用这个行级的排他锁来实现分布式锁了,同时unlock的时候只要释放commit这个事务,就能达到释放锁的目的。

五 基于ZK实现分布式锁

引荐:<<基于zookeeper实现分布式锁>> <<基于zookeeper实现分布式锁方案>>

六 基于Redis实现分布式锁

redis命令大全 中有get,set,setnx,del,getset等方法.

回顾下我们的起始业务代码,

咋办?--->奥 那我们加把锁呗. 我们在方法上加上synchronized关键字, 诶 发现完全可以解决刚刚的并发问题没有超卖,少卖等情况.

synchronized作用于静态方法和非静态方法的区别: 

 非静态方法:

 *  给对象加锁(可以理解为给这个对象的内存上锁,注意 只是这块内存,其他同类对象都会有各自的内存锁),这时候

 * 在其他一个以上线程中执行该对象的这个同步方法(注意:是该对象)就会产生互斥

 *  静态方法: 

 * 相当于在类上加锁(*.class 位于代码区,静态方法位于静态区域,这个类产生的对象公用这个静态方法,所以这块

 * 内存,N个对象来竞争), 这时候,只要是这个类产生的对象,在调用这个静态方法时都会产生互斥


接下来小编制造麻烦了,在本地搭建两台Tomcat端口分别是8081,8082 用nginx负载均衡 分发到这两台机器上.

在conf文件nginx.conf中http中增加配置:使其redislock域名会均匀分发到两台Tomcat上.

1.重启命令nginx -s reload     2.停止命令nginx -s stop       3.启动命令start nginx

然后发现synchronized加了也没用.还是有并发问题.

咋办--->这时分布式锁就派上用场了!

public String deductStockDeduct() {
        String bestpayLockKey = "bestpayLock";
        try {
            final Boolean getLock = stringRedisTemplate.opsForValue().setIfAbsent(bestpayLockKey, "bestpay");
            if (!getLock) {
                return "end";
            } else {
                int stock = Integer.parseInt(stringRedisTemplate.opsForValue().get("bestpay"));
                log.info("redisStock: {}", stock);
                if (stock > 0) {
                    int realStock = stock - 1;
                    stringRedisTemplate.opsForValue().set("bestpay", realStock + "");
                    log.info("扣减成功,剩余库存:{}", realStock);
                } else {
                    log.info("扣减失败,库存不足!");
                }
            }
        } catch (NumberFormatException e) {
            log.error("getLock error------");
        } finally {
            stringRedisTemplate.delete(bestpayLockKey);
        }
        return "success";
    }

改为上面代码后发现没有超卖情况了, 但仔细想想在实际业务情况下是不是有很多问题.. 他把没拿到锁的线程直接抛给上帝了...

咋办---> 如果我们没拿到锁就一直循环去获取锁

 while (true) {
                getLock = stringRedisTemplate.opsForValue().setIfAbsent(bestpayLockKey, "bestpay");
                if (getLock) {
                    stringRedisTemplate.expire(bestpayLockKey,10,TimeUnit.SECONDS);
                    break;
                }
            }

发现业务逻辑已经可以实现了. 但是发现性能很差, 有什么可改进的地方吗?

发现while循环导致没获取到锁的会一直尝试获取锁,资源消耗严重 我们直接让没获取到锁的线程休眠2ms左右,发现瞬间爽了很多.

 

 

其实2.6以上的jedis其实可以直接用.set方法,原子执行set操作. 以下可以考虑lua脚本执行

可以看到,我们加锁就一行代码:jedis.set(String key, String value, String nxxx, String expx, int time),这个set()方法一共有五个形参:

  • 第一个为key,我们使用key来当锁,因为key是唯一的。

  • 第二个为value,我们传的是requestId,很多童鞋可能不明白,有key作为锁不就够了吗,为什么还要用到value?原因就是我们在上面讲到可靠性时,分布式锁要满足第四个条件解铃还须系铃人,通过给value赋值为requestId,我们就知道这把锁是哪个请求加的了,在解锁的时候就可以有依据。requestId可以使用UUID.randomUUID().toString()方法生成。

  • 第三个为nxxx,这个参数我们填的是NX,意思是SET IF NOT EXIST,即当key不存在时,我们进行set操作;若key已经存在,则不做任何操作;

  • 第四个为expx,这个参数我们传的是PX,意思是我们要给这个key加一个过期的设置,具体时间由第五个参数决定。

  • 第五个为time,与第四个参数相呼应,代表key的过期时间

 


干活满满..有问题大家一起探讨 qq260179415 ,后面大家想想可能还会出现什么不可预估的情况.. (lua脚本,redission 等)

现在看起来想要实现非常完美的redis分布式锁是不是不是很难!

本文主体逻辑图:

 

Demo下载:https://download.csdn.net/download/qq_28953809/11144179

 


作者简介:就职于甜橙金融信息技术部,负责java后端开发工作,喜欢研究新的技术服务于业务需求,保证服务的高并发,高可用,高性能.

 

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值