elasticsearch源码分析-02选举主节点

elastic elasticsearch 源码解析之选主选举过程 这里的选主为什么提角色?是因为不同角色在选主中起到不同的作用.master的非节点不但参与投票同时还可以参与竞选, master 的角色仅投票不参与竞选,其余角色不参与.支持的角色如果不设置node.roles则默认有所有角色, 如果配置了,以配置的为准.跨集群搜索和副本角色只有master角色才允许高可用集群最少需要3个masterdata角色如果分层的话,又可以分为不同的层在存储时间,是否压缩,访问性能等进行差异化管理.以达到空间和性能的最优化. 阅读详情

选举主节点

选举主节点入口方法在node的start方法中

 //node启动开始选举master节点
discovery.startInitialJoin();

@Override
public void startInitialJoin() {
    // start the join thread from a cluster state update. See {@link JoinThreadControl} for details.
    //从集群状态更新启动连接线程
    synchronized (stateMutex) {
        // do the join on a different thread, the caller of this method waits for 30s anyhow till it is discovered
        joinThreadControl.startNewThreadIfNotRunning();
    }
}

选举运行在线程池Generic中运行

public void startNewThreadIfNotRunning() {
    assert Thread.holdsLock(stateMutex);
    //线程已经运行直接退出
    if (joinThreadActive()) {
        return;
    }
    //提交任务
    threadPool.generic().execute(new Runnable() {
        @Override
        public void run() {
            Thread currentThread = Thread.currentThread();
            if (!currentJoinThread.compareAndSet(null, currentThread)) {
                return;
            }
            //不停执行加入集群服务
            while (running.get() && joinThreadActive(currentThread)) {
                try {
                    innerJoinCluster();
                    return;
                } catch (Exception e) {
                    logger.error("unexpected error while joining cluster, trying again", e);
                    assert ExceptionsHelper.reThrowIfNotNull(e);
                }
            }
        }
    });
}

首先判断是否已经有选举任务正在运行,如果有则直接退出,提交了一个任务到generic中运行,最终调用innerJoinCluster方法

  • 选举临时主节点
//选举临时master节点
masterNode = findMaster();

首先查询当前集群中活跃的主节点或者从主节点候选列表中选择新的主节点,如果选举成功则返回。我们分析一下该方法下的几个主要过程
发送ping请求到所有节点

//ping所有节点,并接受返回结果
List<ZenPing.PingResponse> fullPingResponses = pingAndWait(pingTimeout).toList();
if (fullPingResponses == null) {
    logger.trace("No full ping responses");
    return null;
}

protected void ping(final Consumer<PingCollection> resultsConsumer,
                        final TimeValue scheduleDuration,
                        final TimeValue requestDuration) {
    final List<TransportAddress> seedAddresses = new ArrayList<>();
    //所有节点地址
    seedAddresses.addAll(hostsProvider.getSeedAddresses(createHostsResolver()));
    //获取所有节点
    final DiscoveryNodes nodes = contextProvider.clusterState().nodes();
    // add all possible master nodes that were active in the last known cluster configuration
    for (ObjectCursor<DiscoveryNode> masterNode : nodes.getMasterNodes().values()) {
        seedAddresses.add(masterNode.value.getAddress());
    }

    final ConnectionProfile connectionProfile =
        ConnectionProfile.buildSingleChannelProfile(TransportRequestOptions.Type.REG, requestDuration, requestDuration,
                                                    TimeValue.MINUS_ONE, null);
    final PingingRound pingingRound = new PingingRound(pingingRoundIdGenerator.incrementAndGet(), seedAddresses, resultsConsumer,
                                                       nodes.getLocalNode(), connectionProfile);
    //记录ping结果
    activePingingRounds.put(pingingRound.id(), pingingRound);
    final AbstractRunnable pingSender = new AbstractRunnable() {
        @Override
        public void onFailure(Exception e) {
            if (e instanceof AlreadyClosedException == false) {
                logger.warn("unexpected error while pinging", e);
            }
        }

        @Override
        protected void doRun() throws Exception {
            //发送ping请求
            sendPings(requestDuration, pingingRound);
        }
    };
    //使用通用的的线程池执行
    threadPool.generic().execute(pingSender);
    //并且添加两个定时执行任务
    threadPool.schedule(pingSender, TimeValue.timeValueMillis(scheduleDuration.millis() / 3), ThreadPool.Names.GENERIC);
    threadPool.schedule(pingSender, TimeValue.timeValueMillis(scheduleDuration.millis() / 3 * 2), ThreadPool.Names.GENERIC);
    threadPool.schedule(new AbstractRunnable() {
        @Override
        protected void doRun() throws Exception {
            //结束ping,关闭资源
            finishPingingRound(pingingRound);
        }

        @Override
        public void onFailure(Exception e) {
            logger.warn("unexpected error while finishing pinging round", e);
        }
    }, scheduleDuration, ThreadPool.Names.GENERIC);
}

根据配置的单播地址构造需要发送请求的nodes,发送ping请求到node

protected void sendPings(final TimeValue timeout, final PingingRound pingingRound) {
    final ClusterState lastState = contextProvider.clusterState();
    final UnicastPingRequest pingRequest = new UnicastPingRequest(pingingRound.id(), timeout, createPingResponse(lastState));

    List<TransportAddress> temporalAddresses = temporalResponses.stream().map(pingResponse -> {
        assert clusterName.equals(pingResponse.clusterName()) :
        "got a ping request from a different cluster. expected " + clusterName + " got " + pingResponse.clusterName();
        return pingResponse.node().getAddress();
    }).collect(Collectors.toList());

    final Stream<TransportAddress> uniqueAddresses = Stream.concat(pingingRound.getSeedAddresses().stream(),
                                                                   temporalAddresses.stream()).distinct();

    // resolve what we can via the latest cluster state
    //单播node节点
    final Set<DiscoveryNode> nodesToPing = uniqueAddresses
        .map(address -> {
            DiscoveryNode foundNode = lastState.nodes().findByAddress(address);
            //检查节点之前是否连接过
            if (foundNode != null && transportService.nodeConnected(foundNode)) {
                return foundNode;
            } else {
                return new DiscoveryNode(
                    address.toString(),
                    address,
                    emptyMap(),
                    emptySet(),
                    Version.CURRENT.minimumCompatibilityVersion());
            }
        }).collect(Collectors.toSet());
    //遍历发送所有节点
    nodesToPing.forEach(node -> sendPingRequestToNode(node, timeout, pingingRound, pingRequest));
}

