引
在分布式系统中,资源可能同时被多个客户端申请访问,因此保证数据访问的正确性和性能是分布式系统必须要考虑的问题。非分布式下我们通常是通过synchronize或lock,以及数据库锁(不限制非分布式和分布式),而这两种多存在相应的弊端,synchronize或lock不能解决分布式系统,数据库锁在大量请求下容易产生锁等待、死锁和处理失败对数据库的影响较大。所以分布式锁的应用成为大多数的首选。
Zookeeper分布式锁
完全分布式锁是全局同步的,这意味着在任何时刻没有两个客户端会同时认为它们都拥有相同的锁,Zookeeper 可以实现分布式锁。
Zookeeper分布式锁的实现流程
Zookeeper实现分布式锁是通过节点和临时顺序节点来实现的:
1.在构造函数里面启动的时候建立一个节点,假如命名为:lock。节点类型为持久节点(PERSISTENT)【ZooKeeper里面的znode节点会自动同步的,而且是强一致性,创建一个节点后只有ZooKeeper集群同步完成后算成功】
2.每当进程需要访问共享资源时,会在lock节点下面建立响应的顺序子节点,节点类型为临时顺序节点(EPHEMERAL_SEQUENTIAL)
3.在建立子节点之后,判断刚刚建立的子节点顺序号是否为最小节点,如果是最小节点,则可以获得该锁对资源进行访问。(临时子节点建立会自动生成一个序号的)
4.如果不是该节点,就获得该节点的上一顺序节点,并给该节点是否存在注册监听事件。同时在这里阻塞。等待监听事件的发生。获得控制权(实现watch接口,并且重写process方法,在process里面实现监听)
5.当完成之后,关闭ZooKeeper连接,进而可以应发监听事件,释放该锁(客户端关闭ZooKeeper连接之后会删除当前的临时节点)
具体实现
我们先用zookeeper包去实现这个流程,稍后在用Curator去实现(轮子)
/**
*
* @author J
*/
public class DistributedLock implements Lock, Watcher {
private ZooKeeper zk = null;
// 根节点
private String ROOT_LOCK = "/locks";
// 竞争的资源
private String lockName;
// 等待的前一个锁
private String WAIT_LOCK;
// 当前锁
private String CURRENT_LOCK;
// 计数器
private CountDownLatch countDownLatch;
private int sessionTimeout = 30000;
private List<Exception> exceptionList = new ArrayList<Exception>();
/**
* 配置分布式锁
* @param config 连接的url
* @param lockName 竞争资源
*/
public DistributedLock(String config, String lockName) {
this.lockName = lockName;
try {
// 连接zookeeper
zk = new ZooKeeper(config, sessionTimeout, this);
Stat stat = zk.exists(ROOT_LOCK, false);
if (stat == null) {
// 如果根节点不存在,则创建根节点
zk.create(ROOT_LOCK, new byte[0], ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT);
}
} catch (IOException e) {
e.printStackTrace();
} catch (InterruptedException e) {
e.printStackTrace();
} catch (KeeperException e) {
e.printStackTrace();
}
}
/**
* 节点监视器
*/
public void process(WatchedEvent event) {
if (this.countDownLatch != null) {
this.countDownLatch.countDown();
}
}
public void lock() {
if (exceptionList.size() > 0) {
throw new LockException(exceptionList.get(0));
}
try {
if (this.tryLock()) {
System.out.println(Thread.currentThread().getName() + " " + lockName + "获得了锁");
return;
} else {
// 等待锁
waitForLock(WAIT_LOCK, sessionTimeout);
}
} catch (InterruptedException e) {
e.printStackTrace();
} catch (KeeperException e) {
e.printStackTrace();
}
}
public boolean tryLock() {
try {
String splitStr = "_lock_";
if (lockName.contains(splitStr)) {
throw new LockException("锁名有误");
}
// 创建临时有序节点
CURRENT_LOCK = zk.create(ROOT_LOCK + "/" + lockName + splitStr, new byte[0],
ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.EPHEMERAL_SEQUENTIAL);
System.out.println(CURRENT_LOCK + " 已经创建");
// 取所有子节点
List<String> subNodes = zk.getChildren(ROOT_LOCK, false);
// 取出所有lockName的锁
List<String> lockObjects = new ArrayList<String>();
for (String node : subNodes) {
String _node = node.split(splitStr)[0];
if (_node.equals(lockName)) {
lockObjects.add(node);
}
}
Collections.sort(lockObjects);
System.out.println(Thread.currentThread().getName() + " 的锁是 " + CURRENT_LOCK);
// 若当前节点为最小节点,则获取锁成功
if (CURRENT_LOCK.equals(ROOT_LOCK + "/" + lockObjects.get(0))) {
return true;
}
// 若不是最小节点,则找到自己的前一个节点
String prevNode = CURRENT_LOCK.substring(CURRENT_LOCK.lastIndexOf("/") + 1);
WAIT_LOCK = lockObjects.get(Collections.binarySearch(lockObjects, prevNode) - 1);
} catch (InterruptedException e) {
e.printStackTrace();
} catch (KeeperException e) {
e.printStackTrace();
}
return false;
}
public boolean tryLock(long timeout, TimeUnit unit) {
try {
if (this.tryLock()) {
return true;
}
return waitForLock(WAIT_LOCK, timeout);
} catch (Exception e) {
e.printStackTrace();
}
return false;
}
// 等待锁
private boolean waitForLock(String prev, long waitTime) throws KeeperException, InterruptedException {
Stat stat = zk.exists(ROOT_LOCK + "/" + prev, true);
if (stat != null) {
System.out.println(Thread.currentThread().getName() + "等待锁 " + ROOT_LOCK + "/" + prev);
this.countDownLatch = new CountDownLatch(1);
// 计数等待,若等到前一个节点消失,则precess中进行countDown,停止等待,获取锁
this.countDownLatch.await(waitTime, TimeUnit.MILLISECONDS);
this.countDownLatch = null;
System.out.println(Thread.currentThread().getName() + " 等到了锁");
}
return true;
}
public void unlock() {
try {
System.out.println("释放锁 " + CURRENT_LOCK);
zk.delete(CURRENT_LOCK, -1);
CURRENT_LOCK = null;
zk.close();
} catch (InterruptedException e) {
e.printStackTrace();
} catch (KeeperException e) {
e.printStackTrace();
}
}
public Condition newCondition() {
return null;
}
public void lockInterruptibly() throws InterruptedException {
this.lock();
}
public class LockException extends RuntimeException {
private static final long serialVersionUID = 1L;
public LockException(String e){
super(e);
}
public LockException(Exception e){
super(e);
}
}
}
public static void main(String[] args) {
int n = 500;
Runnable runnable = new Runnable() {
public void run() {
DistributedLock lock = null;
try {
lock = new DistributedLock("127.0.0.1:2181", "lock");
lock.lock();
System.out.println(n);
System.out.println(Thread.currentThread().getName() + "正在运行");
} finally {
if (lock != null) {
lock.unlock();
}
}
}
};
for (int i = 0; i < 10; i++) {
Thread t = new Thread(runnable);
t.start();
}
}
Curator的实现,Curator封装了Zookeeper操作,通过Curator管理和Zookeeper的连接,当连接有问题时会自动重试(retry)。
/**
* 分布式锁
* @author J
*/
public class DistLock {
private static final String rootPath = "distLock";
private static final int DEFAULT_CONNECT_TIMEOUT = 2000;
private static final int DEFAULT_SESSION_TIMEOUT = 2000;
private static final int DEFAULT_LOCK_TIMEOUT = 120;
private String connectionString;
private int connectionTimeout;
private int sessionTimeout;
private CuratorFramework client;
private Map<String, InterProcessMutex> locks;
public DistLock() {
this.connectionString = PropertyUtil.get("zkCluster");
this.connectionTimeout =
StringUtils.isBlank(PropertyUtil.get("connect.timeout")) ? DEFAULT_CONNECT_TIMEOUT
: Integer.valueOf(PropertyUtil.get("connect.timeout"));
this.sessionTimeout =
StringUtils.isBlank(PropertyUtil.get("request.timeout")) ? DEFAULT_SESSION_TIMEOUT
: Integer.valueOf(PropertyUtil.get("request.timeout"));
this.client =
CuratorFrameworkFactory.builder().connectString(connectionString)
.retryPolicy(new ExponentialBackoffRetry(1000, 3))
.connectionTimeoutMs(this.connectionTimeout)
.sessionTimeoutMs(this.sessionTimeout).namespace(rootPath).build();
this.client.start();
locks = new HashMap<>(32);
}
/**
*
* <p>Descrption: 获取zk客户端</p>
* @Author J
* @return CuratorFramework
* @return
*/
public CuratorFramework getClient() {
return client;
}
/**
*
* <p>Descrption: 获取分布式锁</p>
* @Author J
* @return boolean
* @param action
* @param lockId
* @param time
* @return
* @throws Exception
*/
public boolean lock(String action, String lockId, int time) throws Exception {
String uniqueLockId = action+"_"+lockId;
InterProcessMutex lock = new InterProcessMutex(this.client, "/"+uniqueLockId);
boolean isLocked = lock.acquire(time, TimeUnit.SECONDS);
if (isLocked) {
this.locks.put(uniqueLockId, lock);
}
return isLocked;
}
/**
*
* <p>Descrption: 获取分布式锁</p>
* @Author J
* @return boolean
* @param action
* @param lockId
* @return
* @throws Exception
*/
public boolean lock(String action, String lockId) throws Exception {
return lock(action, lockId, DEFAULT_LOCK_TIMEOUT);
}
/**
*
* <p>Descrption: 批量锁</p>
* @Author J
* @return boolean
* @param action
* @param lockIds
* @param time
* @return
* @throws Exception
*/
public boolean batchLock(String action, Collection<String> lockIds, int time) throws Exception {
boolean ret = true;
for (String lockId : lockIds) {
ret = ret && lock(action, lockId, time);
// 所有加锁成功才算成功
if (!ret) {
unBatchLock(action, lockIds);
break;
}
}
return ret;
}
public boolean batchLock(String action, Collection<String> lockIds) throws Exception {
return batchLock(action, lockIds, DEFAULT_LOCK_TIMEOUT);
}
/**
*
* <p>Descrption: 返回无法获取锁的lockId</p>
* @Author J
* @return Set<String>
* @param action
* @param lockIds
* @param time
* @return
* @throws Exception
*/
public Set<String> batchLockRetUnlock(String action, Collection<String> lockIds, int time) throws Exception {
Set<String> unlockIds = new HashSet<>();
for (String lockId : lockIds) {
if (!lock(action, lockId, time)){
unlockIds.add(lockId);
}
}
return unlockIds;
}
public Set<String> batchLockRetUnlock(String action, Collection<String> lockIds) throws Exception {
return batchLockRetUnlock(action, lockIds, DEFAULT_LOCK_TIMEOUT);
}
/**
*
* <p>Descrption: 并发批量加锁</p>
* @Author J
* @return Set<String>
* @param action
* @param lockIds
* @param time
* @return
* @throws Exception
*/
public Set<String> batchParalleLockRetUnlock(String action, Collection<String> lockIds, int time) throws Exception {
final Set<String> unlockIds = new HashSet<>();
// lockIds.parallelStream().limit(100).forEach(lockId -> {
for(String lockId : lockIds){
try {
if (!lock(action, lockId, time)) {
unlockIds.add(lockId);
}
} catch (Exception e) {
e.printStackTrace();
}
};
return unlockIds;
}
public Set<String> batchParalleLockRetUnlock(String action, Collection<String> lockIds) throws Exception {
return batchParalleLockRetUnlock(action, lockIds, DEFAULT_LOCK_TIMEOUT);
}
/**
*
* <p>Descrption: 释放锁</p>
* @Author J
* @return void
* @param action
* @param lockId
* @throws Exception
*/
public void unlock(String action, String lockId) throws Exception {
String uniqueLockId = action+"_"+lockId;
InterProcessMutex lock = null;
if ((lock = this.locks.get(uniqueLockId)) != null) {
this.locks.remove(uniqueLockId);
lock.release();
}
}
/**
*
* <p>Descrption: 批量释放锁</p>
* @Author J
* @return void
* @param action
* @param lockId
* @throws Exception
*/
public void unBatchLock(String action, Collection<String> lockIds) {
for (String lockId : lockIds) {
try {
unlock(action, lockId);
} catch (Exception e) {
continue;
}
}
}
/**
*
* <p>Descrption: 并发批量解锁</p>
* @Author J
* @return void
* @param action
* @param lockIds
*/
public void unBatchParalleLock(String action, Collection<String> lockIds) {
// lockIds.parallelStream().limit(100).forEach(lockId -> {
for(String lockId : lockIds){
try {
unlock(action, lockId);
} catch (Exception e) {
// DO NOTHING
}
};
}
/**
*
* <p>Descrption: 关闭客户端</p>
* @Author J
* @return void
*/
public void close() {
if (this.client != null) {
CloseableUtils.closeQuietly(this.client);
}
}
}
@Autowired
private DistLock distLock;
@Test
public void lockTest() throws Exception {
try {
if(distLock.lock("testlock", "BatchInsertTest", 10)){
System.out.println(1);
}
} catch (Exception e) {
e.printStackTrace();
} finally {
distLock.unlock("kft", "BatchDebitKFT");
}
}
有了Curator,让zookeeper的分布式锁以及其他操作变得更方便,也不用在去自己造轮子啦。