redis实现分布式锁(二)

大家好,我是沸才,上一篇文章,我给大家分享了redis快速实现分布式锁,但是还存在一些问题。

​分布式锁的优化 


1.在释放锁时需要去判断这个锁是不是自己的锁
大家想一下线程一去获取锁去执行业务,突然线程一阻塞了,超时释放锁了,线程二可以去获取锁,当这个情况下线程一苏醒了,执行完业务去释放锁,结果,线程二的锁被释放了造成线程安全问题​(执行流程如下图


那么我么们应该根据什么去判断呢?线程id?因为我们本来存入的就是value就是线程id,但是大家想一下,我们在集群模式下有多个jvm,每个jvm都有自己的线程一,线程二,这样会导致线程id一致啊,所以我们存入的value要保证唯一性 ,所以我们需要添加全局唯一id也就是唯一线程标识这个就是我们判断是不是自己的锁的依据,全局唯一标识保存在每个独立的服务器下,不同的服务器生成的全局唯一id肯定不一样

  • 以前业务流程

  • 现在业务流程

需要修改的有两处

  1. 添加线程唯一标识存入value中
/*
获取锁的方法
**/ 
public boolean tryLock(long timeoutSec) {
        //使用全局唯一生随机数生成器uuid,我这里使用的UUID,也可以用其他方法保证唯一性即可
        String LOCK_PREFIX = UUID.randomUUID() + "-";
         //获取线程表示,线程标示用于当做value,唯一性
        //以前是只存入线程id,现在是uuid(全局唯一id) + 线程id
        String threadid = LOCK_PREFIX + Thread.currentThread().getId();
        //获取锁
       Boolean success = stringRedisTemplate.opsForValue().setIfAbsent(KEY_PREFIX + name,threadid,timeoutSec, TimeUnit.SECONDS);
        return Boolean.TRUE.equals(success);
    }

2.释放锁之前需要判断一下是不是自己的锁

/**
     * 释放锁
     */
    public void unlock() {
         //判断reids存入的value和我们本服务器生成的全局唯一id(uuid + 线程id)是否相等
        //相等才能去释放锁
         if(LOCK_PREFIX + Thread.currentThread().getId() == stringRedisTemplate.opsForValue().get(KEY_PREFIX + name)){
             stringRedisTemplate.delete(KEY_PREFIX + name);
         }

2.使用lua脚本来保持原子性

好啦这样就解决了,误删锁的问题,你是不是以为就优化完成,完美无缺了?

当然NO!大家想一下,我判断锁和释放锁是并发操作,不是并行操作,有没有可能我判断完锁,准备释放时,线程突然又阻塞了,你说这怎么可能,判断完之后就释放锁了,中间有没有代码

还真有可能线程阻塞那就是极端情况下,java中jvm存在垃圾回收机制的阻塞现象(full gc)

这样就是造成线程停止,然后线程一超时释放锁了,线程二去获取锁执行业务,这时线程一又好了因为线程一已经执行了判断是不是自己的锁,所以不用去比较就直接开始去执行释放锁,好啦,这下好啦又造成线程并发安全问题,

所以我们需要引入lua脚本来保持判断锁和释放锁的原子性

lua脚本也是一种编程语言,但是我们只需要了解它的几个基本用法就可以了,并且idea还帮我们结合了lua脚本,可以在idea中编写lua脚本

lua脚本语法

1.lua脚本中调用redis函数

redis.call('命令名称','key','其他参数'...)
eg:#执行 set name jack
redis.call('set','name','jack')

2.写好lua脚本后指行这个命令


如果脚本中的key,value不想写死,可以作为参数传递。key类型参数会放入KEYS数组,其它参数会放入ARGV数组,在lua脚本中可以从KEYS和ARGV数组获取这些参数:

好啦lua脚本基本语法就是这些啦,如果看上面这些似懂非懂,那就去看官方文档,我们可要养成观看官方文档的好习惯,毕竟以后工作了遇到新的技术不一定都有视频讲解

在分布式锁中使用lua脚本

  1. 获取锁中的线程标识
  2. 判断是否与指定的标识一致
  3. 如果一致释放锁
  4. 如果不一致则什么都不做
-- 获取锁中的线程标识 get key
local id = redis.call('get',KEYS[1])
-- 比较线程标识与锁中的标识是否一致
if(id == ARGV[1]) then

    return redis.call('del',KEYS[1]);
end
-- 如果普配不上返回0
    return 0

在文件中写好lua脚本之后我们就开始在业务中调用了,要想在java中使用lua脚本只需使用下面语法

//调用lua脚本
        //第一步创建script
        DefaultRedisScript<Long> UNLOCK = new DefaultRedisScript<>("unlock.lua",Long.class);
        //第二步使用redis命令调用方法
        stringRedisTemplate.execute(UNLOCK,
                //传入keys,singletonList是把字符串编程list集合
                Collections.singletonList(KEY_PREFIX + name),
                //传入argv
                LOCK_PREFIX + Thread.currentThread().getId());

好啦关于redis实现分布式锁完结啦,如果觉得还不错的麻烦点赞+收藏❤❤❤我是沸才会持续更新有用的编程知识

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值