首先检查与该node是否已经建立过连接,如果已经建立直接返回连接,如果没有则调用transport模块创建连接

protected void doRun() throws Exception {
    Connection connection = null;
    if (transportService.nodeConnected(node)) {
        try {
            // concurrency can still cause disconnects
            //并发可能会导致断开连接
            connection = transportService.getConnection(node);
        } catch (NodeNotConnectedException e) {
            logger.trace("[{}] node [{}] just disconnected, will create a temp connection", pingingRound.id(), node);
        }
    }
    //之前没有创建连接
    if (connection == null) {
        connection = pingingRound.getOrConnect(node);
    }

    logger.trace("[{}] sending to {}", pingingRound.id(), node);
    //"internal:discovery/zen/unicast"; 发送ping请求,并且将返回结果保存在pingingRound中
    transportService.sendRequest(connection, ACTION_NAME, pingRequest,
                                 TransportRequestOptions.builder().withTimeout((long) (timeout.millis() * 1.25)).build(),
                                 getPingResponseHandler(pingingRound, node));
}

//创建连接
result = transportService.openConnection(node, connectionProfile);

获取所有返回结果,并将本节点结果到fullPingResponses中

//本地节点
final DiscoveryNode localNode = transportService.getLocalNode();

// add our selves
assert fullPingResponses.stream().map(ZenPing.PingResponse::node)
    .filter(n -> n.equals(localNode)).findAny().isPresent() == false;
//添加本地节点
fullPingResponses.add(new ZenPing.PingResponse(localNode, null, this.clusterState()));

过滤结果,获取只有master角色的节点返回结果

// filter responses 过滤不具有master节点返回的结果
        final List<ZenPing.PingResponse> pingResponses = filterPingResponses(fullPingResponses, masterElectionIgnoreNonMasters, logger);

然后会构建两个列表一个是activeMasters存储集群当前活跃master集合,另一个是masterCandidates具有master资格的候选节点列表

//每个节点认为的master的节点集合
List<DiscoveryNode> activeMasters = new ArrayList<>();
for (ZenPing.PingResponse pingResponse : pingResponses) {
    if (pingResponse.master() != null && !localNode.equals(pingResponse.master())) {
        activeMasters.add(pingResponse.master());
    }
}

activeMasters列表中保存了每个节点认为的当前master节点,正常情况下只有一个,这里不含本节点

List<ElectMasterService.MasterCandidate> masterCandidates = new ArrayList<>();
for (ZenPing.PingResponse pingResponse : pingResponses) {
    //记录可以作为master节点的节点
    if (pingResponse.node().isMasterNode()) {
        masterCandidates.add(new ElectMasterService.MasterCandidate(pingResponse.node(), pingResponse.getClusterStateVersion()));
    }
}

过滤掉只有master资格的节点,构建候选master节点集合,如果上面的activeMasters列表为空则从masterCandidates选举主节点

 //如果还没有选举出来master
