锁是为了保证在多线程并发的情况下,只有一个线程执行同步代码块。在一个JVM进程中,可通过synchronized 或者 J.U.C包中的显示并发锁保证。分布式锁就是多个进程一次只能有一个进程(或线程)执行同步代码块。
可以采用框架的实现方式,如Memcached的原子性操作add命令、redis的setnx命令和lua脚本、zookeeper的顺序临时节点等来保证。 redis实现方案: 加锁操作
/**
* try get distributed lock
* @param lockKey
* @param requestId:锁的value值
* @param expireTime TimeUnit seconds
* @return Whether or not to get the lock successfully
*/
public boolean tryGetDistributedLock(String lockKey, String requestId, long expireTime) {
boolean result = false;
try {
// 保证原子性,取锁由一条命令完成
String response = jedisCluster.set(lockKey, requestId, "NX", "PX", expireTime*1000);
if(LOCK_SUCCESS.equals(response)) {
result = true;
}
}
catch (Exception e) {
log.error("Try get distributed lock:[lockKey:{0}, requestId:{1}] error due to {2}.", lockKey, requestId, e);
}
return result;
}
解锁操作
public boolean releaseDistributedLock(String lockKey, String requestId) {
boolean result = false;
try {
// 保证原子性,解锁由一条命令完成
String script = "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end";
Object response = jedisCluster.eval(script, Collections.singletonList(lockKey), Collections.singletonList(requestId));
if (RELEASE_SUCCESS.equals(response)) {
return true;
}
}
catch (Exception e) {
log.error("Release distributed lock:[lockKey:{0}, requestId:{1}] error due to {2}.", lockKey, requestId, e);
}
return result;
}
requestId的值最好设置为与线程相关,这样保证只有获得该锁的线程可以释放锁。 如果加锁之后的业务事情还没有完成,但是持有锁的事件超过了 expireTime ,此时锁被释放,仍不能完全保证同步代码块的执行。解决方案:可将expireTime 设置的长些,或者在加锁线程内开启一个守护线程,为锁续命。。
zookeeper 使用curator框架
InterProcessMutex lock = new InterProcessMutex(client, lockPath);
if ( lock.acquire(maxWait, waitUnit) ) {
try {
// do something
}
finally {
lock.release();
}
}
参考文章: 基于Zookeeper的分布式锁