Redis分布式锁最简单的实现
要实现分布式锁,首先需要Redis具备“互斥”能力,这可以通过SETNX命令实现。SETNX表示SET if Not Exists,即如果key不存在,才会设置它的值,否则什么也不做。利用这一点,不同客户端就能实现互斥,从而实现一个分布式锁。
举例:
- 客户端1申请加锁,加锁成功:
- 客户端2申请加锁,由于它后到达,所以加锁失败:
当加锁成功的客户端操作完共享资源后,需要及时释放锁,通常通过DEL命令删除这个key即可。
避免死锁
如果加锁的客户端出现异常或挂掉,那么可能会造成死锁现象,为避免这种情况,可以给锁设置一个过期时间:
SETNX lock 1 // 加锁
EXPIRE lock 10 // 10s后自动过期
但是,由于SETNX和EXPIRE是两条命令,无法保证其原子性,因此有可能只执行了SETNX,未执行EXPIRE。为了解决这个问题,可以使用Redis 2.6.12之后扩展的SET命令:
SET lock 1 EX 10 NX
防止锁被其他客户端释放
为防止锁被其他客户端释放,可以在加锁时设置一个只有自己知道的“唯一标识”:
SET lock $uuid EX 20 NX
释放锁时,先检查锁是否归自己持有:
if redis.get("lock") == $uuid:
redis.del("lock")
由于GET和DEL不是原子操作,可以使用Lua脚本来保证原子性:
if redis.call("GET",KEYS[1]) == ARGV[1]
then
return redis.call("DEL",KEYS[1])
else
return 0
end
Java代码实现分布式锁
package com.msb.redis.lock;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisPool;
import redis.clients.jedis.params.SetParams;
import java.util.Arrays;
import java.util.UUID;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
@Component
public class RedisDistLock implements Lock {
private final static int LOCK_TIME = 5*1000;
private final static String RS_DISTLOCK_NS = "tdln:";
private final static String RELEASE_LOCK_LUA =
"if redis.call('get',KEYS[1])==ARGV[1] then\n" +
" return redis.call('del', KEYS[1])\n" +
" else return 0 end";
private ThreadLocal<String> lockerId = new ThreadLocal<>();
private Thread ownerThread;
private String lockName = "lock";
@Autowired
private JedisPool jedisPool;
public String getLockName() {
return lockName;
}
public void setLockName(String l