栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 软件开发 > 后端开发 > Java

Redis 实现分布式锁

Java 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

Redis 实现分布式锁

什么是分布式锁已经在之前的文章介绍过了,不明白的同学请回头阅读。这篇文章开始介绍分布式锁的具体实现。实现方式有很多,基于 redis 第一可以使用 redis 的 setnx 命令去手动封装,第二种方式可以通过开源的 redisson。

今天介绍最基本的通过 setnx命令来实现。首先了解下 setnx 命令:

SETNX

set if not exist,如果 key 不存在,设置指定的值并返回 1,否则不做任何操作返回 0,整个过程是原子操作。

通过该命令来实现锁时需要考虑多线程并发带来的异常情况,比如上一次程序中断导致锁没有释放、死锁问题。

思路

锁未释放问题,可以通过过期时间来解决,执行 setnx 命令时将 key 设置为 timestamp:

SETNX lock.foo 

获取锁失败时,通过 GET 命令获取 timestamp 如果小于等于当前时间,则释放锁。这里注意,不能 仅仅调用 DEL 命令释放,然后再调用 SETNX 获取,因为 DEL 和 SETNX 是两个命令,非原子操作,会存在多线程并发问题,比如:

假设客户端 c1 获取了锁后,执行了一系列业务操作后没来得及释放锁就宕机了,c2、c3 接下来获取锁:

c2 执行 SETNX 失败,开始调用 GET 发现已经过期,刚好 c3 也执行到此,c2、c3 接下来开始分别执行 DEL 和 SETNX 命令,最后同时都成功获取到了锁!这个问题的解决也很简单,就是使用一个 GETSET 命令 替换 DEL 和 SETNX 两个命令,从而保证无并发问题。

代码实现

基于以上讨论,获取锁的代码整理如下:

    public boolean lock(String lockKey,String timeStamp,long time){
        // setnx 成功
        if(stringRedisTemplate.opsForValue().setIfAbsent(lockKey, timeStamp)){
           // 给锁加个过期时间,如果被执行了,锁到期后自动释放
            stringRedisTemplate.expire (lockKey,time,TimeUnit.MILLISECONDS);
            return true;
        }

        //setnx 失败,获取锁失败
        // 判断锁超时 
        String currentLock = (String) stringRedisTemplate.opsForValue().get(lockKey);
        // 如果锁过期
        if(!StringUtils.isEmpty(currentLock) && Long.parseLong(currentLock) < System.currentTimeMillis()){
            // GETSET 上新锁,并返回旧的锁
            String preLock = (String) stringRedisTemplate.opsForValue().getAndSet(lockKey, timeStamp);

            // 二次检查,如果刚好两个线程都执行了 GETSET , 只有 GETSET 前后是同一把锁的才算获取锁成功,类似于 CAS 的思想!!!
            if(!StringUtils.isEmpty(preLock) && preLock.equals(currentLock)){
                stringRedisTemplate.expire (lockKey,time,TimeUnit.MILLISECONDS);
                return true;
            }
        }
        return false;
    }

释放锁的时候,也要检查时间戳:

    public boolean release(String lockKey,String timeStamp){
        try {
            String currentValue = (String) stringRedisTemplate.opsForValue().get(lockKey);
            if(!StringUtils.isEmpty(currentValue) && currentValue.equals(timeStamp) ){
                // 删除锁状态
                return stringRedisTemplate.opsForValue().getOperations().delete(lockKey);
            }
        } catch (Exception e) {
            log.error ( "锁释放异常,{}",e );
            return false;
        }
    }

需要注意的事,锁的时间应该设置足够长,因为业务代码可能会比较耗时,尤其是涉及到了数据库或者 IO 操作时。


如果觉得还不错的话,关注、分享、在看, 原创不易,且看且珍惜~

转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/778334.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号