if (activeMasters.isEmpty()) {
    //达到了minimumMasterNodes个节点,单个节点也可以满足
    if (electMaster.hasEnoughCandidates(masterCandidates)) {
        //选出master节点
        final ElectMasterService.MasterCandidate winner = electMaster.electMaster(masterCandidates);
        logger.trace("candidate {} won election", winner);
        //返回master节点
        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
    //已经选出了master,从返回的master中选择一个id最小的节点
    return electMaster.tieBreakActiveMasters(activeMasters);
}

如果当前没有主节点,则从候选节点中选择主节点。首先判断当前法定人数是否达到,正常情况是master节点数/2+1

public boolean hasEnoughCandidates(Collection<MasterCandidate> candidates) {
     //没有候选节点
     if (candidates.isEmpty()) {
         return false;
     }
     //单节点选举
     if (minimumMasterNodes < 1) {
         return true;
     }
     assert candidates.stream().map(MasterCandidate::getNode).collect(Collectors.toSet()).size() == candidates.size() :
     "duplicates ahead: " + candidates;
     return candidates.size() >= minimumMasterNodes;
 }

如果当前候选节点达到法定人数则从中选择一个作为master

public MasterCandidate electMaster(Collection<MasterCandidate> candidates) {
    //再次检验是否有足够的候选节点
    assert hasEnoughCandidates(candidates);
    //对节点进行排序
    List<MasterCandidate> sortedCandidates = new ArrayList<>(candidates);
    //比对两个候选节点的clusterState的版本,大的版本排在前面
    //如果两个版本相同则比较节点的id,升序排序,选出id最小的
    sortedCandidates.sort(MasterCandidate::compare);
    //取出第一个
    return sortedCandidates.get(0);
}

这里可以看出只是将所有候选节点进行排序选择一个最小节点作为master,并且自己实现了compare方法

public static int compare(MasterCandidate c1, MasterCandidate c2) {
    //比较两个候选节点的状态版本信息,版本新的在前
    int ret = Long.compare(c2.clusterStateVersion, c1.clusterStateVersion);
    //如果版本相同,比较节点id
    if (ret == 0) {
        ret = compareNodes(c1.getNode(), c2.getNode());
    }
    return ret;
}

首先会比较两个候选节点的集群状态版本信息,如果版本信息比较新则排在前面,如果版本号相同则需要比较两个节点的节点id,然后选择排在最前面的一个节点为master
如果activeMasters列表不为空则从中选择一个作为master

//已经选出了master,从返回的master中选择一个id最小的节点
return electMaster.tieBreakActiveMasters(activeMasters);

public DiscoveryNode tieBreakActiveMasters(Collection<DiscoveryNode> activeMasters) {
    return activeMasters.stream().min(ElectMasterService::compareNodes).get();
}

 private static int compareNodes(DiscoveryNode o1, DiscoveryNode o2) {
     //两个节点都没有master资格
     if (o1.isMasterNode() && !o2.isMasterNode()) {
         return -1;
     }
     //选择具有master资格的节点
     if (!o1.isMasterNode() && o2.isMasterNode()) {
         return 1;
     }
     //比较两者的id,从小到大,升序排序
     return o1.getId().compareTo(o2.getId());
 }

activeMasters集合一般都是具有master资格的节点,这里可以看到只是简单的获取id小的节点作为master

  • 本节点为临时master
//选举出来的master节点是本节点
        if (transportService.getLocalNode().equals(masterNode)) {
            //需要加入的其他具有主节点资格的节点数,就是承认当前节点是主节点的数量
            final int requiredJoins = Math.max(0, electMaster.minimumMasterNodes() - 1); // we count as one
            logger.debug("elected as master, waiting for incoming joins ([{}] needed)", requiredJoins);
            //等待超过半数的节点加入,选择本节点作为master
            nodeJoinController.waitToBeElectedAsMaster(requiredJoins, masterElectionWaitForJoinsTimeout,
                    new NodeJoinController.ElectionCallback() {
                        @Override
                        public void onElectedAsMaster(ClusterState state) {
                            //选举成功
                            synchronized (stateMutex) {
                                joinThreadControl.markThreadAsDone(currentThread);
                            }
                        }

                        @Override
                        public void onFailure(Throwable t) {
                            logger.trace("failed while waiting for nodes to join, rejoining", t);
                            synchronized (stateMutex) {
                                joinThreadControl.markThreadAsDoneAndStartNew(currentThread);
                            }
                        }
                    }

            );
        }

获取需要得投票数量,然后等待投票数量超过法定数量则选举成功,如果达到超时时间没有得到足够选票则失败

public void waitToBeElectedAsMaster(int requiredMasterJoins, TimeValue timeValue, final ElectionCallback callback) {
        final CountDownLatch done = new CountDownLatch(1);
        //阻塞到选举成功或失败
        final ElectionCallback wrapperCallback = new ElectionCallback() {
            @Override
            public void onElectedAsMaster(ClusterState state) {
                done.countDown();
                callback.onElectedAsMaster(state);
            }

            @Override
            public void onFailure(Throwable t) {
                done.countDown();
                callback.onFailure(t);
            }
        };

        ElectionContext myElectionContext = null;

        try {
            // check what we have so far..
            // capture the context we add the callback to make sure we fail our own
            synchronized (this) {
                assert electionContext != null : "waitToBeElectedAsMaster is called we are not accumulating joins";
                myElectionContext = electionContext;
                //设置需要承认本节点为master的数量,并设置回调,解除阻塞
                electionContext.onAttemptToBeElected(requiredMasterJoins, wrapperCallback);
                //检查是否有足够节点join,让自己选为master,
                // 并且想其他节点通知自己选举成功更新集群状态
                checkAndElectIfNeeded();
            }

            try {
                if (done.await(timeValue.millis(), TimeUnit.MILLISECONDS)) {
                    // callback handles everything
                    return;
                }
            } catch (InterruptedException e) {

            }
            if (logger.isTraceEnabled()) {
                final int pendingNodes = myElectionContext.getPendingMasterJoinsCount();
                logger.trace("timed out waiting to be elected. waited [{}]. pending master node joins [{}]", timeValue, pendingNodes);
            }
            //选举超时
            failContextIfNeeded(myElectionContext, "timed out waiting to be elected");
        } catch (Exception e) {
            logger.error("unexpected failure while waiting for incoming joins", e);
            if (myElectionContext != null) {
                failContextIfNeeded(myElectionContext, "unexpected failure while waiting for pending joins [" + e.getMessage() + "]");
            }
        }
    }

这里可以看到是通过CountDownLatch进行控制,阻塞等待一定时间。当选举完成会发布集群状态,然后执行回调,再执行wrapperCallback方法,执行countDown比较选举结束,上面阻塞的程序继续往下走

private final ClusterStateTaskListener electionFinishedListener = new ClusterStateTaskListener() {
    @Override
    public void clusterStateProcessed(String source, ClusterState oldState, ClusterState newState) {
        //本节点选为master
        if (newState.nodes().isLocalNodeElectedMaster()) {
            ElectionContext.this.onElectedAsMaster(newState);
        } else {
            onFailure(source, new NotMasterException("election stopped [" + source + "]"));
        }
    }

    @Override
    public void onFailure(String source, Exception e) {
        ElectionContext.this.onFailure(e);
    }
};

}
  • 查看投票结果
    投票其实就是加入请求数量,在ZenDiscovery的构造函数中会创建membership对象处理node加入、离开和校验
this.membership = new MembershipAction(transportService, new MembershipListener(), onJoinValidators);

