万网网站建设方案书,网站建设管理岗位职责,wordpress自适应主题制作,百度怎么发广告如何基于 Redis 实现一个最简易的分布式锁#xff1f;
不论是本地锁还是分布式锁#xff0c;核心都在于“互斥”。
在 Redis 中#xff0c; SETNX 命令是可以帮助我们实现互斥。SETNX 即 SET if Not eXists (对应 Java 中的 setIfAbsent 方法)#xff0c;如果 key 不存在…如何基于 Redis 实现一个最简易的分布式锁
不论是本地锁还是分布式锁核心都在于“互斥”。
在 Redis 中 SETNX 命令是可以帮助我们实现互斥。SETNX 即 SET if Not eXists (对应 Java 中的 setIfAbsent 方法)如果 key 不存在的话才会设置 key 的值。如果 key 已经存在SETNX 啥也不做。
SETNX lockKey uniqueValue
(integer) 1
SETNX lockKey uniqueValue
(integer) 0
释放锁的话直接通过 DEL命令删除对应的 key 即可。
DEL lockKey
(integer) 1
为了防止误删到其他的锁这里我们建议使用 Lua 脚本通过 key 对应的 value唯一值来判断。
选用 Lua 脚本是为了保证解锁操作的原子性。因为 Redis 在执行 Lua 脚本时可以以原子性的方式执行从而保证了锁释放操作的原子性。
// 释放锁时先比较锁对应的 value 值是否相等避免锁的误释放
if redis.call(get,KEYS[1]) ARGV[1] thenreturn redis.call(del,KEYS[1])
elsereturn 0
end 这是一种最简易的 Redis 分布式锁实现实现方式比较简单性能也很高效。不过这种方式实现分布式锁存在一些问题。就比如应用程序遇到一些问题比如释放锁的逻辑突然挂掉可能会导致锁无法被释放进而造成共享资源无法再被其他线程/进程访问。 为什么要给锁设置一个过期时间
为了避免锁无法被释放我们可以想到的一个解决办法就是给这个 key也就是锁 设置一个过期时间 。
127.0.0.1:6379 SET lockKey uniqueValue EX 3 NX
OK
lockKey加锁的锁名uniqueValue能够唯一标识锁的随机字符串NX只有当 lockKey 对应的 key 值不存在的时候才能 SET 成功EX过期时间设置秒为单位EX 3 标示这个锁有一个 3 秒的自动过期时间。与 EX 对应的是 PX毫秒为单位这两个都是过期时间设置。
一定要保证设置指定 key 的值和过期时间是一个原子操作 不然的话依然可能会出现锁无法被释放的问题。
这样确实可以解决问题不过这种解决办法同样存在漏洞如果操作共享资源的时间大于过期时间就会出现锁提前过期的问题进而导致分布式锁直接失效。如果锁的超时时间设置过长又会影响到性能。
你或许在想如果操作共享资源的操作还未完成锁过期时间能够自己续期就好了 如何实现锁的优雅续期
对于 Java 开发的小伙伴来说已经有了现成的解决方案 Redisson。 Redisson 是一个开源的 Java 语言 Redis 客户端提供了很多开箱即用的功能不仅仅包括多种分布式锁的实现。并且Redisson 还支持 Redis 单机、Redis Sentinel、Redis Cluster 等多种部署架构。
Redisson 中的分布式锁自带自动续期机制使用起来非常简单原理也比较简单其提供了一个专门用来监控和续期锁的 Watch Dog 看门狗如果操作共享资源的线程还未执行完成的话Watch Dog 会不断地延长锁的过期时间进而保证锁不会因为超时而被释放。 看门狗名字的由来于 getLockWatchdogTimeout() 方法这个方法返回的是看门狗给锁续期的过期时间默认为 30 秒。
//默认 30秒支持修改
private long lockWatchdogTimeout 30 * 1000;public Config setLockWatchdogTimeout(long lockWatchdogTimeout) {this.lockWatchdogTimeout lockWatchdogTimeout;return this;
}
public long getLockWatchdogTimeout() {return lockWatchdogTimeout;
}
renewExpiration() 方法包含了看门狗的主要逻辑
private void renewExpiration() {//......Timeout task commandExecutor.getConnectionManager().newTimeout(new TimerTask() {Overridepublic void run(Timeout timeout) throws Exception {//......// 异步续期基于 Lua 脚本CompletionStageBoolean future renewExpirationAsync(threadId);future.whenComplete((res, e) - {if (e ! null) {// 无法续期log.error(Cant update lock getRawName() expiration, e);EXPIRATION_RENEWAL_MAP.remove(getEntryName());return;}if (res) {// 递归调用实现续期renewExpiration();} else {// 取消续期cancelExpirationRenewal(null);}});}// 延迟 internalLockLeaseTime/3默认 10s也就是 30/3 再调用}, internalLockLeaseTime / 3, TimeUnit.MILLISECONDS);ee.setTimeout(task);}
默认情况下每过 10 秒看门狗就会执行续期操作将锁的超时时间设置为 30 秒。看门狗续期前也会先判断是否需要执行续期操作需要才会执行续期否则取消续期操作。
Watch Dog 通过调用 renewExpirationAsync() 方法实现锁的异步续期
protected CompletionStageBoolean renewExpirationAsync(long threadId) {return evalWriteAsync(getRawName(), LongCodec.INSTANCE, RedisCommands.EVAL_BOOLEAN,// 判断是否为持锁线程如果是就执行续期操作就锁的过期时间设置为 30s默认if (redis.call(hexists, KEYS[1], ARGV[2]) 1) then redis.call(pexpire, KEYS[1], ARGV[1]); return 1; end; return 0;,Collections.singletonList(getRawName()),internalLockLeaseTime, getLockName(threadId));
}
可以看出 renewExpirationAsync 方法其实是调用 Lua 脚本实现的续期这样做主要是为了保证续期操作的原子性。
我这里以 Redisson 的分布式可重入锁 RLock 为例来说明如何使用 Redisson 实现分布式锁
// 1.获取指定的分布式锁对象
RLock lock redisson.getLock(lock);
// 2.拿锁且不设置锁超时时间具备 Watch Dog 自动续期机制
lock.lock();
// 3.执行业务
...
// 4.释放锁
lock.unlock();
只有未指定锁超时时间才会使用到 Watch Dog 自动续期机制。
// 手动给锁设置过期时间不具备 Watch Dog 自动续期机制
lock.lock(10, TimeUnit.SECONDS);
如果使用 Redis 来实现分布式锁的话还是比较推荐直接基于 Redisson 来做的。 如何实现可重入锁
所谓可重入锁指的是在一个线程中可以多次获取同一把锁比如一个线程在执行一个带锁的方法该方法中又调用了另一个需要相同锁的方法则该线程可以直接执行调用的方法即可重入 而无需重新获得锁。像 Java 中的 synchronized 和 ReentrantLock 都属于可重入锁。
不可重入的分布式锁基本可以满足绝大部分业务场景了一些特殊的场景可能会需要使用可重入的分布式锁。
可重入分布式锁的实现核心思路是线程在获取锁的时候判断是否为自己的锁如果是的话就不用再重新获取了。为此我们可以为每个锁关联一个可重入计数器和一个占有它的线程。当可重入计数器大于 0 时则锁被占有需要判断占有该锁的线程和请求获取锁的线程是否为同一个。 Redis 如何解决集群情况下分布式锁的可靠性
为了避免单点故障生产环境下的 Redis 服务通常是集群化部署的。
Redis 集群下上面介绍到的分布式锁的实现会存在一些问题。由于 Redis 集群数据同步到各个节点时是异步的如果在 Redis 主节点获取到锁后在没有同步到其他节点时Redis 主节点宕机了此时新的 Redis 主节点依然可以获取锁所以多个应用服务就可以同时获取到锁。 针对这个问题Redis 之父 antirez 设计了 Redlock 算法 来解决。
Redlock 算法的思想是让客户端向 Redis 集群中的多个独立的 Redis 实例依次请求申请加锁如果客户端能够和半数以上的实例成功地完成加锁操作那么我们就认为客户端成功地获得分布式锁否则加锁失败。
即使部分 Redis 节点出现问题只要保证 Redis 集群中有半数以上的 Redis 节点可用分布式锁服务就是正常的。
Redlock 是直接操作 Redis 节点的并不是通过 Redis 集群操作的这样才可以避免 Redis 集群主从切换导致的锁丢失问题。
Redlock 实现比较复杂性能比较差发生时钟变迁的情况下还存在安全性隐患。
实际项目中不建议使用 Redlock 算法成本和收益不成正比可以考虑基于 Redis 主从复制哨兵模式实现分布式锁。