常用的分布式锁实现方式
分布式锁一般有三种实现方式:
- 数据库乐观锁;
- 基于 Redis 的分布式锁;
- 基于 ZooKeeper 的分布式锁。
分布式锁的可靠性
为了确保分布式锁可用,至少要确保锁的实现同时满足以下四个条件:
- 互斥性:在任意时刻,只有一个客户端能持有锁。
- 不会发生死锁:即使有一个客户端在持有锁的期间崩溃而没有主动解锁,也能保证后续其他客户端能加锁(设置超时)。
- 具有容错性:只要大部分的Redis节点正常运行,客户端就可以加锁和解锁(Redis高可用)。
- 解铃还须系铃人:加锁和解锁必须是同一个客户端,客户端自己不能解别人加的锁。
实现
- 依赖 Jedis 组件,通过 Maven 引入 Jedis
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
<version>2.9.0</version>
</dependency>
- 加锁
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 nx, String expx, int time) 参数解释:
第一个为key,我们使用key来当锁,因为key是唯一的。
第二个为value,在可靠性中,分布式锁要满足第四个条件解铃还须系铃人,通过给value赋值为requestId,我们就知道这把锁是哪个请求加的了,在解锁的时候就可以有依据。
第三个为nx,这个参数设置为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 代码传到 jedis.eval() 方法里,并使参数 KEYS[1] 赋值为 lockKey,ARGV[1] 赋值为 requestId。eval() 方法是将 Lua 代码交给 Redis 服务端执行。并且直到eval命令执行完成,Redis才会执行其他命令。
总结:
- 要通过 Redis 实现分布式锁,只要保证能满足可靠性里的四个条件。
- 如果项目中 Redis 是多机部署的,可以尝试使用 Redisson 实现分布式锁。