网上找分布式锁的时候发现有两种错误的方式很普遍的流传着,这里就介绍一种比较正确的redis实现分布式锁的方法吧,首先让我们来看看两种错误示范
错误一:使用setnx + key过期来实现
介绍下redis的setnx(key,value)方法是SET IF NOT EXIST,意思是只有当key不存在的时候才能设置,这样我们的我们就能当一台机器一台机器通过setnx 获取到锁了,其他的机器就没办法获取了,然后通过给他key设置expire(过期时间)来防止死锁,但是这种情况由于setnx 和expire是两步,不具有原子性如果setnx后奔溃会造成死锁
错误二:使用setnx + 时间戳来实现
前面说由于设置和过期是两步可能造成死锁,所以就发展出了setnx(key,expire)听过给key设置值为过期时间,这样其他的机器获取到锁的时候先比较一下设置的过期时间是否小于当前时间,如果小于则删除锁,并且重新赋锁,但是这种情况必须强制同步所有服务器上的时间,而且多个服务器获取锁可能会造成后面获得锁的A设置的时间戳被B给替换掉,而且锁所有人都可以解锁,不可靠
正确方法:使用set(String key, String value, String nxxx, String expx, int time)加锁
从2.6.12版本后,redis的set()方法就可以设置5个参数了,其中
第一个为key 我们可以用来设置锁
第二个为value 我们可以用当前服务器唯一id来
第三个为nx/xx nx表示只能设置不存在的,xx表示只能设置存在的,我们用nx
第四个为过期时间 ex表示秒 px表示毫秒 我们用px
第五个是过期时间的值
这样我们就能设置锁和过期时间放在一个原子性方法里了
当然加锁了也要解锁,为了保持解锁的原子性 我们用
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;
}
}
这样我们就能愉快的获取锁了