public MembershipAction(TransportService transportService, MembershipListener listener,
                            Collection<BiConsumer<DiscoveryNode,ClusterState>> joinValidators) {
    this.transportService = transportService;
    this.listener = listener;

    //注册加入集群处理
    transportService.registerRequestHandler(DISCOVERY_JOIN_ACTION_NAME,
                                            ThreadPool.Names.GENERIC, JoinRequest::new, new JoinRequestRequestHandler());
    //注册加入集群校验处理
    transportService.registerRequestHandler(DISCOVERY_JOIN_VALIDATE_ACTION_NAME,
                                            ThreadPool.Names.GENERIC, ValidateJoinRequest::new,
                                            new ValidateJoinRequestRequestHandler(transportService::getLocalNode, joinValidators));
    //注册离开集群处理
    transportService.registerRequestHandler(DISCOVERY_LEAVE_ACTION_NAME,
                                            ThreadPool.Names.GENERIC, LeaveRequest::new, new LeaveRequestRequestHandler());
}

当join请求到来时会在JoinRequestRequestHandler进行处理

private class JoinRequestRequestHandler implements TransportRequestHandler<JoinRequest> {

    @Override
    public void messageReceived(final JoinRequest request, final TransportChannel channel, Task task) throws Exception {
        listener.onJoin(request.getNode(), new JoinCallback() {
            @Override
            public void onSuccess() {//成功
                try {
                    //发送返回
                    channel.sendResponse(TransportResponse.Empty.INSTANCE);
                } catch (Exception e) {
                    onFailure(e);
                }
            }

            @Override
            public void onFailure(Exception e) {
                try {
                    channel.sendResponse(e);
                } catch (Exception inner) {
                    inner.addSuppressed(e);
                    logger.warn("failed to send back failure on join request", inner);
                }
            }
        });
    }
}

然后调用handleJoinRequest方法

void handleJoinRequest(final DiscoveryNode node, final ClusterState state, final MembershipAction.JoinCallback callback) {
        if (nodeJoinController == null) {
            throw new IllegalStateException("discovery module is not yet started");
        } else {
            // we do this in a couple of places including the cluster update thread. This one here is really just best effort
            // to ensure we fail as fast as possible.
            onJoinValidators.stream().forEach(a -> a.accept(node, state));
            if (state.getBlocks().hasGlobalBlock(STATE_NOT_RECOVERED_BLOCK) == false) {
                JoinTaskExecutor.ensureMajorVersionBarrier(node.getVersion(), state.getNodes().getMinNodeVersion());
            }
            // try and connect to the node, if it fails, we can raise an exception back to the client...
            //连接node
            transportService.connectToNode(node);

            // validate the join request, will throw a failure if it fails, which will get back to the
            // node calling the join request
            //验证加入请求,如果失败将抛出失败,这将返回到
            // 节点调用加入请求
            try {
                membership.sendValidateJoinRequestBlocking(node, state, joinTimeout);
            } catch (Exception e) {
                logger.warn(() -> new ParameterizedMessage("failed to validate incoming join request from node [{}]", node),
                    e);
                callback.onFailure(new IllegalStateException("failure when sending a validation request to node", e));
                return;
            }
            //处理加入请求
            nodeJoinController.handleJoinRequest(node, callback);
        }
    }

public synchronized void handleJoinRequest(final DiscoveryNode node, final MembershipAction.JoinCallback callback) {
        //正在选举
        if (electionContext != null) {
            electionContext.addIncomingJoin(node, callback);
            checkPendingJoinsAndElectIfNeeded();
        } else {
            //如果是单个节点加入集群,提交提个状态变更
            masterService.submitStateUpdateTask("zen-disco-node-join",
                new JoinTaskExecutor.Task(node, "no election context"), ClusterStateTaskConfig.build(Priority.URGENT),
                joinTaskExecutor, new JoinTaskListener(callback, logger));
        }
    }

累积加入请求数量及投票数量

public synchronized void addIncomingJoin(DiscoveryNode node, MembershipAction.JoinCallback callback) {
            ensureOpen();
            joinRequestAccumulator.computeIfAbsent(node, n -> new ArrayList<>()).add(callback);
        }

检查是否达到法定数量

private synchronized void checkPendingJoinsAndElectIfNeeded() {
    assert electionContext != null : "election check requested but no active context";
    //连接数
    final int pendingMasterJoins = electionContext.getPendingMasterJoinsCount();
    //不足够连接数
    if (electionContext.isEnoughPendingJoins(pendingMasterJoins) == false) {
        if (logger.isTraceEnabled()) {
            logger.trace("not enough joins for election. Got [{}], required [{}]", pendingMasterJoins,
                         electionContext.requiredMasterJoins);
        }
    } else {
        if (logger.isTraceEnabled()) {
            logger.trace("have enough joins for election. Got [{}], required [{}]", pendingMasterJoins,
                         electionContext.requiredMasterJoins);
        }
        //选举胜出,成功成为master,广播选为master状态
        electionContext.closeAndBecomeMaster();
        electionContext = null; // clear this out so future joins won't be accumulated
    }
}

如果达到法定数量,则选举结束向集群提交一个集群状态变更任务,广播自己成为master

public synchronized void closeAndBecomeMaster() {
    assert callback != null : "becoming a master but the callback is not yet set";
    assert isEnoughPendingJoins(getPendingMasterJoinsCount()) : "becoming a master but pending joins of "
        + getPendingMasterJoinsCount() + " are not enough. needs [" + requiredMasterJoins + "];";
    //设置选举关闭
    innerClose();

    Map<JoinTaskExecutor.Task, ClusterStateTaskListener> tasks = getPendingAsTasks("become master");
    //向集群广播自己选为master的状态的任务source
    final String source = "zen-disco-elected-as-master ([" + tasks.size() + "] nodes joined)";

    // noop listener, the election finished listener determines result
    //noop 监听器,选举完成监听器确定结果
    tasks.put(JoinTaskExecutor.newBecomeMasterTask(), (source1, e) -> {});
    //添加选举结束的回调
    tasks.put(JoinTaskExecutor.newFinishElectionTask(), electionFinishedListener);
    //提交一个集群状态变更任务,向集群广播自己选为master的状态
    masterService.submitStateUpdateTasks(source, tasks, ClusterStateTaskConfig.build(Priority.URGENT), joinTaskExecutor);
        }
  • 其他节点为临时master
