当前位置:网站首页>可靠的分布式锁 RedLock 与 redisson 的实现
可靠的分布式锁 RedLock 与 redisson 的实现
2022-06-27 16:55:00 【用户3147702】
1. 引言
此前的文章中,我们详细介绍了基于 redis 的分布式事务锁的实现:
我们看到,分布式锁是如何一步步改进方案最终实现其高可用的。
但就“高可用”来说,似乎仍然有所欠缺,那就是如果他所依赖的 redis 是单点的,如果发生故障,则整个业务的分布式锁都将无法使用,即便是我们将单点的 redis 升级为 redis 主从模式或集群,对于固定的 key 来说,master 节点仍然是独立存在的,由于存在着主从同步的时间间隔,如果在这期间 master 节点发生故障,slaver 节点被选举为 master 节点,那么,master 节点上存储的分布式锁信息可能就会丢失,从而造成竞争条件。
那么,如何避免这种情况呢?
redis 官方给出了基于多个 redis 集群部署的高可用分布式锁解决方案 — RedLock,本文我们就来详细介绍一下。
2. RedLock 的加解锁过程
基于上述理论,我们知道,RedLock 是在多个 Redis 集群上部署的一种分布式锁的实现方式,他有效避免了单点问题。
假设我们有 N 个 Redis 服务或集群,RedLock 的加锁过程就如下所示:
- client 获取当前毫秒级时间戳,并设置超时时间 TTL
- 依次向 N 个 Redis 服务发出请求,用能够保证全局唯一的 value 申请锁 key
- 如果从 N/2+1 个 redis 服务中都获取锁成功,那么,本次分布式锁的获取被视为成功,否则视为获取锁失败。
- 如果获取锁失败,或执行达到 TTL,则向所有 Redis 服务都发出解锁请求。
3. Java 实现 — redisson
java 语言中,redisson 包实现了对 redlock 的封装,主要是通过 redis client 与 lua 脚本实现的,之所以使用 lua 脚本,是为了实现加解锁校验与执行的事务性。
此前主页君对 redis 结合 lua 实现严格的事务有过一篇文章来介绍,可以参考:
3.1 唯一 ID 的生成
分布式事务锁中,为了能够让作为中心节点的存储节点获悉锁的持有者,从而避免锁被非持有者误解锁,每个发起请求的 client 节点都必须具有全局唯一的 id。
通常我们是使用 UUID 来作为这个唯一 id,redisson 也是这样实现的,在此基础上,redisson 还加入了 threadid 避免了多个线程反复获取 UUID 的性能损耗:
protected final UUID id = UUID.randomUUID();
String getLockName(long threadId) {
return id + ":" + threadId;
}3.2 加锁逻辑
redisson 加锁的核心代码非常容易理解,通过传入 TTL 与唯一 id,实现一段时间的加锁请求。
下面是可重入锁的实现逻辑:
<T> RFuture<T> tryLockInnerAsync(long leaseTime, TimeUnit unit, long threadId, RedisStrictCommand<T> command) {
internalLockLeaseTime = unit.toMillis(leaseTime);
// 获取锁时向5个redis实例发送的命令
return commandExecutor.evalWriteAsync(getName(), LongCodec.INSTANCE, command,
// 校验分布式锁的KEY是否已存在,如果不存在,那么执行hset命令(hset REDLOCK_KEY uuid+threadId 1),并通过pexpire设置失效时间(也是锁的租约时间)
"if (redis.call('exists', KEYS[1]) == 0) then " +
"redis.call('hset', KEYS[1], ARGV[2], 1); " +
"redis.call('pexpire', KEYS[1], ARGV[1]); " +
"return nil; " +
"end; " +
// 如果分布式锁的KEY已存在,则校验唯一 id,如果唯一 id 匹配,表示是当前线程持有的锁,那么重入次数加1,并且设置失效时间
"if (redis.call('hexists', KEYS[1], ARGV[2]) == 1) then " +
"redis.call('hincrby', KEYS[1], ARGV[2], 1); " +
"redis.call('pexpire', KEYS[1], ARGV[1]); " +
"return nil; " +
"end; " +
// 获取分布式锁的KEY的失效时间毫秒数
"return redis.call('pttl', KEYS[1]);",
// KEYS[1] 对应分布式锁的 key;ARGV[1] 对应 TTL;ARGV[2] 对应唯一 id
Collections.<Object>singletonList(getName()), internalLockLeaseTime, getLockName(threadId));
}3.3 释放锁逻辑
protected RFuture<Boolean> unlockInnerAsync(long threadId) {
// 向5个redis实例都执行如下命令
return commandExecutor.evalWriteAsync(getName(), LongCodec.INSTANCE, RedisCommands.EVAL_BOOLEAN,
// 如果分布式锁 KEY 不存在,那么向 channel 发布一条消息
"if (redis.call('exists', KEYS[1]) == 0) then " +
"redis.call('publish', KEYS[2], ARGV[1]); " +
"return 1; " +
"end;" +
// 如果分布式锁存在,但是唯一 id 不匹配,表示锁已经被占用
"if (redis.call('hexists', KEYS[1], ARGV[3]) == 0) then " +
"return nil;" +
"end; " +
// 如果就是当前线程占有分布式锁,那么将重入次数减 1
"local counter = redis.call('hincrby', KEYS[1], ARGV[3], -1); " +
// 重入次数减1后的值如果大于0,表示分布式锁有重入过,那么只设置失效时间,不删除
"if (counter > 0) then " +
"redis.call('pexpire', KEYS[1], ARGV[2]); " +
"return 0; " +
"else " +
// 重入次数减1后的值如果为0,则删除锁,并发布解锁消息
"redis.call('del', KEYS[1]); " +
"redis.call('publish', KEYS[2], ARGV[1]); " +
"return 1; "+
"end; " +
"return nil;",
// KEYS[1] 表示锁的 key,KEYS[2] 表示 channel name,ARGV[1] 表示解锁消息,ARGV[2] 表示 TTL,ARGV[3] 表示唯一 id
Arrays.<Object>asList(getName(), getChannelName()), LockPubSub.unlockMessage, internalLockLeaseTime, getLockName(threadId));
}4. redisson RedLock 的使用
redisson 实现的分布式锁的使用非常简单:
Config config = new Config();
config.useSentinelServers().addSentinelAddress("127.0.0.1:6369","127.0.0.1:6379", "127.0.0.1:6389")
.setMasterName("masterName")
.setPassword("password").setDatabase(0);
RedissonClient redissonClient = Redisson.create(config);
RLock redLock = redissonClient.getLock("REDLOCK_KEY");
boolean isLock;
try {
isLock = redLock.tryLock(500, 10000, TimeUnit.MILLISECONDS);
if (isLock) {
//TODO if get lock success, do something;
}
} catch (Exception e) {
} finally {
redLock.unlock();
}可以看到,由于 redisson 包的实现中,通过 lua 脚本校验了解锁时的 client 身份,所以我们无需再在 finally 中去判断是否加锁成功,也无需做额外的身份校验,可以说已经达到开箱即用的程度了。
5. redisson 的高级功能
5.1 异常情况的处理
分布式事务锁最常见的一个问题就是如果已经获取到锁的 client 在 TTL 时间内没有完成竞争资源的处理,而此时锁会被自动释放,造成竞争条件的发生。
这种情况如果让 client 端设置定时任务自动延长锁的占用时间,会造成 client 端逻辑的复杂和冗余。
redisson 在实现的过程中,自然也考虑到了这一问题,redisson 提供了一个“看门狗”的可选特性,并且增加了 lockWatchdogTimeout 配置参数,看门狗线程会自动在 lockWatchdogTimeout 超时后顺延锁的占用时间,从而避免上述问题的发生。
但是,由于看门狗作为独立线程存在,对于性能有所影响,如果并非是处理高度竞争且处理时长不固定的特殊资源,那么并不建议启用 redisson 的看门狗特性。
5.2 多个锁联合使用 — 联锁
既然 redisson 通过多个 redis 节点实现了 RedLock,那么,如果一个业务同时需要占用若干资源,是否可以将多个锁联合使用呢?答案也是可以的。
基于 Redis 的分布式 RedissonMultiLock 对象将多个 RLock 对象分组,并将它们作为一个锁处理。每个 RLock 对象可能属于不同的 Redisson 实例。
在这种复杂场景下,上述“看门狗”特性建议一定要启用,因为任何一个锁状态的崩溃都有可能会造成其他所有锁的持续挂起或资源被意外抢占。
下面是 redisson 联锁的一个示例:
public void multiLock(Integer expireTime,TimeUnit timeUnit,String ...lockKey){
RLock [] rLocks = new RLock[lockKey.length];
for(int i = 0,length = lockKey.length; i < length ;i ++){
RLock lock = redissonClient.getLock(lockKey[i]);
rLocks[i] = lock;
}
RedissonMultiLock multiLock = new RedissonMultiLock(rLocks);
multiLock.lock(expireTime,timeUnit);
logger.info("【Redisson lock】success to acquire multiLock for [ "+lockKey+" ],expire time:"+expireTime+timeUnit);
}边栏推荐
- 利用OpenCV执行相机校准
- Good news - British software 2022 has obtained 10 invention patents!
- Usage of rxjs mergemap
- Two methods of MySQL database login and logout
- How to rewrite tdengine code from 0 to 1 with vscode in "technical class"
- 脉脉热帖:为啥大厂都热衷于造轮子?
- 为什么要从 OpenTSDB 迁移到 TDengine
- InfluxDB集群功能不再开源,TDengine集群功能更胜一筹
- 云原生数据库:数据库的风口,你也可以起飞
- Add in address of idea official website
猜你喜欢

