选举的使用场景
在分布式环境下,为了保证系统的高可用性,我们必须消除单点,其中一个主要的解决方案就是建立集群。集群有多个提供相同服务的机器组成,在这样场景下,又会出现一致性问题,原因是我们无法保证多台机子上的数据是一致的。在这种情况下,我们可以通过一主多从的方案,通过主节点来进行读写操作,从节点只进行读操作的方式,通过主从复制来尽可能保证一致性。那么如何来决定谁来当主节点呢?
这就是我们今天要讲的 — 选举
设计模式
在zookeeper选举的逻辑中,由于涉及到诸多的状态,zookeeper采用了观察者模式,来执行不同状态下的回调,实现了对 回调逻辑 与 主流程之间的耦合,即如果我要增加当zookeeper选举结束后的逻辑,此时我不需要修改zookeeper选举的主流程代码。
源码解读
public synchronized void start() {
// 执行选举开始前的回调
state = State.START;
dispatchEvent(EventType.START);
// 非空校验
if (zooKeeper == null) {
throw new IllegalStateException(
"No instance of zookeeper provided. Hint: use setZooKeeper()");
}
if (hostName == null) {
throw new IllegalStateException(
"No hostname provided. Hint: use setHostName()");
}
try {
// 发起投票
makeOffer();
// 决定选举结果
determineElectionStatus();
} catch (KeeperException | InterruptedException e) {
becomeFailed(e);
}
}
发起投票:
private void makeOffer() throws KeeperException, InterruptedException {
// 执行投票开始的回调
state = State.OFFER;
dispatchEvent(EventType.OFFER_START);
LeaderOffer newLeaderOffer = new LeaderOffer();
byte[] hostnameBytes;
synchronized (this) {
newLeaderOffer.setHostName(hostName);
hostnameBytes = hostName.getBytes();
// 开始投票,创建临时顺序节点,这里是选举的关键,保证了多个节点创建的临时节点是不一样的,按顺序的
newLeaderOffer.setNodePath(zooKeeper.create(rootNodeName + "/" + "n_",
hostnameBytes, ZooDefs.Ids.OPEN_ACL_UNSAFE,
CreateMode.EPHEMERAL_SEQUENTIAL));
leaderOffer = newLeaderOffer;
}
LOG.debug("Created leader offer {}", leaderOffer);
// 执行投票结束的回调
dispatchEvent(EventType.OFFER_COMPLETE);
}
决定选举结果
private void determineElectionStatus() throws KeeperException, InterruptedException {
// 执行选举开始的回调
state = State.DETERMINE;
dispatchEvent(EventType.DETERMINE_START);
// 获取当前节点
LeaderOffer currentLeaderOffer = getLeaderOffer();
String[] components = currentLeaderOffer.getNodePath().split("/");
// id为hostName
currentLeaderOffer.setId(Integer.valueOf(components[components.length - 1].substring("n_".length())));
// 收集选票,获取投票箱内的选票信息(这里的票是没有顺序的)
List<LeaderOffer> leaderOffers = toLeaderOffers(zooKeeper.getChildren(rootNodeName, false));
/*
* For each leader offer, find out where we fit in. If we're first, we
* become the leader. If we're not elected the leader, attempt to stat the
* offer just less than us. If they exist, watch for their failure, but if
* they don't, become the leader.
*/
// 唱票阶段
for (int i = 0; i < leaderOffers.size(); i++) {
LeaderOffer leaderOffer = leaderOffers.get(i);
if (leaderOffer.getId().equals(currentLeaderOffer.getId())) {
// 执行选举结束回调
dispatchEvent(EventType.DETERMINE_COMPLETE);
// 如果你是第一张票,你就是leader,否则就是follower
if (i == 0) {
// 成为leader
becomeLeader();
} else {
// 成为follower
becomeReady(leaderOffers.get(i - 1));
}
/* Once we've figured out where we are, we're done. */
break;
}
}
}
我们来看看成为leader和成为follower之后的逻辑:
// 成为leader的逻辑很简单,什么都不做,只执行可能存在的回调操作
private void becomeLeader() {
state = State.ELECTED;
dispatchEvent(EventType.ELECTED_START);
LOG.info("Becoming leader with node: {}", getLeaderOffer().getNodePath());
dispatchEvent(EventType.ELECTED_COMPLETE);
}
private void becomeReady(LeaderOffer neighborLeaderOffer) throws KeeperException, InterruptedException {
// 对前一个邻居节点创建监听,当前一个邻居节点宕机,则会执行回调.(下面会将回调内容)
Stat stat = zooKeeper.exists(neighborLeaderOffer.getNodePath(), this);
if (stat != null) {
// 创建监听成功,执行回调
dispatchEvent(EventType.READY_START);
state = State.READY;
dispatchEvent(EventType.READY_COMPLETE);
} else {
/*
* If the stat fails, the node has gone missing between the call to
* getChildren() and exists(). We need to try and become the leader.
*/
// 如果创建监听失败,我们认为前一个相邻节点可能在选举过程中被删除了,我们需要重新选举
determineElectionStatus();
}
}
我们来看一下从节点对前一个相邻节点的监听回调:
类LeaderElectionSupport是实现了Watch接口的,里面的process实现如下:
@Override
public void process(WatchedEvent event) {
// 如果监听到前面的相邻节点挂掉了(被移除了)
if (event.getType().equals(Watcher.Event.EventType.NodeDeleted)) {
// 一般都不相等
if (!event.getPath().equals(getLeaderOffer().getNodePath())
&& state != State.STOP) {
LOG.debug(
"Node {} deleted. Need to run through the election process.",
event.getPath());
try {
// 尝试进行重新选举主节点
determineElectionStatus();
} catch (KeeperException | InterruptedException e) {
becomeFailed(e);
}
}
}
}
可见,在zookeeper集群中,即使一个从节点挂掉了,zookeeper集群也会进行重新选举。