zookeeper实现master选举

985 阅读5分钟

zookeeper系列之【IdGenerator,简单原理】

zookeeper系列之【IdGenerator,多线程测试】

zookeeper系列之【分布式锁】

zookeeper系列之【master选举】


为避免单点问题,采用master-slave机制的分布式框架,都需要保证master宕机的时候,standby的机器能接上继续工作,hadoopnamenode就是这样。

这边介绍下Zookeeper实现master选举的方式。分公平选举和非公平选举。(实际上跟锁竞争是一个道理)

公平选举方式原理

这个过程就跟排队买火车票是一样的。窗口(master)只有一个,如果有10个人排队,队伍中的任何一个人都不用关心队伍有多长。他只需要知道2个信息,① 我前面的人完事没有。② 我愿意等多久。

① 前面的人完事了,那我自然就可以买了,要不然我就继续等着。

② 我只愿意等1分钟,那过了1分钟,还没等到,我就溜呗。

第①点说明,队伍中任何一个人只需要watch前面的人,前面的人完事通知后面的人。

第②点说明,队伍中有人是可能中途放弃的,当这个人放弃之后,他后面的人watch的对象就会变成放弃的人的前面的人。

1.首先提供base目录

2.我们把队伍中的每个人看做是准备选举master的每个应用,并且给来排队的每个应用都来一个顺序唯一的编号。每个应用来排队的时候,在masterbase目录下创建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 + " 线程结束");
					}
				}
			});
		}
	}
}