现在大多数服务都是分布式部署,分布式环境下需要考虑同步问题时需要用到分布式的同步锁。
分布式锁一般有三种实现方式:
- 1.数据库乐观锁;
- 2.基于Redis的分布式锁;
- 3.基于ZooKeeper的分布式锁。
为了确保分布式锁可用,要确保锁的实现满足以下三个条件:
- 1、互斥性
- 2、不会发生死锁
- 3、加锁和解锁必须是同一个客户端
常见的错误的分布式锁Redis实现
错误加锁代码:
先看两个个加锁代码示例:
示例1:
public static void wrongGetLock1(Jedis jedis, String lockKey, String val, int expireTime) {
Long result = jedis.setnx(lockKey, val);
if (result == 1) {
// 若在这里程序突然崩溃,则无法设置过期时间,将发生死锁
jedis.expire(lockKey, expireTime);
}
}
示例1存在的问题:使用jedis.setnx()和jedis.expire()组合实现加锁,setnx()方法作用就是SET IF NOT EXIST,expire()方法就是给锁加一个过期时间。乍一看好像和前面的set()方法结果一样,然而由于这是两条Redis命令,不具有原子性,如果程序在执行完setnx()之后突然崩溃,导致锁没有设置过期时间。那么将会发生死锁。
示例2:
public synchronized boolean lock() throws InterruptedException { this.locked = false; final long nano = System.currentTimeMillis(); final long timeout = timeoutMillis; while (System.currentTimeMillis() - nano < timeout) { long expireTime = System.currentTimeMillis() + expireMillis + 1;
final String expireStr = String.valueOf(expireTime); if (this.setNX(lockKey, expireStr)) { this.locked = true; return true; } String currentValueStr = this.get(lockKey); if (currentValueStr != null && isExpired(currentValueStr)) { String oldValueStr = this.getSet(lockKey, expireStr);
if (oldValueStr != null && isExpired(oldValueStr)) { this.locked = true; return true; } } //短暂休眠,避免活锁 Thread.sleep(100, random.nextInt(30)); } return false; }
示例2使用jedis.setnx()命令实现加锁,其中key是锁,value是锁的过期时间。 执行过程:1. 通过setnx()方法尝试加锁,如果当前锁不存在,返回加锁成功。2. 如果锁已经存在则获取锁的过期时间,和当前时间比较,如果锁已经过期,则设置新的过期时间,返回加锁成功。
那么这段代码问题在哪里?1. 由于是客户端自己生成过期时间,所以需要强制要求分布式下每个客户端的时间必须同步。 2. 当锁过期的时候,如果多个客户端同时执行jedis.getSet()方法,那么虽然最终只有一个客户端可以加锁,但是这个客户端的锁的过期时间可能被其他客户端覆盖。3. 锁不具备拥有者标识,即任何客户端都可以解锁。
错误解锁代码:
下面看几个错误解锁示例:
错误示例1:
最常见的解锁代码就是直接使用jedis.del()方法删除锁,这种不先判断锁的拥有者而直接解锁的方式,会导致任何客户端都可以随时进行解锁,即使这把锁不是它的。
public static void wrongReleaseLock1(Jedis jedis, String lockKey) {
jedis.del(lockKey);
}
错误示例2
这种解锁代码乍一看也是没问题,与正确姿势差不多,唯一区别的是分成两条命令去执行,代码如下:
public static void wrongReleaseLock2(Jedis jedis, String lockKey, String requestId) {
// 判断加锁与解锁是不是同一个客户端
if (requestId.equals(jedis.get(lockKey))) {
// 若在此时,这把锁突然不是这个客户端的,则会误解锁
jedis.del(lockKey);
}
}
如代码注释,问题在于如果调用jedis.del()方法的时候,这把锁已经不属于当前客户端的时候会解除他人加的锁。那么是否真的有这种场景?答案是肯定的,比如客户端A加锁,一段时间之后客户端A解锁,在执行jedis.del()之前,锁突然过期了,此时客户端B尝试加锁成功,然后客户端A再执行del()方法,则将客户端B的锁给解除了。
分布式锁Redis正确实现
加锁:
public boolean trylock(){
String result = this.jedis.set(lockKey,requestId,SET_IF_NOT_EXIST,SET_WITH_EXPIRE_TIME_MILLISECONDS,expireTime);
return LOCK_SUCCESS.equals(result);
}
可以看到,我们加锁就一行代码: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的过期时间。
总的来说,执行上面的set()方法就只会导致两种结果:
当前没有锁(key不存在),那么就进行加锁操作,并对锁设置个有效期,同时value表示加锁的客户端。 已有锁存在,不做任何操作。 心细的童鞋就会发现了,我们的加锁代码满足我们可靠性里描述的三个条件。首先,set()加入了NX参数,可以保证如果已有key存在,则函数不会调用成功,也就是只有一个客户端能持有锁,满足互斥性。其次,由于我们对锁设置了过期时间,即使锁的持有者后续发生崩溃而没有解锁,锁也会因为到了过期时间而自动解锁(即key被删除),不会发生死锁。最后,因为我们将value赋值为requestId,代表加锁的客户端请求标识,那么在客户端在解锁的时候就可以进行校验是否是同一个客户端。
解锁
有了正确的加锁姿势,看一下正确的解锁姿势:
public boolean unlock(){
String script = "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end";
Object result = this.jedis.eval(script, Collections.singletonList(lockKey), Collections.singletonList(requestId));
return RELEASE_SUCCESS.equals(result);
}
我们解锁只需要两行代码就搞定了!第一行代码,我们写了一个简单的Lua脚本代码,第二行代码,我们将Lua代码传到jedis.eval()方法里,并使参数KEYS[1]赋值为lockKey,ARGV[1]赋值为requestId。eval()方法是将Lua代码交给Redis服务端执行。 思路是要确保上述操作是原子性的,所以检查和删除在一个命令里执行,首先获取锁对应的value值,检查是否与requestId相等,如果相等则删除锁(解锁)。jedis.eval命令执行Lua代码的候,Lua代码将被当成一个命令去执行,并且直到eval命令执行完成,Redis才会执行其他命令。
Zxvipe https://bestadalafil.com/ – cialis coupon Whrlhv Landsteiner received the Nobel Prize in Physiology or Medicine for his discovery of human blood groups. where to buy cialis Hbiaji Myonus Lecture Whats Your Type Personality and Health Suggested Reading Digman Personality Structure. https://bestadalafil.com/ – Cialis