先把结论抛出来:redis无法正确实现分布式锁!即使是redis单节点也不行!redis的所谓分布式锁无法用在对锁要求严格的场景下,比如:同一个时间点只能有一个客户端获取锁。

首先来看下单节点下一般redis分布式锁的实现,其实就是个set:

加锁:

 /**
* 尝试获取分布式锁
* @param jedis Redis客户端
* @param lockKey 锁
* @param requestId 请求标识
* @param expireTime 超期时间
* @return 是否获取成功
*/
public static boolean tryGetDistributedLock(Jedis jedis, String lockKey, String requestId, int expireTime) {
String result = jedis.set(lockKey, requestId, SET_IF_NOT_EXIST, SET_WITH_EXPIRE_TIME, expireTime);
if (LOCK_SUCCESS.equals(result)) {
return true;
}
return false;
}
 

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

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

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

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

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

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

解锁:

/**
* 释放分布式锁
* @param jedis Redis客户端
* @param lockKey 锁
* @param requestId 请求标识
* @return 是否释放成功
*/
public static boolean releaseDistributedLock(Jedis jedis, String lockKey, String requestId) {
String script = "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end";
Object result = jedis.eval(script, Collections.singletonList(lockKey), Collections.singletonList(requestId));
if (RELEASE_SUCCESS.equals(result)) {
return true;
}
return false;
}

解锁也很简单,只需要两行代码就搞定了!第一行代码,我们写了一个简单的Lua脚本代码,第二行代码,我们将Lua代码传到jedis.eval()方法里,并使参数KEYS[1]赋值为lockKey,ARGV[1]赋值为requestId。eval()方法是将Lua代码交给Redis服务端执行,首先获取锁对应的value值,检查是否与requestId相等,如果相等则删除锁(解锁)。使用Lua语言主要是确保上述操作是原子性的。

看上去似乎是完美无瑕的一种分布式锁的实现方式,我们重新看下加锁的代码:

public static boolean tryGetDistributedLock(Jedis jedis, String lockKey, String requestId, int expireTime) {
String result = jedis.set(lockKey, requestId, SET_IF_NOT_EXIST, SET_WITH_EXPIRE_TIME, expireTime);
if (LOCK_SUCCESS.equals(result)) {
return true;
}
return false;
}

场景1:

线程1在执行set的时候,redis服务端已经执行成功,但是因为网络原因,响应还没有返回给客户端,过了expireTime时间以后,响应终于回来了,对于线程1来说,它是拿到了分布式锁的,但是注意,此时的锁已经是失效的了!如果此时又来个线程2申请加锁,显然也能获取锁,因为线程1的锁已经失效了,此时就会有2个线程同时获取锁!

场景2:

线程1执行完set以后,redis服务端执行成功,在执行if的时候,jvm发生了FullGC,应用暂停,超过了expireTime以后,GC完成,程序继续执行,此时线程1仍然认为自己是持有锁的,实际上锁已经过期了!如果此时线程2又来申请加锁,成功,此时线程2也获得了锁,因此也会出现2个线程同时执行被锁保护的代码的情况!

综上,可以看出来,就算是在单节点情况下,redis也是无法实现严格意义上的分布式锁的!

如果想要实现严格意义上的分布式锁呢?最常用的就是zookeeper了。我们来看下zookeeper为啥可以实现分布式锁。

zookeeper实现分布式锁的步骤:

假设锁空间的根节点为/lock:

(1)客户端连接zookeeper,并在/lock下创建临时的且有序的子节点,第一个客户端对应的子节点为/lock/lock-0000000000,第二个为/lock/lock-0000000001,以此类推。

(2)客户端获取/lock下的子节点列表,判断自己创建的子节点是否为当前子节点列表中序号最小的子节点,如果是则认为获得锁,否则监听/lock的子节点变更消息,获得子节点变更通知后重复此步骤直至获得锁;

(3)执行业务代码;

(4)完成业务流程后,删除对应的子节点释放锁。

上面的步骤可以看出来,zookeeper跟redis不一样,它是完全不依赖客户端的状态的,因此zookeeper才可以严格实现分布式锁!

redis的分布式锁是不是就一无是处了呢?当然不是!在一些要求不是那么严格的场景下还是可以使用的,比如:凌晨1点执行定时任务出报表,哪怕是执行2次也没什么问题。

参考文献:

https://www.cnblogs.com/linjiqin/p/8003838.html

http://zhangtielei.com/posts/blog-redlock-reasoning.html

https://blog.csdn.net/qiangcuo6087/article/details/79067136

