一.分布式锁简介
在分布式系统之前,系统中的锁还是单服务器上的锁,比如锁住一个进程中的多线程访问同一资源。如使用synchronized
来实现。随着系统的发展,到后来分布式应用,有可能同一资源被多台服务器上的不同进程竞争,这种情况下,出现了今天讨论的分布式锁
。
目前主流的分布式锁实现方式主要有下面三种:
- 基于数据库的索引和行锁,数据库乐观锁
- 基于Redis的单线程原子操作:setNX
- 基于Zookeeper的临时有序节点
这里主要介绍Redis的实现。锁需要满足下面的条件:
- 互斥性,只有一个客户端能占有锁,具有排他
- 无死锁,即使发生有客户端无法解锁,也能保证后续其他客户端能加锁,应该有超时或者异常情况下,释放锁
二.Redis实现
1.使用setnx
实现
先上代码:
long now = System.currentTimeMillis();// 使用 setNx 加锁,保证操作的原子性boolean result = SUCCESS.equals(jedis.setnx(lockName,String.valueOf(now + expire*1000)));// 下面的if主要是解决死锁问题if(!result){ String timestamp = jedis.get(lockName); // 如果设置过期时间失败的话,再通过value的时间戳来和当前时间戳比较,防止出现死锁 if(timestamp!=null && Long.parseLong(timestamp)
使用 setnx 实现加锁,其中key是锁,value是锁的过期时间。加了时间戳比较,防止出现死锁情况。 细心的朋友可能也会发现还是存在下面的问题:
- 时间戳同步问题。客户端自己生成过期时间,所以需要强制要求分布式下每个客户端的时间必须同步
- 当锁过期的时候,如果多个客户端同时执行 getSet ,最终只有一个客户端可以加锁,但是这个客户端的锁的过期时间可能被其他客户端覆盖
- 锁不具备拥有者标识,即任何客户端都可以解锁
所以就有了第2种实现方式。
2.使用set "NX" "PX"
实现
实现如下:
public static final String SET_IF_NOT_EXIST = "NX";public static final String SET_WITH_EXPIRE_TIME = "PX";// "NX" 当key不存在时,我们进行set操作;若key已经存在,则不做任何操作//"PX" 给key加一个过期时间String result = jedis.set(lockName, requestId, SET_IF_NOT_EXIST, SET_WITH_EXPIRE_TIME, expire*1000);if(LOCK_SUCCESS.equals(result)){ return true;}return false;
3.解锁实现
解锁的时候,主要思想就是在redis
里面执行一段Lua
脚本。
为什么使用Lua脚本?
保证操作的原子性。eval命令执行Lua代码的时候,Lua代码将被当成一个命令去执行,并且直到eval命令执行完成,Redis才会执行其他命令。另外,开源Redisson中的分布式锁就是Lua实现,我后面会有一篇文章分析Redisson分布式锁的实现。
// KEYS[1] lockName 锁名称 ARGV[1] requestIdString 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(lockName), Collections.singletonList(requestId));if (SUCCESS.equals(result)) { return true;}return false;
三.其他Redis实现
Redisson提供了一种可重入的分布式锁的实现--RedissonLock。
使用示例:
RLock dlock = client.getLock(lockName);boolean result = false;try { // expire 等待时间 leaseTime 超时时间 result = dlock.tryLock(expire, 20, TimeUnit.SECONDS);} catch (InterruptedException e) { e.printStackTrace();} finally { if (dlock.isLocked()) { dlock.unlock(); }}return result;
相关的实现和示例代码放到了Github上,,如果有作用,给个star,哈哈~