SETNX命令简介
SETNX key value返回(1:key的值被设置,0:key的值没被设置),将key的值设为value,并且仅当key不存在。- 锁的
key为目标数据的唯一键,value为锁的期望超时时间点; - 基于
Redis实现的分布式锁,主要基于redis的setnx(set if not exist)命令;
1. jedis实现分布式锁
1 | <dependency> |
1.1 实现示例:
1 | public static boolean correctGetLock(String lockKey, String requestId, int expireTime) { |
jedis.set(String key, String value, String nxxx, String expx, int time)
- **key**:保证唯一,用来当锁(redis记录的key)
- **value**:redis记录的value,目的是为了标志锁的所有者(竞争锁的客户端),保证解锁时只能解自己加的锁。requestId可以使用UUID.randomUUID().toString()方法生成
- **nxxx**:"NX"意思是SET IF NOT EXIST,即当key不存在时,我们进行set操作,若key已经存在,则不做任何操作
- **expx**:"PX"意思是要给这个key加一个过期的设置(单位毫秒),过期时间由第五个参数决定
- **time**:expx设置为"PX"时,redis key的过期时间
1.2 解锁示例:
1 | public boolean correctReleaseLock(String lockKey, String requestId) { |
eval命令执行Lua代码的时候,Lua代码将被当成一个命令去执行,并且直到eval命令执行完成,Redis才会执行其他命令,所以保证了检查和删除操作都是原子的。
1.3 这类琐最大的缺点
加锁时只作用在一个Redis节点上,即使Redis通过sentinel保证高可用,如果这个master节点由于某些原因发生了主从切换,那么就会出现锁丢失的情况:
- 在
Redis的master节点上拿到了锁; - 但是这个加锁的
key还没有同步到slave节点; master故障,发生故障转移,slave节点升级为master节点;- 导致锁丢失。
因此,
Redis作者antirez基于分布式环境下提出了一种更高级的分布式锁的实现方式:Redlock。基于Redis的Redisson实现了Redlock。
2. Redisson实现普通分布式锁
普通分布式实现非常简单,无论是那种架构,向Redis通过EVAL命令执行LUA脚本即可。
1 | <dependency> |
单机模式:
1 | // 构造redisson实现分布式锁必要的Config |
哨兵模式:
即Sentinel模式,实现代码和单机模式几乎一样,唯一的不同就是Config的构造:
1 | Config config = new Config(); |
集群模式:
即Cluster模式,集群模式构造Config如下:
1 | Config config = new Config(); |
3. Redisson实现Redlock分布式锁
3.1 Redlock算法大概原理:
- 在
Redis的分布式环境中,我们假设有N个Redis master。这些节点完全互相独立,不存在主从复制或者其他集群协调机制。我们确保将在N个实例上使用与在Redis单实例下相同方法获取和释放锁。 - 为了取到锁,客户端应该执行以下操作:
- 获取当前
Unix时间,以毫秒为单位。 - 依次尝试从
N个实例,使用相同的key和具有唯一性的value(例如UUID)获取锁。 - 客户端使用当前时间减去开始获取锁时间(步骤1记录的时间)就得到获取锁使用的时间。
- 当且仅当(N/2+1)的Redis节点都取到锁,并且使用的时间小于锁失效时间时,锁才算获取成功,例如3个节点至少需要
3/2+1=22个。 - 如果取到了锁,key的真正有效时间等于有效时间减去获取锁所使用的时间(步骤3计算的结果)。
- 若获取锁失败,客户端应该在所有的Redis实例上进行解锁(即便某些Redis实例根本就没有加锁成功)。
- 获取当前
3.2 使用Redlock
单机模式Redis为例:
1 | Config config = new Config(); |
最核心的变化就是 RedissonRedLock redLock=**new RedissonRedLock(lock1,lock2,lock3);**,因为我这里是以三个节点为例。
- 如果是主从
Redis架构、哨兵Redis架构、集群Redis架构实现Redlock,只需要改变上述config1、config2、config3为主从模式、哨兵模式、集群模式配置即可,但相应需要3个独立的Redis主从集群、3个Redis独立的哨兵集群、3个独立的Cluster集群。 - 以
sentinel模式架构为例,3个sentinel模式集群,如果要获取分布式锁,那么需要向这3个sentinel集群通过EVAL命令执行LUA脚本,需要3/2+1=2,即至少2个sentinel集群响应成功,才算成功的以Redlock算法获取到分布式锁。
4. Redlock问题合集
4.1 N个节点的理解
假设我们用N(>=3)个节点实现Redlock算法的分布式锁。不是一个有N个主节点的cluster集群;而是要么是N个redis单实例,要么是N个sentinel集群,要么是N个cluster集群。
4.2 失效时间如何设置
这个问题的场景是,假设设置失效时间10秒,如果由于某些原因导致10秒还没执行完任务,这时候锁自动失效,导致其他线程也会拿到分布式锁。
这确实是Redis分布式最大的问题,不管是普通分布式锁,还是Redlock算法分布式锁,都没有解决这个问题。也有一些文章提出了对失效时间续租,即延长失效时间,很明显这又提升了分布式锁的复杂度(没有现成的框架有实现)。
4.3 redis分布式锁的高可用
关于Redis分布式锁的安全性问题,在分布式系统专家Martin Kleppmann和Redis的作者Antirez之间已经发生过一场争论。有兴趣的同学,搜索”基于Redis的分布式锁到底安全吗”就能得到你想要的答案,需要注意的是,有上下两篇(这应该就是传说中的神仙打架吧)。
4.4 使用Zookeeper还是Redis实现分布式锁
没有绝对的好坏,只有更适合自己的业务。
就性能而言,Redis很明显优于Zookeeper;就分布式锁实现的健壮性(高可用)而言,Zookeeper很明显优于Redis。至于如何选择,还要看具体业务场景。
原文链接: http://chaooo.github.io/2019/04/08/redis-lock.html
版权声明: 转载请注明出处.