如何用redis正确实现分布式锁?的更多相关文章

  1. 如何用 redis 造一把分布式锁

    基本概念 锁 wiki:In computer science, a lock or mutex (from mutual exclusion) is a synchronization mechan ...

  2. redis 正确实现分布式锁的正确方式

    前言 最近在自己所管理的项目中,发现redis加锁的方式不对,在高并发的情况有问题.故在网上找搜索了一把相关资料.发现好多都是互相抄袭的,很多都是有缺陷的.好多还在用redis 的 setnx命令来实 ...

  3. 基于redis实现的分布式锁

    基于redis实现的分布式锁 我们知道,在多线程环境中,锁是实现共享资源互斥访问的重要机制,以保证任何时刻只有一个线程在访问共享资源.锁的基本原理是:用一个状态值表示锁,对锁的占用和释放通过状态值来标 ...

  4. 一个Redis实现的分布式锁

    import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.data.redis.conne ...

  5. 基于Redis的简单分布式锁的原理

    参考资料:https://redis.io/commands/setnx 加锁是为了解决多线程的资源共享问题.Java中,单机环境的锁可以用synchronized和Lock,其他语言也都应该有自己的 ...

  6. redis客户端、分布式锁及数据一致性

    Redis Java客户端有很多的开源产品比如Redission.Jedis.lettuce等. Jedis是Redis的Java实现的客户端,其API提供了比较全面的Redis命令的支持:Redis ...

  7. Redis系列(二)--分布式锁、分布式ID简单实现及思路

    分布式锁: Redis可以实现分布式锁,只是讨论Redis的实现思路,而真的实现分布式锁,Zookeeper更加可靠 为什么使用分布式锁: 单机环境下只存在多线程,通过同步操作就可以实现对并发环境的安 ...

  8. 在redis上实现分布式锁

    /** *在redis上实现分布式锁 */ class RedisLock { private $redisString; private $lockedNames = []; public func ...

  9. redis系列:分布式锁

    redis系列:分布式锁 1 介绍 这篇博文讲介绍如何一步步构建一个基于Redis的分布式锁.会从最原始的版本开始,然后根据问题进行调整,最后完成一个较为合理的分布式锁. 本篇文章会将分布式锁的实现分 ...

随机推荐

  1. Docker环境安装部署Java应用(含安装Tomcat和JDK)

    1.部署思路 两台docker机(centos 7系统),Docker 版本:18.09.6, build 481bc77156 Docker host IP:192.168.102.135 Dock ...

  2. nmon服务器监控工具的使用安装

    nmon是一个监控服务器性能的工具 目录 1.安装nmon 2.数据采集 1.安装nmon nmon是一种linux服务器性能监控工具,他还提供了很好的图表结果展示功能.本篇以centos6.5系统为 ...

  3. cento7忘记root密码怎么办

    1.首先开启系统,一直按 e 键 进入编辑选项 2.光标下移,在UTF-8行这一段修改两处,首相找到ro改为rw,即只读改为可读写权限:然后在这段的尾部加入 init=/bin/sh 3.此时按住Ct ...

  4. Android的消息机制之ThreadLocal的工作原理

    ThreadLocal 可以把一个对象保存在指定的线程中,对象保存后,只能在指定线程中获取保存的数据,对于其他线程来说则无法获取到数据. 日常开发中 ThreadLocal 使用的地方比较少,但是系统 ...

  5. java:Review(J2ee)

    1.oracle: 1.1 增:insert into 删:delete from 改:update tablename set 查:select * from 1.2 聚合函数 max,min,av ...

  6. 自己实现一个list比较器 实现Comparator()接口

    一:一个实体类 成员变量有名字,年龄,分数 )))))); List<User> list = new ArrayList<>(); list.add(user1); list ...

  7. cocos2dx基础篇(22) 基本动画CCAnimation/CCAnimate

    [小知识] CCSpriteFrame     :精灵帧.    它是相对动画而产生的,其实就是一张纹理图片. CCAnimationFrame  :动画帧.    由精灵帧与间隔帧数组成,是动画CC ...

  8. 【VS开发】【智能语音处理】MATLAB 与 音频处理 相关内容摘记

    MATLAB 与 音频处理 相关内容摘记 MATLAB 与 音频处理 相关内容摘记 1 MATLAB 音频相关函数 1 MATLAB 处理音频信号的流程 2 音量标准化 2 声道分离合并与组合 3 数 ...

  9. Linux中安装配置KVM虚拟化

    KVM 概述: KVM 即 Kernel-based Virtual Machine 基于内核的虚拟机. KVM,是一个开源的系统虚拟化模块,自 Linux 2.6.20 之后集成在 Linux 的各 ...

  10. hbase的读写过程

    hbase的读写过程: hbase的架构: Hbase真实数据hbase真实数据存储在hdfs上,通过配置文件的hbase.rootdir属性可知,文件在/user/hbase/下hdfs dfs - ...