elasticsearch集群的建立和维护主要包括以下几个部分:
1、结点发现
2、选主
3、集群建立
4、ClusterState更新与同步
5、集群健康检测
1 节点发现
要形成集群,每个节点需要知道集群中有哪些节点。elasticsearch通过gossip协议发现集群中有哪些节点
1.1 什么时机启动节点发现过程
elasticsearch
节点发现的代码主要在discovery
目录下,实现了一个默认的节点发现机制ZenDiscovery
,elasticsearch Node
初始化完成后,在它的start()
方法中会调用discovery.start()
方法
ZenDiscovery.start()
方法会调用doStart()
方法,在这个方法中,会初始化ClusterState,ClusterState维护了整个集群的状态。初始时,ClusterState中只有一个节点,就是节点自身。
然后Node.start()
方法调用discovery.startInitialJoin()
开始节点发现过程。
discovery.startInitialJoin()
最终会调用ZenDiscovery.innerJoinCluster()
方法,这个方法实现了节点发现的主流程。这个方法中,首先是一个while循环,如果没有选举出master,则执行findMaster()
java
while (masterNode == null && joinThreadControl.joinThreadActive(currentThread)) {
// 选主,如果没有选择出主,则继续进行
masterNode = findMaster();
}
然后分为两种情况: 1)如果自己是master,则计算集群需要的最少节点数
arduino
// 需要加入的节点数
final int requiredJoins = Math.max(0, electMaster.minimumMasterNodes() - 1); // we count as one
然后在masterElectionWaitForJoinsTimeout
时间内等待requiredJoins个节点加入集群。如果成功建立集群,则结束;否则重新开始一个新的集群建立过程
2)如果自己是slave,则进入加入集群的流程
ini
final boolean success = joinElectedMaster(masterNode);
同样,如果加入成功,则结束;否则,重新开始一个新的集群建立过程
findMaster()
java
// ping 其他的node,发现集群中所有的节点,每一个pingresponse表示一个发现的节点
List<ZenPing.PingResponse> fullPingResponses = pingAndWait(pingTimeout).toList();
pingAndWait()
会调用UnicastZenPing.ping()
方法ping其他的节点,返回发现的所有节点。
seedNodes
的获取: 1)节点启动的配置文件中获取,即elasticsearch.yml
中配置项discovery.zen.ping.unicast.hosts
配置的节点 2)ClusterState中记录的已经发现的Master节点 3)近期ping自己的节点。。这些节点保存在变量中temporalResponses
中,temporalResponses
中的节点会在接收到它之后的2倍的超时时间后删除
java
public static class UnicastPingRequest extends TransportRequest {
int id;
TimeValue timeout;
PingResponse pingResponse;
}
class PingResponse implements Streamable {
private long id;
private ClusterName clusterName;
private DiscoveryNode node;
private DiscoveryNode master;
private long clusterStateVersion;
}
也就是说,发送ping报文的节点会告诉被ping的节点:自己集群的名字、自己的节点、自己选择的master节点和自己clusterstate的版本号
UnicastZenPing
构造方法中注册了ping报文的处理handler
java
// 注册处理unicast ping 请求报文的handler
transportService.registerRequestHandler(ACTION_NAME, UnicastPingRequest::new, ThreadPool.Names.SAME,
new UnicastPingRequestHandler());
如果ping报文的集群与收到ping报文节点的集群是一样的,则会把自己发现的所有节点作为响应发现给对方
java
static class UnicastPingResponse extends TransportResponse {
int id;
PingResponse[] pingResponses;
}
是一个PingResponse的列表,每一个PingResponse代表一个节点,这个列表的节点来源于: 1)接收到ping的节点自己 2)最近一段时间ping自己的节点。这些节点保存在变量中temporalResponses
中,temporalResponses
中的节点会在接收到它之后的2倍的超时时间后删除
一个PingRound会ping三轮:
java
// 第一次
threadPool.generic().execute(pingSender);
// 三分之一周期后第二次
threadPool.schedule(TimeValue.timeValueMillis(scheduleDuration.millis() / 3), ThreadPool.Names.GENERIC, pingSender);
// 三分之二周期后第三次
threadPool.schedule(TimeValue.timeValueMillis(scheduleDuration.millis() / 3 * 2), ThreadPool.Names.GENERIC, pingSender);
// 结束
threadPool.schedule(scheduleDuration, ThreadPool.Names.GENERIC, new AbstractRunnable() {
@Override
protected void doRun() throws Exception {
finishPingingRound(pingingRound);
}
@Override
public void onFailure(Exception e) {
logger.warn("unexpected error while finishing pinging round", e);
}
});
PingRound结束后,节点会根据自己接收到的PingResponse进行选主
2 选主
PingRound结束后,节点会根据自己接收到的PingResponse进行选主 首先,在这些接收到的PingResponse中,会筛除掉自己和非master节点
下面分两种情况: 1)所有的PingResponse的master node都是空,也就是说当前集群中没有master 则根据下面规则选择一个master: a)所有的PingResponse选择一个clusterStateVersion最大的节点 b)如果最大的clusterStateVersion节点有多个,则从这多个节点中选择node id最小的一个
ElectMasterService
java
public static int compare(MasterCandidate c1, MasterCandidate c2) {
// we explicitly swap c1 and c2 here. the code expects "better" is lower in a sorted
// list, so if c2 has a higher cluster state version, it needs to come first.
int ret = Long.compare(c2.clusterStateVersion, c1.clusterStateVersion);
if (ret == 0) {
ret = compareNodes(c1.getNode(), c2.getNode());
}
return ret;
}
- 如果PingResponse中存在一个或多个master,则从中选择一个节点作为master,选择的规则与上面相同
3 集群建立
master选择出来后,master节点等待其他节点发送JoinRequest
给自己,而slave节点会向master节点发送JoinRequest
。
java
public static class JoinRequest extends TransportRequest {
DiscoveryNode node;
}
JoinRequest
报文只包括一个信息,就是告诉master,想加入集群的是哪个节点
slave节点在发送完JoinRequest
后,会等待响应,如果响应是NotMasterException
,即对方认为自己并不是master,这时可能是对方还没有把自己选举为master,所以slave节点会稍等一段时间后重试。如果尝试了joinRetryAttempts
次后还没有成功,或者确定对方确实不是master或遇到了其他的问题,则重新启动集群建立过程。
master节点在接收到JoinRequest
后,并不会立即响应slave节点,因为master需要接收到minimum_master_nodes - 1
个master node的JoinRequest
,并把新的cluster state同步到这些节点后,才认为自己是真正的master
在MembershipAction
构造方法中注册了JoinRequest
,joinValidators
,LeaveRequest
报文的处理handler
master接收到JoinRequest
之后,通过MembershipAction.JoinRequestRequestHandler
最后会调用ZenDiscovery.MembershipListener.onJoin()
方法,这个方法最终会向slave节点发送ValidateJoinRequest
给slave节点,而slave节点目前没有做什么校验,而是直接返回response。
然后调用NodeJoinController.handleJoinRequest()
真正处理JoinRequest
,这个方法调用中,会把JoinRequest消息及它的回调处理方法添加到
swift
// MembershipAction.JoinCallback 的处理为 向 发送 JoinRequest 的节点发送响应
private final Map<DiscoveryNode, List<MembershipAction.JoinCallback>> joinRequestAccumulator = new HashMap<>();
而回调方法即是发送响应报文
然后调用NodeJoinController.checkPendingJoinsAndElectIfNeeded()
判断join的节点数是否达到要求
java
public synchronized boolean isEnoughPendingJoins(int pendingMasterJoins) {
final boolean hasEnough;
if (requiredMasterJoins < 0) {
// requiredMasterNodes is unknown yet, return false and keep on waiting
hasEnough = false;
} else {
assert callback != null : "requiredMasterJoins is set but not the callback";
hasEnough = pendingMasterJoins >= requiredMasterJoins;
}
return hasEnough;
}
如果已达到要求数量的node,则调用ElectionContext.closeAndBecomeMaster()
,然后将JoinRequest的callback方法,以及JoinCallBack封装到JoinTaskListener中,同时还将electionFinishedListener
封装到electionFinishedListener
中,这些任务都提交给MasterService
bash
masterService.submitStateUpdateTasks(source, tasks, ClusterStateTaskConfig.build(Priority.URGENT), joinTaskExecutor);
MasterService
会把这些任务封装成UpdateTask
,然后提交给线程池中执行。
JoinCallBack封装到JoinTaskLister,ElectionCallBack封装到ClusterStateTaskListener
ElectionCallBack 定义在 ZenDiscovery.innerJoinCluster()方法中 JoinCallback 定义在MembershipAction.JoinRequestRequestHandler 内部类中
然后又将 JoinTaskListener(ClusterStateTaskListener)封装到 SafeClusterStateTaskListener、
SafeClusterStateTaskListener和JoinTaskExecutor封装到 TaskBatcher.UpdateTask extends BatchedTask
4 ClusterState更新与同步
通过MasterService.submit()
提交ClusterState 更新及同步任务。最后会执行TaskBatcher.runIfNotProcessed()
执行提交任务。这个方法又会调用MasterService.Batcher.run()
方法,
将任务封装进TaskInputs
, 然后执行runTasks(TaskInputs taskInputs)
方法,这个方法中,执行task batcher,计算更新后的ClusterState,同时ClusterState的版本号增加1
ini
// 执行task后的新clusterstate
TaskOutputs taskOutputs = calculateTaskOutputs(taskInputs, previousClusterState, startTimeNS);
然后将ClusterState变化构造ClusterChangedEvent
发送给集群中其他节点
ini
final ClusterState previousClusterState = state();
ClusterState newClusterState = taskOutputs.newClusterState;
// publish new clusterstate 给其他节点
clusterStatePublisher.accept(clusterChangedEvent, taskOutputs.createAckListener(threadPool, newClusterState));
publish完成后,调用前面封装到UpdateTask中的回调方法
scss
// 调用回调方法,JoinCallBack和ElectionCallBack
taskOutputs.processedDifferentClusterState(previousClusterState, newClusterState);
publish过程: 上面clusterStatePublisher.accept()
实际调用的是ZenDiscovery.publish()
方法,这个方法的关键内容
java
public void publish(ClusterChangedEvent clusterChangedEvent, AckListener ackListener) {
// 检查ClusterState合法性
if (clusterChangedEvent.previousState() != this.committedState.get()) {
throw new FailedToCommitClusterStateException("state was mutated while calculating new CS update");
}
// 待发布的 ClusterState 入队列;master和slave使用的都是这个队列
pendingStatesQueue.addPending(newState);
try {
// 把 ClusterState 发布给其他节点
publishClusterState.publish(clusterChangedEvent, electMaster.minimumMasterNodes(), ackListener);
} catch (FailedToCommitClusterStateException t) {
// 省略
}
final DiscoveryNode localNode = newState.getNodes().getLocalNode();
final CountDownLatch latch = new CountDownLatch(1);
final AtomicBoolean processedOrFailed = new AtomicBoolean();
// 标记为已提交
pendingStatesQueue.markAsCommitted();
synchronized (stateMutex) {
// 更新 ClusterState 的内容为新ClusterState的内容,并更新 MasterFaultDetection 和 NodsFaultDetection 中的节点
boolean sentToApplier = processNextCommittedClusterState("master " + newState.nodes().getMasterNode() +
" committed version [" + newState.version() + "] source [" + clusterChangedEvent.source() + "]");
}
// indefinitely wait for cluster state to be applied locally
try {
latch.await();
} catch (InterruptedException e) {
}
}
publish cluster state 最终会调用 PublishClusterStateAction.innerPublish()
, 通过BytesTransportRequest
报文发送给其他节点
PublishClusterStateAction
构造方法注册了BytesTransportRequest
和CommitClusterStateRequest
的handler,
BytesTransportRequest
的handler会调用Zendiscovery.onIncomingClusterState()
方法,
java
/**
* 接收到 ClusterState 同步消息后,调用的处理方法
* @param incomingState
*/
@Override
public void onIncomingClusterState(ClusterState incomingState) {
// 检查 版本号
validateIncomingState(logger, incomingState, committedState.get());
pendingStatesQueue.addPending(incomingState);
}
检查clusterstate的版本号需要比自己的clusterstate的版本号大,然后添加到pendingStatesQueue
中,然后发送一条响应消息给master
master如果已经接收到了minimum_master_nodes
个确认消息,标记这个clusterstate更新已提交,然后想其他节点发送 CommitClusterStateRequest
消息
节点接收到master的commit消息后,会调用ZenDiscovery.onClusterStateCommitted()
方法,将该clusterstate更新标记为commit,并修改本地的cluster state
java
/**
* ClusterState commit 处理方法
* @param stateUUID
* @param processedListener
*/
@Override
public void onClusterStateCommitted(String stateUUID, ActionListener<Void> processedListener) {
// 标记为已提交
final ClusterState state = pendingStatesQueue.markAsCommitted(stateUUID,
new PendingClusterStatesQueue.StateProcessedListener() {
@Override
public void onNewClusterStateProcessed() {
processedListener.onResponse(null);
}
@Override
public void onNewClusterStateFailed(Exception e) {
processedListener.onFailure(e);
}
});
if (state != null) {
synchronized (stateMutex) {
// 更新 ClusterState
processNextCommittedClusterState("master " + state.nodes().getMasterNode() +
" committed version [" + state.version() + "]");
}
}
}
5 集群健康检测
MasterFaultDetection
slave node ping master node,检测master node的健康状态
NodesFaultDetection
master node ping slave nodes,检测slave node的健康状态
ClusterState 提交后,在DenDiscovery.processNextCommittedClusterState()
方法中会执行下面语句:
java
if (adaptedNewClusterState.nodes().isLocalNodeElectedMaster()) {
// update the set of nodes to ping
nodesFD.updateNodesAndPing(adaptedNewClusterState);
} else {
// check to see that we monitor the correct master of the cluster
if (masterFD.masterNode() == null || !masterFD.masterNode().equals(adaptedNewClusterState.nodes().getMasterNode())) {
masterFD.restart(adaptedNewClusterState.nodes().getMasterNode(),
"new cluster state received and we are monitoring the wrong master [" + masterFD.masterNode() + "]");
}
}
MasterFaultDetection.innerStart()
会周期性的调用MasterPinger.run()
, MasterPinger.run()
会向master发送MasterPingRequest
,数据格式:
java
public static class MasterPingRequest extends TransportRequest {
private DiscoveryNode sourceNode;
private DiscoveryNode masterNode;
private ClusterName clusterName;
}
MasterFaultDetection
构造方法中注册了MasterPingRequest
的handler。 master接收到MasterPingRequest
后,会检查自己是否是master以及集群名称是否匹配,然后发送MasterPingResponseResponse
。 如果slave发现master健康检测失败,则会调用它的listener,在ZenDiscovery
构造方法中注册的listener为MasterNodeFailureListener
, 处理master gone的处理方法:
java
private void handleMasterGone(final DiscoveryNode masterNode, final Throwable cause, final String reason) {
if (lifecycleState() != Lifecycle.State.STARTED) {
// not started, ignore a master failure
return;
}
if (localNodeMaster()) {
// we might get this on both a master telling us shutting down, and then the disconnect failure
return;
}
logger.info((Supplier<?>) () -> new ParameterizedMessage("master_left [{}], reason [{}]", masterNode, reason), cause);
synchronized (stateMutex) {
if (localNodeMaster() == false && masterNode.equals(committedState.get().nodes().getMasterNode())) {
// flush any pending cluster states from old master, so it will not be set as master again
pendingStatesQueue.failAllStatesAndClear(new ElasticsearchException("master left [{}]", reason));
rejoin("master left (reason = " + reason + ")");
}
}
}
NodesFaultDetection.updateNodesAndPing()
周期性调用 NodeFD.run()
方法,向slave节点发送PingRequest
消息。如果发现slave node不可以达,则移除node。 NodesFaultDetection
的构造方法注册了PingRequest
消息的handler