Exporting coordinates of points in TXT format in ArcGIS

Open source summer 2022 | opengauss project selected and announced

Galaxy Kirin V10 system activation

VS code 运行yarn run dev 报yarn : 无法加载文件XXX的问题

云原生数据库:数据库的风口,你也可以起飞

清华徐勇、段文晖研究组开发出高效精确的第一性原理电子结构深度学习方法与程序

技术分享 | kubernetes pod 简介

Usage of rxjs mergemap

MySQL数据库登录和退出的两种方式

阿里巴巴的使命、愿景、核心价值观
随机推荐
一位平凡毕业生的大学四年
xctf攻防世界 MISC薪手进阶区
Three methods to quickly open CMD in a specified folder or place
Market status and development prospect forecast of the global shuttleless air jet loom industry in 2022
Two methods of MySQL database login and logout
[elt.zip] openharmony paper Club - memory compression for data intensive applications
Market status and development prospect forecast of global epoxy resin active toughener industry in 2022
国际数字经济学院、华南理工 | Unified BERT for Few-shot Natural Language Understanding(用于小样本自然语言理解的统一BERT)
Four years of College for an ordinary graduate
Market status and development prospect forecast of global tetramethylammonium hydroxide developer industry in 2022
How to arrange digital collections on online platforms such as reading and Chinese online? Will "read/write-to-earn" products be launched in the future?
Industry university cooperation cooperates to educate people, and Kirin software cooperates with Nankai University to complete the practical course of software testing and maintenance
手把手教你在Windows 10安装Oracle 19c(详细图文附踩坑指南)
广发期货开户安全吗?
Campus book resource sharing platform
Why migrate from opentsdb to tdengine
PostgreSQL database Wal - resource manager rmgr
Is Guosen Securities a state-owned enterprise? Is it safe to open an account with Guosen Securities?
【网络研讨会】MongoDB 携手 Google Cloud 加速企业数字化创新
使用logrotate对宝塔的网站日志进行自动切割