elasticsearch源码分析之集群服务(八)

为什么集群

集群是为一组互联的完整计算机,一起作为一个统一的计算资源而工作,给人以一台机器的感觉。
集群有三大优点,所以很多系统都以集群的形式出现:

  • 高性价比:使用普通的计算机来构建集群,能够以非常低的价格,获得比大型机更高的性能。
  • 高可用性:因为集群中的每个节点都是一台独立的计算机(或者部署在其上),集群服务都有容错能力,所以某一个节点的故障不意味着集群服务的失败。
  • 可伸缩性:可以通过横向调整集群节点的伸缩,实现集群的服务能力。

elasticsearch以集群的形式服务也是基于上面原因。

集群模型

分布式集群业界一般有两种模型,中心化集群和去中心化集群。

  • 中心化:中心化集群包含有主节点,用来对非主节点进行协调;非主节点负责任务执行。如hadoop。
  • 去中心化:去中心化化集群实现比较复杂,所有节点都对等的,如 Cassandra、Redis-cluster。

elasticsearch集群属于中心化集群。

  1. 在众多节点中选举一个节点作为mater,维护元数据信息(meta、routing等)。
  2. index\type等读写操作、集群state、reroute等操作通过master实现,参见TransportMasterNodeAction的子类。
  3. 数据的查询、写入等操作通过集群所有节点(scatter),存储节点(gather)完成,减轻了master节点的计算。

模型实现

涉及模块

主要包含了es的gateway模块(元数据读写)、cluster模块(集群状态处理)、discovery模块(集群发现服务)三大模块完成集群功能。

如何构建

Created with Raphaël 2.1.0 开始 构建节点id 加入集群(选举master) 修改集群状态 持续错误检测 是否有异常节点 结束 yes no

流程解析:

构建节点

在集群节点启动时,先初始化nodeid表明自己身份。优先loadstate导入元数据信息,在clusterstate中解析出nodeid;如果没有clusterstate元数据则直接uuid生成nodeid。

/**
     * scans the node paths and loads existing metaData file. If not found a new meta data will be generated
     * and persisted into the nodePaths
     */
    private static NodeMetaData loadOrCreateNodeMetaData(Settings settings, Logger logger,
                                                         NodePath... nodePaths) throws IOException {
        final Path[] paths = Arrays.stream(nodePaths).map(np -> np.path).toArray(Path[]::new);
        NodeMetaData metaData = NodeMetaData.FORMAT.loadLatestState(logger, NamedXContentRegistry.EMPTY, paths);
        if (metaData == null) {
            metaData = new NodeMetaData(generateNodeId(settings));
        }
        // we write again to make sure all paths have the latest state file
        NodeMetaData.FORMAT.write(metaData, paths);
        return metaData;
    }

加入集群

在node类启动时候通过ZenDiscovery.innerJoinCluster加入集群。该方法保证加入群集或失败后生成一个新的连接线程尝试加入集群。下面是该方法解析:

1、findMaster。寻找自己所属集群的master。
①首先通过pingAndWait同步获得所有ping得到的节点fullPingResponses、加上自己本地节点
java
List fullPingResponses = pingAndWait(pingTimeout).toList();
...
fullPingResponses.add(new ZenPing.PingResponse(localNode, null, clusterService.state()));

