Redis分布式锁本质上是在Redis中占一个“坑”,当其他进程也要来占坑的时候,如果有其他进程来占坑,就放弃或者稍后重试。
1、基本实现
“占坑”的基本操作是使用setnx(set if not exists)指令,保证只有第一个客户端能占到;使用完再用del指令释放“坑”。当然,为了避免客户端执行的中间出现异常挂掉,还应该增加过期机制,让锁可以超时自动释放。例如:
> setnx lock_code lock_value
OK
> expire lock_code 5
......
> del lock_code
(integer) 1
但是上述方案存在一个问题,如果客户端在执行setnx后、执行expire之前出现了问题,那么超时机制是无法实现的。为了解决这个问题,Redis 2.8加入了set指令的扩展参数集。
这种原子性操作的命令如下:
> set lock_code lock_value ex 5 nx
OK
> expire lock_code 5
......
> del lock_code
(integer) 1
2、超时问题
如果在加锁和释放锁之间的逻辑执行的过长,可能会导致锁超时释放,这时其他线程就可以访问临界资源,导致临界区的代码不能严格串行执行。
解决这个问题最根本的方法,还是尽量不要把过长的任务放到分布式锁期间执行。
3、可重入锁
基于ThreadLocal和引用计数器来实现,在ThreadLocal中存储一个Map<String, Integer>实例,key表示锁的名字,Integer表示锁的重入次数,加锁可以使用jedis的set方法来实现:
private boolean lock(String lockKey) {
return jedis.set(lockKey, "", "nx", "ex", 5L) != null;
}