//选举出来的master节点不是自己
//停止选主服务,改为加入主节点
nodeJoinController.stopElectionContext(masterNode + " elected");

//发送加入master节点的请求
final boolean success = joinElectedMaster(masterNode);

首先停止选master服务,改为发送加入集群请求

private boolean joinElectedMaster(DiscoveryNode masterNode) {
        try {
            // first, make sure we can connect to the master
            //首先检查是否能连接master
            transportService.connectToNode(masterNode);
        } catch (Exception e) {
            logger.warn(() -> new ParameterizedMessage("failed to connect to master [{}], retrying...", masterNode), e);
            return false;
        }
        int joinAttempt = 0; // we retry on illegal state if the master is not yet ready
        while (true) {
            try {
                logger.trace("joining master {}", masterNode);
                //发送加入集群
                membership.sendJoinRequestBlocking(masterNode, transportService.getLocalNode(), joinTimeout);
                return true;
            } catch (Exception e) {
                final Throwable unwrap = ExceptionsHelper.unwrapCause(e);
                //重试
                if (unwrap instanceof NotMasterException) {
                    if (++joinAttempt == this.joinRetryAttempts) {
                      ...  
                } else {
                    ...
                    return false;
                }
            }

            try {
                Thread.sleep(this.joinRetryDelay.millis());
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
            }
        }
    }

发送加入集群请求

public void sendJoinRequestBlocking(DiscoveryNode masterNode, DiscoveryNode node, TimeValue timeout) {
        //"internal:discovery/zen/join";发送加入请求
        transportService.submitRequest(masterNode, DISCOVERY_JOIN_ACTION_NAME, new JoinRequest(node),
            EmptyTransportResponseHandler.INSTANCE_SAME).txGet(timeout.millis(), TimeUnit.MILLISECONDS);
    }

加入请求处理就在JoinRequestRequestHandler中
最后检查加入集群是否成功,如果成功则检查集群状态中的master节点是否存在,不存在需要重新选举;然后比较选择的master节点和自己加入的节点是否相同,不相同则加入新发布的master;如果选择的master节点是本节点加入的节点则设置选举结束。如果加入集群失败则需要重新选举。
到这里集群已经选择出来了master节点,其他节点已经加入集群。那如何保证master到非master节点的连接是否存活,相反非master到master的连接是否存活需要用到两个探测器MasterFaultDetection和NodesFaultDetection

//检查主节点是否失效
        this.masterFD = new MasterFaultDetection(settings, threadPool, transportService, this::clusterState, masterService, clusterName);
        //添加监听器
        this.masterFD.addListener(new MasterNodeFailureListener());
        this.nodesFD = new NodesFaultDetection(settings, threadPool, transportService, this::clusterState, clusterName);
        //添加监听器
        this.nodesFD.addListener(new NodeFaultDetectionListener());

MasterFaultDetection会在非master节点运行定时任务,默认每隔1s发送一次"internal:discovery/zen/fd/master_ping"请求,如果失败则需要重新加入集群,执行一次选主流程

private class MasterNodeFailureListener implements MasterFaultDetection.Listener {

        @Override
        public void onMasterFailure(DiscoveryNode masterNode, Throwable cause, String reason) {
            handleMasterGone(masterNode, cause, reason);
        }
    }

//ping master节点失败了
    private void handleMasterGone(final DiscoveryNode masterNode, final Throwable cause, final String reason) {
        ...
        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));
                //重新选举加入master
                rejoin("master left (reason = " + reason + ")");
            }
        }
    }

NodesFaultDetection运行在master节点,master也是定时发送心跳ping请求给非master节点,如果发送失败则先重试,重试达到一定次数调用notifyNodeFailure,如果返回连接错误则调用handleTransportDisconnect(node)

transportService.sendRequest(node, PING_ACTION_NAME, newPingRequest(), options, new TransportResponseHandler<PingResponse>() {
    @Override
    public PingResponse read(StreamInput in) throws IOException {
        return new PingResponse(in);
    }

    @Override
    public void handleResponse(PingResponse response) {
        if (!running()) {
            return;
        }
        retryCount = 0;
        threadPool.schedule(NodeFD.this, pingInterval, ThreadPool.Names.SAME);
    }

    @Override
    public void handleException(TransportException exp) {
        if (!running()) {
            return;
        }
        if (exp instanceof ConnectTransportException || exp.getCause() instanceof ConnectTransportException) {
            handleTransportDisconnect(node);
            return;
        }

        retryCount++;
        logger.trace( () -> new ParameterizedMessage(
            "[node  ] failed to ping [{}], retry [{}] out of [{}]", node, retryCount, pingRetryCount), exp);
        if (retryCount >= pingRetryCount) {
            logger.debug("[node  ] failed to ping [{}], tried [{}] times, each with  maximum [{}] timeout", node,
                         pingRetryCount, pingRetryTimeout);
            // not good, failure,查过ping重试次数,移除节点
            if (nodesFD.remove(node, NodeFD.this)) {
                notifyNodeFailure(node, "failed to ping, tried [" + pingRetryCount + "] times, each with maximum ["
                                  + pingRetryTimeout + "] timeout");
            }
        } else {
            // resend the request, not reschedule, rely on send timeout
            transportService.sendRequest(node, PING_ACTION_NAME, newPingRequest(), options, this);
        }
    }

    @Override
    public String executor() {
        return ThreadPool.Names.SAME;
    }
});