②然后进行初步过滤得到pingResponses(如果我们启用了discovery.zen.master_election.ignore_non_master_pings则就会把那些node.master = false那些节点都忽略掉)
“`java
static List

// nodes discovered during pinging
List masterCandidates = new ArrayList<>();
for (ZenPing.PingResponse pingResponse : pingResponses) {
    if (pingResponse.node().isMasterNode()) {
        masterCandidates.add(new ElectMasterService.MasterCandidate(pingResponse.node(), pingResponse.getClusterStateVersion()));
    }
}

if (activeMasters.isEmpty()) {
    if (electMaster.hasEnoughCandidates(masterCandidates)) {
        final ElectMasterService.MasterCandidate winner = electMaster.electMaster(masterCandidates);
        logger.trace("candidate {} won election", winner);
        return winner.getNode();
    } else {
        // if we don't have enough master nodes, we bail, because there are not enough master to elect from
        logger.warn("not enough master nodes discovered during pinging (found [{}], but needed [{}]), pinging again",
                    masterCandidates, electMaster.minimumMasterNodes());
        return null;
    }
} else {
    assert !activeMasters.contains(localNode) : "local node should never be elected as master when other nodes indicate an active master";
    // lets tie break between discovered nodes
    return electMaster.tieBreakActiveMasters(activeMasters);
}

2、判断找到的master是否是自己节点
①如果是本节点则waitToBeElectedAsMaster(wait的原因是discovery.zen.minimum_master_nodes,一般配置为集群数量的一半防止脑裂,等待足够的节点join加入认同你为master,选举结束),然后启动nodesFD.updateNodesAndPing
②如果不是则发送join到master(joinElectedMaster:node join master、master反connect该node),加入不成功则重新启动加入流程(startNewThreadIfNotRunning)。

if (clusterService.localNode().equals(masterNode)) {
...           nodeJoinController.waitToBeElectedAsMaster(requiredJoins, masterElectionWaitForJoinsTimeout,
                    new NodeJoinController.ElectionCallback() {
                        @Override
                        public void onElectedAsMaster(ClusterState state) {
                            joinThreadControl.markThreadAsDone(currentThread);
                            ...
                            nodesFD.updateNodesAndPing(state); // start the nodes FD
                        }

                        @Override
                        public void onFailure(Throwable t) {
...
joinThreadControl.markThreadAsDoneAndStartNew(currentThread);
                        }
                    }

            );
        } else {
            ...
            // send join request
            final boolean success = joinElectedMaster(masterNode);
            // finalize join through the cluster state update thread
            final DiscoveryNode finalMasterNode = masterNode;
            clusterService.submitStateUpdateTask("finalize_join (" + masterNode + ")", new LocalClusterUpdateTask() {
                @Override
                public ClusterTasksResult execute(ClusterState currentState) throws Exception {
                    if (!success) {
                        // failed to join. Try again...
                        joinThreadControl.markThreadAsDoneAndStartNew(currentThread);
                        return unchanged();
                    }
                    ...
                }

                @Override
                public void onFailure(String source, @Nullable Exception e) {
                    ...
                    joinThreadControl.markThreadAsDoneAndStartNew(currentThread);
                }
            });
        }

修改状态

在加入集群之后都执行了clusterService.submitStateUpdateTask,提交状态变化。主要做了以下一些事情:

  1. 发布集群状态(如果是master)
  2. 持久化state
  3. 通知listener(包含gateway的读取等)

发布状态

// if we are the master, publish the new state to all nodes
// we publish here before we send a notification to all the listeners, since if it fails
// we don't want to notify
//作为master,发布ClusterState到各个node节点
if (newClusterState.nodes().isLocalNodeElectedMaster()) {
    logger.debug("publishing cluster state version [{}]", newClusterState.version());
    try {
        clusterStatePublisher.accept(clusterChangedEvent, ackListener);
    } catch (Discovery.FailedToCommitClusterStateException t) {
        final long version = newClusterState.version();
        logger.warn(
            (Supplier) () -> new ParameterizedMessage(
                "failing [{}]: failed to commit cluster state version [{}]", taskInputs.summary, version),
            t);
        // ensure that list of connected nodes in NodeConnectionsService is in-sync with the nodes of the current cluster state
        nodeConnectionsService.connectToNodes(previousClusterState.nodes());
        nodeConnectionsService.disconnectFromNodesExcept(previousClusterState.nodes());
        taskOutputs.publishingFailed(t);
        return;
    }
}

state元数据读写参照elasticsearch源码分析之Gateway(六)

错误检测

错误检测分为两种:MasterFaultDetection和NodeFaultDetection。

  1. MasterFaultDetection类,在非master节点上启动,用于检测master是否存活。和findMaster()里面的不一样就是这里不再用temp连接而是在threadPool里面的长连接,这里对错误进行分类,如果是一些业务错误则不受尝试次数的限制,如请求的节点根本不是master节点,请求的master不是自己的cluster等等,会直接调用notifyMasterFailure回调,如果是常规错误,则记录尝试次数,当错误次数超过了阈值,则调用notifyMasterFailure回调。 启动是在clusterService.submitStateUpdateTask中的taskOutputs.processedDifferentClusterState(previousClusterState, newClusterState)方法中,最终调度ZenDiscovery.clusterStateProcessed方法,启动masterFD.restart。
  2. NodeFaultDetection类,在master节点上启动,用于检测node节点是否存活。原理同MasterFaultDetection,对错误进行分类,当常规错误次数超过了阈值,则调用notifyNodeFailure回调,执行clusterService.submitStateUpdateTask,removenode。启动是在ZenDiscovery.publish方法中,publish方法只在master节点上执行;是在成为master之后,直接地调用nodesFD.updateNodesAndPing(state)。

这样通过以上几个关键步骤完成了集群建设容错框架,具体集群的不同角色会提供不同服务功能。

参考
《操作系统第六版》
《分布式系统原理介绍》
es源码

你可能感兴趣的:(elasticsearch)