最近看了一个老项目(2018年的),发现其中用 Redis 来实现分布式锁🔒。
// jedis
public String lock(String lockName, long acquireTimeout) {
return lockWithTimeout(lockName, acquireTimeout, DEFAULT_EXPIRE);
}
public String lockWithTimeout(String lockName, long acquireTimeout, long timeout) {
RedisConnectionFactory connectionFactory = redisTemplate.getConnectionFactory();
RedisConnection redisConnection = connectionFactory.getConnection();
/** 随机生成一个value */
String identifier = UUID.randomUUID().toString();
String lockKey = LOCK_PREFIX + lockName;
int lockExpire = (int) (timeout / 1000);
long end = System.currentTimeMillis() + acquireTimeout; /** 获取锁的超时时间,超过这个时间则放弃获取锁 */
while (System.currentTimeMillis() < end) {
if (redisConnection.setNX(lockKey.getBytes(), identifier.getBytes())) {
redisConnection.expire(lockKey.getBytes(), lockExpire);
/** 获取锁成功,返回标识锁的value值,用于释放锁确认 */
RedisConnectionUtils.releaseConnection(redisConnection, connectionFactory);
return identifier;
}
/** 返回-1代表key没有设置超时时间,为key设置一个超时时间 */
if (redisConnection.ttl(lockKey.getBytes()) == -1) {
redisConnection.expire(lockKey.getBytes(), lockExpire);
}
try {
Thread.sleep(10);
} catch (InterruptedException e) {
log.warn("获取分布式锁:线程中断!");
Thread.currentThread().interrupt();
}
}
RedisConnectionUtils.releaseConnection(redisConnection, connectionFactory);
return null;
}
public boolean releaseLock(String lockName, String identifier) {
if (StringUtils.isEmpty(identifier)) return false;
RedisConnectionFactory connectionFactory = redisTemplate.getConnectionFactory();
RedisConnection redisConnection = connectionFactory.getConnection();
String lockKey = LOCK_PREFIX + lockName;
boolean releaseFlag = false;
while (true) {
try {
byte[] valueBytes = redisConnection.get(lockKey.getBytes());
/** value为空表示锁不存在或已经被释放*/
if (valueBytes == null) {
releaseFlag = false;
break;
}
/** 通过前面返回的value值判断是不是该锁,若是该锁,则删除,释放锁 */
String identifierValue = new String(valueBytes);
if (identifier.equals(identifierValue)) {
redisConnection.del(lockKey.getBytes());
releaseFlag = true;
}
break;
} catch (Exception e) {
log.warn("释放锁异常", e);
}
}
RedisConnectionUtils.releaseConnection(redisConnection, connectionFactory);
return releaseFlag;
}
public void lockTest(String lockName, Long acquireTimeout, CouponSummary couponSummary) {
String lockIdentify = redisLock.lock(lockName,acquireTimeout);
if (StringUtils.isNotEmpty(lockIdentify)){
// 业务代码
redisLock.releaseLock(lockName, lockIdentify);
}
else{
System.out.println("get lock failed.");
}
}
看完之后,有这几点感悟
再结合 redisson 框架来看的话,就会发现
再把视角移到 Redis 服务器来,就会发现 单点问题 的存在,此时分布式锁就无法使用了。
这个问题可以通过 主从,哨兵,集群 模式解决,但是又有了一个 故障转移问题 。
先简要介绍下这几个模式
回到 分布式锁 这个话题,通过主从切换,可以实现故障转移。但是当加锁成功时,master 挂了,此时还没同步锁信息到这个 slave 上,那这个分布式锁也是失效了。
网上的方案是通过 Redlock(红锁) 来解决。
Redlock 的大致意思就是给多个节点加锁,超过半数成功的话,就认为加锁成功。
redisson 的红锁用法👇
RLock lock1 = redissonInstance1.getLock("lock1");
RLock lock2 = redissonInstance2.getLock("lock2");
RLock lock3 = redissonInstance3.getLock("lock3");
RedissonRedLock lock = new RedissonRedLock(lock1, lock2, lock3);
// 同时加锁:lock1 lock2 lock3
// 红锁在大部分节点上加锁成功就算成功。
lock.lock();
...
lock.unlock();
我更偏向于解决这个 主从复制延迟 的问题,比如
当然,具体问题具体分析,可以根据业务准备补偿措施,但也要避免这个过度设计。
在查阅资料时,看到了这么一个事情 👇
《数据密集型应用系统设计》的作者 Martin 去反驳这个 Redlock ,并用一个进程暂停(GC)的例子,指出了 Redlock 安全性问题:
客户端 1 请求锁定节点 A、B、C、D、E 客户端 1 的拿到锁后,进入 GC(时间比较久) 所有 Redis 节点上的锁都过期了 客户端 2 获取到了 A、B、C、D、E 上的锁 客户端 1 GC 结束,认为成功获取锁 客户端 2 也认为获取到了锁,发生「冲突」
还有 时钟 漂移的问题
这里我就不过多 CV 了,可以看看原文👇
《一文讲透Redis分布式锁安全问题》:https://cloud.tencent.com/developer/article/2332108
《How to do distributed locking》https://martin.kleppmann.com/2016/02/08/how-to-do-distributed-locking.html
看到这个 18年 的项目,不自觉想起在校的时光,那会还很多不懂,也不会搜索,获取到的信息非常有限。
现在就不一样了,像以前那样搜搜都能看到很多图文并茂的文章,甚至有 AI 出来解答,各种社区文档都非常丰富,但也多了一个麻烦,更需要去验证这个信息的真伪了。😵
本文就到这里啦,感谢您的阅读,有不对的地方也请您帮忙指正!谢谢~😋 喜欢的小伙伴们,别忘了点赞关注呀~😋 祝你有个美好的一天!😝 https://github.com/Java4ye 😆
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。