zookeeper系列之【IdGenerator,简单原理】
zookeeper系列之【IdGenerator,多线程测试】
为避免单点问题,采用master-slave机制的分布式框架,都需要保证master宕机的时候,standby的机器能接上继续工作,hadoop的namenode就是这样。
这边介绍下Zookeeper实现master选举的方式。分公平选举和非公平选举。(实际上跟锁竞争是一个道理)
公平选举方式原理
这个过程就跟排队买火车票是一样的。窗口(master)只有一个,如果有10个人排队,队伍中的任何一个人都不用关心队伍有多长。他只需要知道2个信息,① 我前面的人完事没有。② 我愿意等多久。
① 前面的人完事了,那我自然就可以买了,要不然我就继续等着。
② 我只愿意等1分钟,那过了1分钟,还没等到,我就溜呗。
第①点说明,队伍中任何一个人只需要watch前面的人,前面的人完事通知后面的人。
第②点说明,队伍中有人是可能中途放弃的,当这个人放弃之后,他后面的人watch的对象就会变成放弃的人的前面的人。
1.首先提供base目录
2.我们把队伍中的每个人看做是准备选举master的每个应用,并且给来排队的每个应用都来一个顺序唯一的编号。每个应用来排队的时候,在master的base目录下创建EphemeralSequential节点。然后把列表下排队的节点都get回去。看看自己排在哪边,如果是第一个,则直接算是选举master成功了。要不然就watch他的前一个。然后等着。
目录结构
/master/fairmaster
├── /0000000000
└── /0000000002
└── /0000000003
└── /0000000004
非公平选举原理
公平选举方式,分先来后到,要排队。
非公平选举,看运气,谁抢的快就是谁的,部分先后。(可能出现锁饥饿问题,某个节点动作总是比人家慢一点,怎么都抢不到锁)
做法:指定Zookeeper上的master节点(必须是还没有创建的),谁能第一个创建此节点,就算是竞选成功。
每个应用进来先尝试创建节点,如果成功,则算是竞选成功。否则,watch这个节点(看它啥时候被删除),当此节点被删除的时候,就算是master宕机了。然后被通知到的所有其他应用就继续尝试创建此节点。谁先创建成功,master就是谁的。否则继续等待(如果设置了超时时长,那到时间就放弃等待,取消watch)。
目录结构
/master/unfairmaster
代码实现
public interface IChoiceMaster {
/**
* 选举,无限制等待
*/
void choice();
/**
* 选举,有时间限制
* @param time
* @param timeUnit
* @return
*/
boolean choice(long time, TimeUnit timeUnit);
}
public class FariMaster implements IChoiceMaster {
// 客户端持有
private ZkClient client;
// 基础目录
private String basePath;
// 基础目录/
private String masterParentPath;
// 基础目录/0000001
private String masterPath;
public FariMaster(ZkClient client, String basePath) {
this.client = client;
this.basePath = basePath;
if (!client.exists(basePath)) {
client.createPersistent(basePath, true);
}
masterParentPath = basePath.concat("/");
}
@Override
public void choice() {
this.getMaster(0, null);
}
@Override
public boolean choice(long time, TimeUnit timeUnit) {
return this.getMaster(time, timeUnit);
}
private boolean getMaster(long time, TimeUnit timeUnit) {
// 进来就先创建临时顺序节点
masterPath = client.createEphemeralSequential(masterParentPath, null);
return this.waitMaster(time, timeUnit);
}
private boolean waitMaster(long time, TimeUnit timeUnit) {
long start = System.currentTimeMillis();
long waitMillos = timeUnit == null ? 0 : timeUnit.toMillis(time);// 后续要等待的时间
List<String> children = client.getChildren(basePath);
Collections.sort(children);
String nodeName = masterPath.substring(masterPath.lastIndexOf("/") + 1);
boolean isMaster = nodeName.equals(children.get(0));// 判断自己是不是第一个
if (isMaster) {
return true;
}
if (timeUnit != null && waitMillos <= 0) {
client.delete(masterPath); // 超时放弃,则删除自己的节点
return false;
}
int idx = Collections.binarySearch(children, nodeName);
String watchPath = masterParentPath.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);// 拿到了master或者超时,取消关注
return this.waitMaster(waitMillos - (System.currentTimeMillis() - start), TimeUnit.MILLISECONDS);// 递归等待
}
}
public class UnFairMaster implements IChoiceMaster {
// 客户端持有
private ZkClient client;
// 基础目录
private String basePath;
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 = takeMaster();
if (hasLock) {// 只有真正得到了master才通知
countDownLatch.countDown();
}
}
};
public UnFairMaster(ZkClient client, String basePath) {
this.client = client;
this.basePath = basePath;
String parentPath = basePath.substring(0, basePath.lastIndexOf("/"));
if (!client.exists(parentPath)) {
client.createPersistent(parentPath, true);
}
}
@Override
public void choice() {
this.getMaster(0, null);
}
@Override
public boolean choice(long time, TimeUnit timeUnit) {
return this.getMaster(time, timeUnit);
}
private boolean takeMaster() {
try {
client.createEphemeral(basePath);
return true;// 创建目录成功,就算是拿到了master
} catch (ZkNodeExistsException ignored) {
return false;
}
}
private boolean getMaster(long time, TimeUnit timeUnit) {
long start = System.currentTimeMillis();
long waitMillos = timeUnit == null ? 0 : timeUnit.toMillis(time);
boolean isMaster = this.takeMaster();
if (isMaster) {
return true;
}
if (timeUnit != null && waitMillos <= 0) {
return false;
}
countDownLatch = new CountDownLatch(1);
client.subscribeDataChanges(basePath, 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(basePath, dataListener);
return res;
}
}
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());
}
});
AtomicReference<Thread> currentMasterClient = new AtomicReference<>();
Semaphore semaphore = new Semaphore(THREAD_NUM);
// 每隔3秒把选举到master的线程打断,这样就模拟了服务器宕机的情况
Executors.newScheduledThreadPool(1).scheduleWithFixedDelay(new Runnable() {
@Override
public void run() {
Thread masterThread = currentMasterClient.get();
masterThread.interrupt();
semaphore.release();
}
}, 3, 3, TimeUnit.SECONDS);
while (true) {
semaphore.acquire();// 每次有master宕机后,再弄一个线程出来,就算是重启了
executorService.execute(new Runnable() {
@Override
public void run() {
ZkClient client = new ZkClient("localhost:2181,localhost:2182,localhost:2183", 5000, 5000, new SerializableSerializer());
// IChoiceMaster choiceMaster = new UnFairMaster(client, "/master/unfairmaster");
IChoiceMaster choiceMaster = new FariMaster(client, "/master/fairmaster");
String threadName = Thread.currentThread().getName();
try {
choiceMaster.choice();// 选举操作,会等待
currentMasterClient.set(Thread.currentThread());// 选举上的线程会被拿出来,另一个schedule线程每隔3秒会把这个线程给打断。
System.out.println(threadName + " get master");
Thread.sleep(200000);
} catch (Exception e) {
System.out.println(threadName + " 宕机了");
} finally {
client.close();
System.out.println(threadName + " 线程结束");
}
}
});
}
}
}