分布式锁一般有三种实现方式:
1. 数据库乐观锁;
2. 基于Redis的分布式锁;
3. 基于ZooKeeper的分布式锁。
为了确保分布式锁可用,我们至少要确保锁的实现同时满足以下四个条件:
1.互斥性。在任意时刻,只有一个客户端能持有锁。
2.不会发生死锁。即使有一个客户端在持有锁的期间崩溃而没有主动解锁,也能保证后续其他客户端能加锁。
3.具有容错性。只要大部分的Redis节点正常运行,客户端就可以加锁和解锁。
4.解铃还须系铃人。加锁和解锁必须是同一个客户端,客户端自己不能把别人加的锁给解了。
加锁代码
public class RedisTool {
private static final String LOCK_SUCCESS = "OK";
private static final String SET_IF_NOT_EXIST = "NX";
private static final String SET_WITH_EXPIRE_TIME = "PX";
/**
* 尝试获取分布式锁
* @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()方法一共有五个形参:
-
第一个为key,我们使用key来当锁,因为key是唯一的。
-
第二个为value,我们传的是requestId,很多童鞋可能不明白,有key作为锁不就够了吗,为什么还要用到value?原因就是我们在上面讲到可靠性时,分布式锁要满足第四个条件解铃还须系铃人,通过给value赋值为requestId,我们就知道这把锁是哪个请求加的了,在解锁的时候就可以有依据。requestId可以使用
UUID.randomUUID().toString()
方法生成。 -
第三个为nxxx,这个参数我们填的是NX,意思是SET IF NOT EXIST,即当key不存在时,我们进行set操作;若key已经存在,则不做任何操作;
-
第四个为expx,这个参数我们传的是PX,意思是我们要给这个key加一个过期的设置,具体时间由第五个参数决定。
-
第五个为time,与第四个参数相呼应,代表key的过期时间。
解锁代码
public class RedisTool {
private static final Long RELEASE_SUCCESS = 1L;
/**
* 释放分布式锁
* @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语言来实现呢?因为要确保上述操作是原子性的。如果:
public static void wrongReleaseLock2(Jedis jedis, String lockKey, String requestId) {
// 判断加锁与解锁是不是同一个客户端
if (requestId.equals(jedis.get(lockKey))) {
// 若在此时,这把锁突然不是这个客户端的,则会误解锁
jedis.del(lockKey);
}
}
就会有问题,比如客户端A加锁,一段时间之后客户端A解锁,在执行jedis.del()
之前,锁突然过期了,此时客户端B尝试加锁成功,然后客户端A再执行del()方法,则将客户端B的锁给解除了。
相关推荐
主要介绍了Java Redis分布式锁的正确实现方式详解,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下
Redis分布式锁在实现跨进程、跨机器的互斥访问时,虽功能强大,但也存在一些常见问题。这些问题主要源于网络延迟、系统时钟误差以及Redis自身的特性。 一个典型问题是锁的"死锁"现象,即因进程意外终止或网络故障,...
在本篇文章里小编给大家整理的是关于Redis分布式锁的正确实现方式介绍,有兴趣的朋友们可以学习下。
虽然网上已经有各种介绍Redis分布式锁实现的博客,然而他们的实现却有着各种各样的问题,为了避免误人子弟,本篇博客将详细介绍如何正确地实现Redis分布式锁。 可靠性 首先,为了确保分布式锁可用,我们至少要确保...
在昨天 review 队友代码的过程中,发现了我们组分布式锁的写法似乎有点问题,实现代码如下: 加锁部分 解锁部分 主要原理是使用了 redis 的 setnx 去插入一组 key-value,其中 key 要上锁的标识(在项目中是锁死...
虽然网上已经有各种介绍Redis分布式锁实现的博客,然而他们的实现却有着各种各样的问题,将详细介绍如何正确地实现Redis分布式锁。
虽然网上已经有各种介绍Redis分布式锁实现的博客,然而他们的实现却有着各种各样的问题,为了避免误人子弟,本篇博客将详细介绍如何正确地实现Redis分布式锁。 可靠性 首先,为了确保分布式锁可用,我们至少要确保锁...
主要介绍了java语言描述Redis分布式锁的正确实现方式,具有一定借鉴价值,需要的朋友可以参考下。
将业务抽象,技术角度看,秒杀就是多个线程对资源进行操作,所以实现秒杀,就必须控制线程对资源的争抢,既要保证高效并发,也要保证操作的正确。 一些可能的实现 刚才提到过,实现秒杀的关键点是控制线程对资源的...
锁的本质就是互斥,保证任何时候能有一个客户端持有同一个锁,如果考虑使用redis来实现一个分布式锁,最简单的方案就是在实例里面创建一个键值,释放锁的时候,将键值删除。但是一个可靠完善的分布式锁需要考虑的...
摘要:分布式架构设计如今在企业中被量的应,在不同的分布式节点进协同作的时候,节点服务的时序、结果的正确性以及执成本也成为了必须考虑的重要因素。其中竞态条件会导致
主要介绍了SpringBoot整合Redis正确的实现分布式锁的示例代码,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
我为的单个实例实现了页面上概述的分布式锁模式。 请参阅“使用单个实例正确实现”部分。 ##入门 git clone https://github.com/krujos/distsched cd distsched该项目使用来支持清单。 如果您想使用它,您需要...
本处涉及到分布式环境下的同步,因此将其解耦,令牌桶模型存储于Redis中,对其同步操作的控制放置在如下控制类,其中同步控制使用到了前面介绍的分布式锁(参考基于Redis分布式锁的正确打开方式)
Hadoop Practice ...使用redis简单正确实现分布式锁 使用Lettuce作为redis客户端实例 redis持久化详解 Zookeeper Practice 基于zookeeper的服务注册与发现 基于zookeeper实现leader选举 zookeeper的leader选举机制