最终都会调用NodeFaultDetectionListener的onNodeFailure方法

private void handleNodeFailure(final DiscoveryNode node, final String reason) {
    if (lifecycleState() != Lifecycle.State.STARTED) {
        // not started, ignore a node failure
        return;
    }
    if (!localNodeMaster()) {
        // nothing to do here...
        return;
    }
    //ping节点失败,移除
    removeNode(node, "zen-disco-node-failed", reason);
}

private void removeNode(final DiscoveryNode node, final String source, final String reason) {
    masterService.submitStateUpdateTask(
        source + "(" + node + "), reason(" + reason + ")",
        new NodeRemovalClusterStateTaskExecutor.Task(node, reason),
        ClusterStateTaskConfig.build(Priority.IMMEDIATE),
        nodeRemovalExecutor,
        nodeRemovalExecutor);
}

public ClusterTasksResult<Task> execute(final ClusterState currentState, final List<Task> tasks) throws Exception {
    final DiscoveryNodes.Builder remainingNodesBuilder = DiscoveryNodes.builder(currentState.nodes());
    boolean removed = false;
    for (final Task task : tasks) {
        if (currentState.nodes().nodeExists(task.node())) {
            remainingNodesBuilder.remove(task.node());
            removed = true;
        } else {
            logger.debug("node [{}] does not exist in cluster state, ignoring", task);
        }
    }

    if (!removed) {
        // no nodes to remove, keep the current cluster state
        return ClusterTasksResult.<Task>builder().successes(tasks).build(currentState);
    }

    final ClusterState remainingNodesClusterState = remainingNodesClusterState(currentState, remainingNodesBuilder);

    return getTaskClusterTasksResult(currentState, tasks, remainingNodesClusterState);
}

@Override
protected ClusterTasksResult<Task> getTaskClusterTasksResult(ClusterState currentState, List<Task> tasks,
                                                             ClusterState remainingNodesClusterState) {
    //检查是还有足够的节点存在
    if (electMasterService.hasEnoughMasterNodes(remainingNodesClusterState.nodes()) == false) {
        final ClusterTasksResult.Builder<Task> resultBuilder = ClusterTasksResult.<Task>builder().successes(tasks);
        final int masterNodes = electMasterService.countMasterNodes(remainingNodesClusterState.nodes());
        rejoin.accept(LoggerMessageFormat.format("not enough master nodes (has [{}], but needed [{}])",
                                                 masterNodes, electMasterService.minimumMasterNodes()));
        return resultBuilder.build(currentState);
    } else {
        return super.getTaskClusterTasksResult(currentState, tasks, remainingNodesClusterState);
    }
}

检查当前剩余节点数量是否达到法定数量,如果不足需要放弃master身份,重新加入集群,避免脑裂发生

ElasticSearch——详解主从模式,以及主节点的选取算法(一) 每个节点计算最小的已知节点ID,该节点为临时Master。向该节点发送领导投票。如果一个节点收到足够多的票数,并且该节点也为自己投票,那么它将扮演领导者的角色,开始发布集群状态。所有节点都会参与选举,并参与投票,但只有有资格成为Master的节点(node.master为true)的投票才有效。获得多少选票可以赢得选举胜利,就是所谓的法定人数。在 ES中 , 法定大小是一个可配置的参数。。为了避免脑裂,最小值应该是有Master资格的节点数n/2+1。 阅读详情

相关推荐

【kafka运维】Leader重新选举运维脚本

文章目录kafka-leader-election Leader重新选举More 日常运维 、问题排查 怎么能够少了滴滴开源的 滴滴开源LogiKM一站式Kafka监控与管控平台 kafka-leader-election Leader重新选举 1 指定Topic指定分区用重新PREFERRED:优先副本策略 进行Leader重选举 > sh bin/kafka-leader-election.sh --bootstrap-server xxxx:9090 --topic test_create

石臻说AI(szzdzhp001) 【原名 石臻臻的杂货铺】 5678

elasticsearch是如何实现master选举的?

在Elasticsearch中,一个集群中的节点被分为两类:主节点(master-eligible nodes)和数据节点(data nodes)。请注意,实际生产环境中,通常会使用更复杂的设置来保证集群的高可用性和稳定性,例如使用Zookeeper来协调主节点选举过程。1.节点加入集群时,它们会互相通信并形成一个临时的选举状态,其中每个节点都有一个称为"node id"的唯一标识符。3.如果有节点断开连接或主节点失效,其余的节点会再次进行选举来选出新的主节点。

Blue92120的博客 1127

大白话讲解Numpy中什么是一维、二维、三维数组,以及5种创建方式

NumPy中提供了多种创建数组的方式,其中最基本的方式就是通过array()函数创建数组在使用该函数时直接传入列表或元组即可。 通过array()函数分别创建一维数组、二维组和三维数组,那么什么是维度呢?一维、二维、三维有什么区别? 1、什么是维度? 维度可以理解为描述数据的一种 “方向” 或者 “层次”。在数组中,维度的数量决定了我们需要用多少个索引来访问一个元素 2、一维、二维、三维的区别? (1)一维:一维数组就像是一条直线上排列的一组数据,例如: a = np.array([1, 2

1361

elasticsearch源码分析-03选举集群状态

首先判断只有主节点可以执行状态选举,然后判断是否已经在执行了状态恢复任务了,如果是则直接返回;遍历所有节点选择返回的索引元数据版本最高的节点作为索引级元数据,然后将索引级元数据添加到metadataBuilder中。从磁盘读取构建索引级元数据和集群级元数据,用于构建集群状态对象ClusterState。开始通过版本号选择集群层元数据,比较版本号,选择版本号最大的集群状态。构造集群状态,删除索引信息,下面会选择索引级元数据。我们继续回到每个节点发送请求的返回处理。构造恢复后的集群级元数据和索引级元数据。

GeekerJava的博客 927

电路课设-音响分频器电路设计

(一)题目简介 1.一阶分频器电路设计 假定高音和低音扬声器均等效为 16 欧姆电阻,设计如图 2 的简单分频器电 路,要求高低频分界(两个滤波器的截止频率)为 5kHz。 1) 写出高音扬声器和低音扬声器电压对于 vs 的传递函数,确定电路中电 容 C 和电感 L 的元件值;2)利用 AC 频率扫描分析求出两个扬声器电压的频率响应曲线,验证计算 结果。用包含高低频成分的电压 vs 激励进行测量验证。 3)计算并利用 EWB 仿真证明从 vs 看进去的阻抗为一常数 16 欧姆。

