zookeeper系列之【IdGenerator,简单原理】
zookeeper系列之【IdGenerator,多线程测试】
加锁,在单进程,多线程下,就是对某个线程,在某个时间段内,独占某个对象资源。这个对象资源就是锁。
多进程,或者集群环境下,加锁,同样是在某个时间段内,独占某个对象资源(或者某个数据资源)。不同的是,信息交换,需要走网络。
这边介绍下Zookeeper
实现分布式锁的方式。分公平锁和非公平锁。(锁的竞争实际上跟master
的竞争是一个道理)
公平锁原理
这个过程就跟排队买火车票是一样的。窗口(锁)只有一个,如果有10个人排队,队伍中的任何一个人都不用关心队伍有多长。他只需要知道2个信息,① 我前面的人完事没有。② 我愿意等多久。
① 前面的人完事了,那我自然就可以买了,要不然我就继续等着。
② 我只愿意等1分钟,那过了1分钟,还没等到,我就溜呗。
第①点说明,队伍中任何一个人只需要watch
前面的人,前面的人完事通知后面的人。
第②点说明,队伍中有人是可能中途放弃的,当这个人放弃之后,他后面的人watch
的对象就会变成放弃的人的前面的人。
1.首先提供这个锁的base
目录
2.我们把队伍中的没个人看做是准备获取锁的每个应用,并且给来排队的每个应用都来一个顺序唯一的编号。每个应用来排队的时候,在锁的base目录下创建EphemeralSequential
节点。然后把列表下排队的节点都get回去。看看自己排在哪边,如果是第一个,则直接算是获取锁了。要不然就watch他的前一个。然后等着。
目录结构
/fairlock/lock1
├── /0000000000
└── /0000000002
└── /0000000003
└── /0000000004
非公平锁原理
公平锁,分先来后到,要排队。
非公平锁,看运气,谁抢的快就是谁的,部分先后。(可能出现锁饥饿问题,某个节点动作总是比人家慢一点,怎么都抢不到锁)
做法:指定Zookeeper
上的锁节点(必须是还没有创建的),谁能第一个创建此节点,就算是获取了锁。
每个应用进来先尝试创建节点,如果成功,则算是获取锁。否则,watch
这个节点(看它啥时候被删除),当此节点被删除的时候,就算是锁被释放了。然后被通知到的所有其他应用就继续尝试创建此节点。谁先创建成功,锁就是谁的。否则继续等待(如果设置了超时时长,那到时间就放弃等待,取消watch
)。
目录结构
/unfairlock/lock1
代码实现
public interface ILock {
/**
* 试图获取锁,不做等待
*/
boolean tryLock();
/**
* 无限制等待
*/
void lock();
/**
* 限制时长等待锁
* @param time
* @param timeUnit
* @return
*/
boolean lock(long time, TimeUnit timeUnit);
/**
* 释放锁
*/
void releaseLock();
}
public class FairLock implements ILock {
// 客户端持有
private ZkClient client;
// base目录+"/lockName"
private String lockPath;
// lockPath+"/"
private String lockPathSon;
// 当前持有的锁的全路径
private String currentLockPath;
public FairLock(ZkClient client, String basePath, String lockName) {
this.client = client;
if (!client.exists(basePath)) {
client.createPersistent(basePath, true);
}
lockPath = basePath.concat("/").concat(lockName);
if (!client.exists(lockPath)) {
client.createPersistent(lockPath);
}
lockPathSon = lockPath.concat("/");
}
@Override
public boolean tryLock() {
return this.getLock(0, TimeUnit.MILLISECONDS);
}
@Override
public void lock() {
this.getLock(0, null);
}
@Override
public boolean lock(long time, TimeUnit timeUnit) {
return this.getLock(time, timeUnit);
}
private boolean getLock(long time, TimeUnit timeUnit) {
// 进来就先创建临时顺序节点
currentLockPath = client.createEphemeralSequential(lockPathSon, null);
return this.waitLock(time, timeUnit);
}
private boolean waitLock(long time, TimeUnit timeUnit) {
long start = System.currentTimeMillis();
long waitMillos = timeUnit == null ? 0 : timeUnit.toMillis(time);// 后续要等待的时间
List<String> children = client.getChildren(lockPath);
Collections.sort(children);
String nodeName = currentLockPath.substring(currentLockPath.lastIndexOf("/") + 1);
boolean hasLock = nodeName.equals(children.get(0));// 判断自己是不是第一个
if (hasLock) {
return true;
}
if (timeUnit != null && waitMillos <= 0) {
client.delete(currentLockPath); // 超时放弃,则删除自己的节点
return false;
}
int idx = Collections.binarySearch(children, nodeName);
String watchPath = lockPathSon.concat(children.get(idx - 1));
CountDownLatch countDownLatch = new CountDownLatch(1);
IZkDataListener dataListener = new IZkDataListener() {
@Override
public void handleDataChange(String dataPath, Object data) throws Exception {
}
@Override
public void handleDataDeleted(String dataPath) throws Exception {
countDownLatch.countDown();
}
};
client.subscribeDataChanges(watchPath, dataListener);// 找到自己的前一个节点关注上
try {
if (timeUnit == null) {
countDownLatch.await();
} else {
countDownLatch.await(waitMillos - (System.currentTimeMillis() - start), TimeUnit.MILLISECONDS);
}
} catch (InterruptedException e) {
e.printStackTrace();
}
client.unsubscribeDataChanges(watchPath, dataListener);// 拿到了锁或者超时,取消关注
return this.waitLock(waitMillos - (System.currentTimeMillis() - start), TimeUnit.MILLISECONDS);// 递归等待
}
@Override
public void releaseLock() {
client.delete(currentLockPath);
}
public class UnFairLock implements ILock {
// 客户端持有
private ZkClient client;
// base目录
private String lockPath;
private CountDownLatch countDownLatch;
private IZkDataListener dataListener = new IZkDataListener() {
@Override
public void handleDataChange(String dataPath, Object data) throws Exception {
}
@Override
public void handleDataDeleted(String dataPath) throws Exception {
boolean hasLock = takeLock();
if (hasLock) {// 只有真正拿到了锁才通知
countDownLatch.countDown();
}
}
};
public UnFairLock(ZkClient client, String basePath, String lockName) {
this.client = client;
if (!client.exists(basePath)) {
client.createPersistent(basePath, true);
}
lockPath = basePath.concat("/").concat(lockName);
}
@Override
public boolean tryLock() {
return this.getLock(0, TimeUnit.MILLISECONDS);
}
@Override
public void lock() {
this.getLock(0, null);
}
@Override
public boolean lock(long time, TimeUnit timeUnit) {
return this.getLock(time, timeUnit);
}
private boolean takeLock() {
try {
client.createEphemeral(lockPath);
return true;// 创建目录成功,就算是拿到了锁
} catch (ZkNodeExistsException ignored) {
return false;
}
}
private boolean getLock(long time, TimeUnit timeUnit) {
long start = System.currentTimeMillis();
long waitMillos = timeUnit == null ? 0 : timeUnit.toMillis(time);
boolean hasLock = this.takeLock();
if (hasLock) {
return true;
}
if (timeUnit != null && waitMillos <= 0) {
return false;
}
countDownLatch = new CountDownLatch(1);
client.subscribeDataChanges(lockPath, dataListener);
boolean res = true;
try {
if (timeUnit == null) {// 永久等待
countDownLatch.await();
} else {
// 超时情况有可能返回false
res = countDownLatch.await(waitMillos - (System.currentTimeMillis() - start), TimeUnit.MILLISECONDS);
}
} catch (InterruptedException e) {
e.printStackTrace();
return false;
}
client.unsubscribeDataChanges(lockPath, dataListener);
return res;
}
@Override
public void releaseLock() {
client.delete(lockPath);
}
}
public class TestClient {
private static final int THREAD_NUM = 10;
public static void main(String[] args) throws InterruptedException {
// 线程池
ExecutorService executorService = new ThreadPoolExecutor(10, 100, 0, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<>(200), new ThreadFactory() {
private final AtomicInteger threadNum = new AtomicInteger(0);
@Override
public Thread newThread(Runnable runnable) {
return new Thread(runnable, "pool-1-thread-" + threadNum.incrementAndGet());
}
});
CountDownLatch countDownLatch = new CountDownLatch(THREAD_NUM);
for (int i = 0; i < THREAD_NUM; i++) {
executorService.execute(new Runnable() {
@Override
public void run() {
ZkClient client = new ZkClient("localhost:2181,localhost:2182,localhost:2183", 5000, 5000, new SerializableSerializer());
ILock fairLock = new FairLock(client, "/fairlock", "lock1");// 公平锁
// ILock unFairLock = new UnFairLock(client, "/unfairlock", "lock1");// 非公平锁
try {
fairLock.lock();// 上锁
System.out.println(Thread.currentThread().getName() + " get lock");
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
fairLock.releaseLock();// 释放锁
System.out.println(Thread.currentThread().getName() + " release lock");
}
countDownLatch.countDown();
}
});
}
countDownLatch.await();
executorService.shutdown();
}
}