elasticsearch集群建立源码分析

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;
}
  1. 如果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构造方法中注册了JoinRequestjoinValidatorsLeaveRequest报文的处理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构造方法注册了BytesTransportRequestCommitClusterStateRequest的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

相关推荐
XiaoLeisj2 小时前
【JavaEE初阶 — 多线程】单例模式 & 指令重排序问题
java·开发语言·java-ee
paopaokaka_luck2 小时前
【360】基于springboot的志愿服务管理系统
java·spring boot·后端·spring·毕业设计
dayouziei2 小时前
java的类加载机制的学习
java·学习
码农小旋风3 小时前
详解K8S--声明式API
后端
Peter_chq3 小时前
【操作系统】基于环形队列的生产消费模型
linux·c语言·开发语言·c++·后端
Yaml44 小时前
Spring Boot 与 Vue 共筑二手书籍交易卓越平台
java·spring boot·后端·mysql·spring·vue·二手书籍
小小小妮子~4 小时前
Spring Boot详解:从入门到精通
java·spring boot·后端
hong1616884 小时前
Spring Boot中实现多数据源连接和切换的方案
java·spring boot·后端
aloha_7894 小时前
从零记录搭建一个干净的mybatis环境
java·笔记·spring·spring cloud·maven·mybatis·springboot
记录成长java5 小时前
ServletContext,Cookie,HttpSession的使用
java·开发语言·servlet