weixin_53507617的博客 1万+

DeepSeek与豆包无缝对接:5分钟搞定智能周报自动化(附完整代码)

本文详细介绍了如何利用DeepSeek与豆包API,快速搭建一个智能周报自动化系统。通过整合豆包平台的工作流数据,并调用DeepSeek大模型进行深度分析与内容生成,该系统能在5分钟内自动抓取、分析多源信息,并生成结构清晰、富有洞察力的团队周报,显著提升工作效率。

weixin_29029995的博客 490

Elasticsearch源码解析(三)——选主流程

Elasticsearch集群的选主过程。

Ingenuity1992的博客 1296

[特殊字符] 从零构建携程/飞猪机票价格爬虫:Python异步抓取与反爬对抗实战

在出行决策、竞品分析、价格预测模型训练等场景中,稳定获取携程(Ctrip)和飞猪(Fliggy)的实时机票价格波动数据是刚需。然而,这两大平台均部署了业界顶尖的反爬体系——包括但不限于:动态请求签名(SignToken设备指纹采集(UACanvasWebGL接口参数加密(X-Sign_abtestcookie混淆)频率限制与 IP 封禁(滑动窗口 + 行为检测)目录前言:为什么需要自建机票价格爬虫?一、技术选型与项目架构1.1 核心技术栈1.2 系统架构图(文字描述)1.3 法律与道德声明。

2201_76125261的博客 337

ES选举:Elasticsearch中Master选举完全解读

Master选举的意义在于集群主节点在遭遇宕机时保障服务的可用性。 理解选举机制及相关算法,有利于了解ES底层的高可用原理,并学习及借鉴其设计思想。 理解`Master选举`的另一个重要原因是:其为 ES 常见面试题之一! 本文我将结合Elasticsearch源码、文字、绘图的方式剖析Master选举的完整过程。有任何问题,欢迎在社区群与我探讨。

Elastic开源社区 7356

SMOKE 3D:从 Python 到 C++ 实现车辆与行人单目 3D 视觉识别在 RK3588 上的深度部署

SMOKE的网络结构通常基于高性能的骨干网络(如DLA-34,您使用的模型就是基于此),通过编码器-解码器架构来提取特征。输入:RGB图像(本项目为骨干网络:用于特征提取。检测头热力图分支:预测关键点热力图。通常在热力图上进行NMS来找到局部最大值,即潜在的关键点位置。回归分支:对于每个关键点位置,预测上述的8维3D属性向量。分类分支:预测每个关键点对应的物体类别。置信度分支:预测每个关键点的置信度。本教程详细介绍了SMOKE单目3D目标检测模型在RK3588上的部署。我们从SMOKE的。

LXY_LOVE36的博客 436

人形机器人开源资源大盘点(四):硬件开源平台

开源人形机器人平台推动技术民主化 本期聚焦四大开源人形机器人平台:斯坦福的ToddlerBot以6000美元低成本实现30自由度运动,支持仿真到现实的零样本迁移;法国Reachy2被HuggingFace收购,融合开源AI与硬件,适配医疗、工业等场景;国内OriginMan基于ROS2全开源,集成大模型能力,支持深度改造;OAN框架则为经典NAO机器人注入开源活力,扩展交互功能。这些平台通过不同路径降低研发门槛,推动人形机器人技术走向开放共享的未来。

Studying…… 370

【Elasticsearch】raft 选举

不,现在的做法不再记录“quorum” 的具体数值,取而代之的是记录一个节点列表,这个列表中保存所有具备 master 资格的节点(有些情况下不是这样,例如集群原本只有1个节点,当增加到2个的时候,这个列表维持不变,因为如果变成2,当集群任意节点离线,都会导致无法选主。5.同理, 节点收到 Master 节点发来的集群状态 ,在处理二段提交的第二阶段: CoordinationState::handleCommit 中,如果 clusterState中的 term 不等于当前 term,则拒绝该请求。

这个人很懒什么都没有~ 698

AI辅助需求分析与提示词生成

文章标题 《AI辅助需求分析与提示词生成》 关键词 人工智能 需求分析 提示词生成 深度学习 机器学习 强化学习 数学模型 系统架构

AI天才研究院 1211

源码分析Elasticsearch Master选举过程

【转自】https://www.sohu.com/a/123557397_465922 关于es的选举机制,相信大家都已经很熟悉了,不过今天看到一个解释,觉得挺有意思的,准过来,各位不妨一看。注:原文中无图 ES 有Master节点和Data节点,Master节点什么意思呢? 就是主人节点,这个集群的主人,就是皇帝。ES同一时刻只有一个Master节点。小生一直看古装走火入魔,所谓心里有王朝,眼里...

空城雀的博客 1205

计算机实战分享3:森林火灾预测分析可视化机器学习预测-完整数据代码-可直接运行

计算机实战分享3:森林火灾预测分析可视化机器学习预测-完整数据代码-可直接运行

603

最新的sdio 4.0 spec 协议标准 带书签 高清完整PDF版

Part-E1-SDIO-Specification-Ver4.00-Final-120220.pdf:最新的sdio 4.0 spec 协议标准 带书签 高清完整PDF版,包括SDIO 接口的外设,SDIO 接口官方协议等。

elasticsearch 是如何实现 master 选举的

这个我看了各种网上分析的版本和源码分析的书籍,云里雾里。核对了一下代码,核心入口为 findMaster,选择主节点成功返回对应 Master,否则返回 null。第二步:比较:先判定是否具备 master 资格,具备候选主节点资格的优先返回;第一步:确认候选主节点数达标,elasticsearch.yml 设置的值。最小主节点数(min_master_nodes)的目的是防止脑裂。只有候选主节点(master:true)的节点才能成为主节点。若两节点都为候选主节点,则 id 小的值会主节点。

qq_33240556的博客 131

OV9734 DATASHEET,完整规格书

OV9734完整规格书,包括电气参数,尺寸规格,寄存器配置表等

elasticsearch 是如何实现master选举的?

前置前提: 1、只有候选主节点( master:true)的节点才能成为主节点。 2、最小主节点数( min_master_nodes)的目的是防止脑裂。 这个我看了各种网上分析的版本和源码分析的书籍,云里雾里。 核对了一下代码,核心入口为findMaster,选择主节点成功返回对应Master,否则返回null。选举流程大致描述如下: 第一步: 确认候选主节点数达标,elasticsearch.yml 设置的值discovery.zen.minimum_master_nodes; 第二步: 比较:先判

siyuanwai的博客 530

迈瑞BS系列全自动生化分析仪LIS协议接口手册(内含HL7和ASTM)

迈瑞BS系列全自动生化分析仪LIS协议接口手册,内含HL7和ASTM)

C# WinForm 基于DirectShow.dll编写的程序,可边预览边保存视频,音频和图片

基于微软的DirectShow.dll的二次开发1.自动识别连接的摄像头跟麦克风且可以对其进行选择(默认选中第一个)2.可录制wmv和avi两种格式的视频并且都带有音频,avi可以录制的分辨率和帧数进行选择,wmv默认进行了淸晰度优化如不喜欢可去除代码中有相关提示3.可在预览点开之后进行拍照,也可边录制视频边拍照4.添加了视频和音频的压缩功能(酌情使用)这次二次开发发现C#对DirectShow的相关信息太少,这次做完也不敢藏私共享出来,还望大家指正

Elastic Stack日志分析(十二)- Elasticsearch Master选举流程详解

Elasticsearch选举Master算法,Elasticsearch Master选举流程详解,Elasticsearch Master选举时间前提

程序园@大Null 2263

【吊打面试官系列-Elasticsearch面试题】elasticsearch 是如何实现 master 选举的?

elasticsearch 是如何实现 master 选举的?

java1234的博客 312

elasticsearch源码分析之discovery(七)

Discovery模块概述elasticsearch中的Discover模块,这个模块主要是发现模块负责发现集群中的节点,以及选取主节点。用作处理elasticsearch中的集群问题,是elasticsearch中比较复杂的一个模块。 discovery有几类: Azure discovery(类似于多播),它可作为插件使用。 EC2 discovery,作为插件使用。 Google Compu

thomas0yang的专栏 4112

JAVA面试小结四十一:elasticsearch 是如何实现 master 选举的

这个我看了各种网上分析的版本和源码分析的书籍,云里雾里。核对了一下代码,核心入口为 findMaster,选择主节点成功返回对应 Master,否则返回 null。第二步:比较:先判定是否具备 master 资格,具备候选主节点资格的优先返回;第一步:确认候选主节点数达标,elasticsearch.yml 设置的值。2、 最小主节点数(min_master_nodes)的目的是防止脑裂。1、 只有候选主节点(master:true)的节点才能成为主节点。若两节点都为候选主节点,则 id 小的值会主节点。

之乎者也·的博客 285

120 道 ES面试题及答案整理,ES常见面试题(持续更新)

最新Elasticsearch面试题【附答案解析】ES面试题及答案,ES最新面试题及答案,ES面试题新答案已经全部更新完了,有些答案是自己总结的,也有些答案是在网上搜集整理的。这些答案难免会存在一些错误,仅供大家参考。如果发现错误还望大家多多包涵,不吝赐教,谢谢~ 如果不背 ES面试题的答案,肯定面试会挂! 这套ES面试题大全,希望对大家有帮助哈~ 博主已将以下这些面试题整理成了一个面试手册,是PDF版的 1、elasticsearch的倒排索引是什么 面试官:想了解你对基础概念的认知。 通俗解

架构师专栏 4万+

hashicorp raft源码分析(一、项目介绍与Leder选举实现)

本文基于 hashicorp/raftv1.7.3版本进行源码分析API手册:https://pkg.go.dev/github.com/hashicorp/raftraft论文中文解读:https://github.com/maemual/raft-zh_cn/blob/master/raft-zh_cn.md在阅读文章前需要有一定的 raft 基础, 不然直接看源码会一头雾水.Raft是一种分布式一致性算法。

qq_42009262的博客 1268

Golang项目引入leaderelection实现leader选举

leaderelection基本工作原理其实就是利用通过Kubernetes中 `configmap` , `endpoints` 或者 `lease` 资源实现一个分布式锁,抢(acqure)到锁的节点成为leader,并且定期更新(renew)。其他进程也在不断的尝试进行抢占,抢占不到则继续等待下次循环。当leader节点挂掉之后,租约到期,其他节点就成为新的leader。

追求幸福,探索未知的博客 1034
上一篇: elasticsearch源码分析-01服务启动
下一篇: elasticsearch源码分析-03选举集群状态
胖柯G
博客等级 码龄10年 453粉丝 57原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值