最近项目上需要用到分布式锁,研究了以下两种方案。
一、zoomkeeper实现
zookeeper 实现分布式锁,主要是通过在一个节点下为每个服务器建立一个子结点,然后通过排序来实现锁的获得与解锁。
假设有三个服务器,序号分别为1,2,3,具体步骤如下:
1.建立root节点/root。
2.在root节点下建立三个子结点/root+{1,2,3}。
3.服务器i获取/root节点下的所有子结点,然后进行排序。
4.如果/root目录下的所有节点的最小节点序号等于自身序号,则获得锁。然后do something。
5.处理完后删除/root/i,释放锁。
该方式实现互斥的基础主要是建立在各自对不同节点的处理互不影响,每个子节点在建立后只有对应的服务器才能删除,所以部存在多服务器同时访问删除某个节点的情况。
但是该方式同时也存在一些缺点:1.建立子节点。如果由某个master服务器来创建所有的子结点,则master服务器需要知道一共有哪些服务器,这给服务带来了复杂性。如果由服务器自己创建相应的子结点,则不能确认何时所有服务器都完成创建,如果所有的服务器还没有创建完子结点便开始排序,会影响到锁的正确性。2.死锁。如果某个节点在注册子节点后宕机,则锁一直不能释放。如需客服该问题,又需要引入不少复杂性。
由于以上缺点,所以放弃了该方案。
二、redis实现
redis的setnx函数天生具有多线程安全的能力,利用这一点能够实现分布式锁。
具体步骤如下:
1.result = setnx(key,key);
2.如果result=1,则获得锁,如果为0则继续尝试。
3.为了克服死锁问题,通过expire来实现。
下面是具体代码
// Copyright (C) 2015 Meituan
// All rights reserved
package com.meituan.service.mobile.sieve.util;
import com.meituan.cache.redisCluster.client.typeInterface.IMedis;
import com.sankuai.meituan.config.util.StringUtils;
/**
* @author tanyuanwei
* @version 1.0
* @created 15-7-22 下午5:26
* 提供分布式锁
*/
public class DistributedLock {
private IMedis medisClient;
private String lockKey;
//锁的失效时间,防止死锁。单位毫秒
private int timeOut=-1;
private boolean getLock = false;
public DistributedLock(){
}
public DistributedLock(IMedis medisClient,String lockKey,int timeOut){
this.medisClient=medisClient;
this.lockKey=lockKey;
this.timeOut=timeOut;
}
/**
* 尝试获取锁,如果没有获取到锁,一直阻塞
* @param waitTime 获取锁的等待时间,毫秒
* @return true:获得锁;false:未获得锁。
*/
public boolean tryLock(long waitTime) throws ParameterException {
if (timeOut<0 || StringUtils.isEmpty(lockKey)){
throw new ParameterException();
}
long endTime = System.currentTimeMillis()+waitTime;
while (System.currentTimeMillis()<endTime) {
long state = medisClient.setStringIfNotExist(lockKey, timeOut, lockKey);
if (state == 1) {
getLock=true;
return true;
} else {
try {
Thread.sleep(1);
} catch (InterruptedException e) {
continue;
}
}
}
return false;
}
/**
* 尝试获取锁,如果没有获取到锁,返回false阻塞
* @return true:获得锁;false:未获得锁。
*/
public boolean lock() throws ParameterException {
if (timeOut<0 || StringUtils.isEmpty(lockKey)){
throw new ParameterException();
}
long state = medisClient.setStringIfNotExist(lockKey,timeOut,lockKey);
if (state == 1){
return true;
}
return false;
}
/**
* 释放锁。
*/
public void unLock(){
if (getLock) {
medisClient.delete(lockKey);
}
}
public void setMedisClient(IMedis medisClient) {
this.medisClient = medisClient;
}
public void setLockKey(String lockKey) {
this.lockKey = lockKey;
}
public void setTimeOut(int timeOut) {
this.timeOut = timeOut;
}
}