elasticsearch源码分析-04集群状态发布

Kibana启动常见报错信息的解决方案 报错信息 "warning","migrations","pid":6181,"message":"Another Kibana instance appears to be migrating the index. Waiting for that migration to complete. If no other Kibana instance is attempting migration... 阅读详情

集群状态发布

cluster模块封装了在集群层面执行的任务,如集群健康、集群级元信息管理、分片分配给节点、节点管理等。集群任务执行之后可能会产生新的集群状态,如果产生新的集群状态主节点会将集群状态广播给其他节点。

集群状态封装在clusterState中,支持增量同步

提交集群任务的主要时机有以下几种:

  • 索引的创建、删除、打开、关闭
  • 索引模板、映射、别名的变化
  • gateway模块发布选举出来的集群状态
  • 快照
  • 分片分配
  • 集群节点变化等

提交集群任务入口在ClusterService的submitStateUpdateTask方法,第一个参数是事件来源,第二个参数是要执行的具体任务

public <T extends ClusterStateTaskConfig & ClusterStateTaskExecutor<T> & ClusterStateTaskListener>
        void submitStateUpdateTask(String source, T updateTask) {
        submitStateUpdateTask(source, updateTask, updateTask, updateTask, updateTask);
    }
    
public <T> void submitStateUpdateTask(String source, T task,
                                          ClusterStateTaskConfig config,
                                          ClusterStateTaskExecutor<T> executor,
                                          ClusterStateTaskListener listener) {
        submitStateUpdateTasks(source, Collections.singletonMap(task, listener), config, executor);
    }

public <T> void submitStateUpdateTasks(final String source,
                                           final Map<T, ClusterStateTaskListener> tasks, final ClusterStateTaskConfig config,
                                           final ClusterStateTaskExecutor<T> executor) {
        masterService.submitStateUpdateTasks(source, tasks, config, executor);
    }

最有代表性的任务是ClusterStateUpdateTask,它实现了ClusterStateTaskConfig、ClusterStateTaskExecutor

public abstract class ClusterStateUpdateTask
        implements ClusterStateTaskConfig, ClusterStateTaskExecutor<ClusterStateUpdateTask>, ClusterStateTaskListener {

在这里插入图片描述
ClusterStateTaskConfig包含了任务的配置信息和优先级

TimeValue timeout();
Priority priority();

ClusterStateTaskExecutor主要是定义要执行的任务,最主要的方法就是execute方法

 ClusterTasksResult<T> execute(ClusterState currentState, List<T> tasks) throws Exception;

任务执行时会传入当前集群状态,任务运行过程中如果产生新的集群状态就返回新的集群状态,如果没有就返回原来的集群状态

ClusterStateTaskListener主要是提交任务后的回调处理

/**
     * A callback called when execute fails.
     */
    void onFailure(String source, Exception e);

    /**
     * called when the task was rejected because the local node is no longer master.
     * Used only for tasks submitted to {@link MasterService}.
     */
    default void onNoLongerMaster(String source) {
        onFailure(source, new NotMasterException("no longer master. source: [" + source + "]"));
    }

    /**
     * Called when the result of the {@link ClusterStateTaskExecutor#execute(ClusterState, List)} have been processed
     * properly by all listeners.
     */
    default void clusterStateProcessed(String source, ClusterState oldState, ClusterState newState) {
    }

MasterService主要负责集群任务管理和运行,只有主节点会提交集群任务到内部队列,并运行队列中的任务

public <T> void submitStateUpdateTasks(final String source,
                                           final Map<T, ClusterStateTaskListener> tasks, final ClusterStateTaskConfig config,
                                           final ClusterStateTaskExecutor<T> executor) {
        if (!lifecycle.started()) {
            return;
        }
        final ThreadContext threadContext = threadPool.getThreadContext();
        final Supplier<ThreadContext.StoredContext> supplier = threadContext.newRestorableContext(true);
        try (ThreadContext.StoredContext ignore = threadContext.stashContext()) {
            threadContext.markAsSystemContext();
            //封装任务
            List<Batcher.UpdateTask> safeTasks = tasks.entrySet().stream()
                .map(e -> taskBatcher.new UpdateTask(config.priority(), source, e.getKey(), safe(e.getValue(), supplier), executor))
                .collect(Collectors.toList());
            //提交任务
            taskBatcher.submitTasks(safeTasks, config.timeout());
        } catch (EsRejectedExecutionException e) {
            // ignore cases where we are shutting down..., there is really nothing interesting
            // to be done here...
            if (!lifecycle.stoppedOrClosed()) {
                throw e;
            }
        }
    }
public void submitTasks(List<? extends BatchedTask> tasks, @Nullable TimeValue timeout) throws EsRejectedExecutionException {
        if (tasks.isEmpty()) {
            return;
        }
        final BatchedTask firstTask = tasks.get(0);
        assert tasks.stream().allMatch(t -> t.batchingKey == firstTask.batchingKey) :
            "tasks submitted in a batch should share the same batching key: " + tasks;
        // convert to an identity map to check for dups based on task identity
        //根据任务标识检查重复数据
        final Map<Object, BatchedTask> tasksIdentity = tasks.stream().collect(Collectors.toMap(
            BatchedTask::getTask,
            Function.identity(),
            (a, b) -> { throw new IllegalStateException("cannot add duplicate task: " + a); },
            IdentityHashMap::new));

        synchronized (tasksPerBatchingKey) {
            //添加相同batchingKey的任务,返回已存在batchingKey的任务
            LinkedHashSet<BatchedTask> existingTasks = tasksPerBatchingKey.computeIfAbsent(firstTask.batchingKey,
                k -> new LinkedHashSet<>(tasks.size()));
            //检查是否存在相同batchingKey的任务
            for (BatchedTask existing : existingTasks) {
                // check that there won't be two tasks with the same identity for the same batching key
                BatchedTask duplicateTask = tasksIdentity.get(existing.getTask());
                if (duplicateTask != null) {
                    throw new IllegalStateException("task [" + duplicateTask.describeTasks(
                        Collections.singletonList(existing)) + "] with source [" + duplicateTask.source + "] is already queued");
                }
            }
            existingTasks.addAll(tasks);
        }
        //执行任务
        if (timeout != null) {
            threadExecutor.execute(firstTask, timeout, () -> onTimeoutInternal(tasks, timeout));
        } else {
            threadExecutor.execute(firstTask);
        }
    }

这里有去重逻辑,拥有相同ClusterStateTaskExecutor对象实例的任务只会执行一次,然后对于其他相同的实例直接赋值相同的执行结果。区分重复任务的方式时通过定义的任务本身,去重的方式不是将重复的数据删除,而是在执行完任务后赋予重复任务相同的结果。

ClusterStateTaskExecutor相同有两种情况可能是提交的任务本身重复,还有就是之前提交的任务已存在,但是尚未执行此时提交相同的任务就会保存到对应的列表中,只会执行一次

任务会被封装到UpdateTask中

class UpdateTask extends BatchedTask {
            final ClusterStateTaskListener listener;

            UpdateTask(Priority priority, String source, Object task, ClusterStateTaskListener listener,
                       ClusterStateTaskExecutor<?> executor) {
                super(priority, source, executor, task);
                this.listener = listener;
            }

            @Override
            public String describeTasks(List<? extends BatchedTask> tasks) {
                return ((ClusterStateTaskExecutor<Object>) batchingKey).describeTasks(
                    tasks.stream().map(BatchedTask::getTask).collect(Collectors.toList()));
            }
        }

提交到线程池运行调用run方法

@Override
public void run() {
    //运行还没处理的任务
    runIfNotProcessed(this);
}
void runIfNotProcessed(BatchedTask updateTask) {
        //具有相同batching key的任务只会执行一次
        if (updateTask.processed.get() == false) {
            final List<BatchedTask> toExecute = new ArrayList<>();
            final Map<String, List<BatchedTask>> processTasksBySource = new HashMap<>();
            synchronized (tasksPerBatchingKey) {
                //获取任务列表
                LinkedHashSet<BatchedTask> pending = tasksPerBatchingKey.remove(updateTask.batchingKey);
                if (pending != null) {
                    for (BatchedTask task : pending) {
                        if (task.processed.getAndSet(true) == false) {
                            logger.trace("will process {}", task);
                            //构建要执行的任务列表
                            toExecute.add(task);
                            processTasksBySource.computeIfAbsent(task.source, s -> new ArrayList<>()).add(task);
                        } else {
                            logger.trace("skipping {}, already processed", task);
                        }
                    }
                }
            }

            if (toExecute.isEmpty() == false) {
                final String tasksSummary = processTasksBySource.entrySet().stream().map(entry -> {
                    String tasks = updateTask.describeTasks(entry.getValue());
                    return tasks.isEmpty() ? entry.getKey() : entry.getKey() + "[" + tasks + "]";
                }).reduce((s1, s2) -> s1 + ", " + s2).orElse("");
                //执行任务
                run(updateTask.batchingKey, toExecute, tasksSummary);
            }
        }
    }

执行任务并发布集群状态的逻辑在MasterService中

@Override
        protected void run(Object batchingKey, List<? extends BatchedTask> tasks, String tasksSummary) {
            ClusterStateTaskExecutor<Object> taskExecutor = (ClusterStateTaskExecutor<Object>) batchingKey;
            List<UpdateTask> updateTasks = (List<UpdateTask>) tasks;
            //运行任务,并发布集群状态
            runTasks(new TaskInputs(taskExecutor, updateTasks, tasksSummary));
        }
private void runTasks(TaskInputs taskInputs) {
        final String summary = taskInputs.summary;
        if (!lifecycle.started()) {
            logger.debug("processing [{}]: ignoring, master service not started", summary);
            return;
        }

        logger.debug("executing cluster state update for [{}]", summary);
        //之前集群状态
        final ClusterState previousClusterState = state();
        //只在主节点执行
        if (!previousClusterState.nodes().isLocalNodeElectedMaster() && taskInputs.runOnlyWhenMaster()) {
            logger.debug("failing [{}]: local node is no longer master", summary);
            taskInputs.onNoLongerMaster();
            return;
        }

        final long computationStartTime = threadPool.relativeTimeInMillis();
        //执行task任务生成新的集群状态
        final TaskOutputs taskOutputs = calculateTaskOutputs(taskInputs, previousClusterState);
        taskOutputs.notifyFailedTasks();
        final TimeValue computationTime = getTimeSince(computationStartTime);
        logExecutionTime(computationTime, "compute cluster state update", summary);

        if (taskOutputs.clusterStateUnchanged()) {
            final long notificationStartTime = threadPool.relativeTimeInMillis();
            taskOutputs.notifySuccessfulTasksOnUnchangedClusterState();
            final TimeValue executionTime = getTimeSince(notificationStartTime);
            logExecutionTime(executionTime, "notify listeners on unchanged cluster state", summary);
        } else {//集群状态发生改变
            final ClusterState newClusterState = taskOutputs.newClusterState;
            if (logger.isTraceEnabled()) {
                logger.trace("cluster state updated, source [{}]\n{}", summary, newClusterState);
            } else {
                logger.debug("cluster state updated, version [{}], source [{}]", newClusterState.version(), summary);
            }
            final long publicationStartTime = threadPool.relativeTimeInMillis();
            try {
                ClusterChangedEvent clusterChangedEvent = new ClusterChangedEvent(summary, newClusterState, previousClusterState);
                // new cluster state, notify all listeners
                final DiscoveryNodes.Delta nodesDelta = clusterChangedEvent.nodesDelta();
                if (nodesDelta.hasChanges() && logger.isInfoEnabled()) {
                    String nodesDeltaSummary = nodesDelta.shortSummary();
                    if (nodesDeltaSummary.length() > 0) {
                        logger.info("{}, term: {}, version: {}, delta: {}",
                            summary, newClusterState.term(), newClusterState.version(), nodesDeltaSummary);
                    }
                }

                logger.debug("publishing cluster state version [{}]", newClusterState.version());
                //发布集群状态
                publish(clusterChangedEvent, taskOutputs, publicationStartTime);
            } catch (Exception e) {
                handleException(summary, publicationStartTime, newClusterState, e);
            }
        }
    }

执行方法前判断是不是主节点因为只有主节点可以运行集群任务,根据执行任务前的集群状态执行任务生成新的集群状态

执行任务获取任务执行结果,并生成新的集群状态

private TaskOutputs calculateTaskOutputs(TaskInputs taskInputs, ClusterState previousClusterState) {
        //执行提交的任务,并且返回新的集群状态
        ClusterTasksResult<Object> clusterTasksResult = executeTasks(taskInputs, previousClusterState);
        //根据分配分片结果生成新的集群状态
        ClusterState newClusterState = patchVersions(previousClusterState, clusterTasksResult);
        return new TaskOutputs(taskInputs, previousClusterState, newClusterState, getNonFailedTasks(taskInputs, clusterTasksResult),
            clusterTasksResult.executionResults);
    }

获取任务列表,调用executor的execute方法

private ClusterTasksResult<Object> executeTasks(TaskInputs taskInputs, ClusterState previousClusterState) {
        ClusterTasksResult<Object> clusterTasksResult;
        try {
            List<Object> inputs = taskInputs.updateTasks.stream().map(tUpdateTask -> tUpdateTask.task).collect(Collectors.toList());
            //执行任务,并返回新的集群状态
            clusterTasksResult = taskInputs.executor.execute(previousClusterState, inputs);
            if (previousClusterState != clusterTasksResult.resultingState &&
                previousClusterState.nodes().isLocalNodeElectedMaster() &&
                (clusterTasksResult.resultingState.nodes().isLocalNodeElectedMaster() == false)) {
                throw new AssertionError("update task submitted to MasterService cannot remove master");
            }
        } catch (Exception e) {
           ......
               clusterTasksResult = ClusterTasksResult.builder()
                .failures(taskInputs.updateTasks.stream().map(updateTask -> updateTask.task)::iterator, e)
                .build(previousClusterState);
        }
        ......
        return clusterTasksResult;
    }

这里我们以gateway恢复集群状态为例

ClusterTasksResult<T> execute(ClusterState currentState, List<T> tasks) throws Exception;
@Override
    public final ClusterTasksResult<ClusterStateUpdateTask> execute(ClusterState currentState, List<ClusterStateUpdateTask> tasks)
            throws Exception {
        //执行集群状态变更task,并且返回执行之后的集群状态结果
        ClusterState result = execute(currentState);
        return ClusterTasksResult.<ClusterStateUpdateTask>builder().successes(tasks).build(result);
    }

		@Override
        public void onSuccess(final ClusterState recoveredState) {
            logger.trace("successful state recovery, importing cluster state...");
            clusterService.submitStateUpdateTask("local-gateway-elected-state",
                new RecoverStateUpdateTask() {
                @Override
                public ClusterState execute(final ClusterState currentState) {
                    final ClusterState updatedState = ClusterStateUpdaters.mixCurrentStateAndRecoveredState(currentState, recoveredState);
                    return super.execute(ClusterStateUpdaters.recoverClusterBlocks(updatedState));
                }
            });
        }

@Override
        public ClusterState execute(final ClusterState currentState) {
            if (currentState.blocks().hasGlobalBlock(STATE_NOT_RECOVERED_BLOCK) == false) {
                logger.debug("cluster is already recovered");
                return currentState;
            }
            //状态信息恢复完成
            final ClusterState newState = Function.<ClusterState>identity()
                    .andThen(ClusterStateUpdaters::updateRoutingTable)
                    .andThen(ClusterStateUpdaters::removeStateNotRecoveredBlock)
                    .apply(currentState);
            //开始分配分片
            return allocationService.reroute(newState, "state recovered");
        }

生成新的集群状态,开始分配分片,根据之前的集群状态和新生成的结果构造新的集群状态

private ClusterState patchVersions(ClusterState previousClusterState, ClusterTasksResult<?> executionResult) {
        //新的集群状态
        ClusterState newClusterState = executionResult.resultingState;

        if (previousClusterState != newClusterState) {
            // only the master controls the version numbers
            //生成新的集群状态版本号,递增的
            Builder builder = incrementVersion(newClusterState);
            //路由表发生了改变,也就是分片信息发送了改变,分片-node
            if (previousClusterState.routingTable() != newClusterState.routingTable()) {
                builder.routingTable(RoutingTable.builder(newClusterState.routingTable())
                    .version(newClusterState.routingTable().version() + 1).build());
            }
            //集群元数据发生了改变
            if (previousClusterState.metadata() != newClusterState.metadata()) {
                builder.metadata(Metadata.builder(newClusterState.metadata()).version(newClusterState.metadata().version() + 1));
            }

            newClusterState = builder.build();
        }

        return newClusterState;
    }

回到MasterService的runTasks方法中新的集群状态已经生成并返回,然后判断集群状态和之前的集群状态是否相同,如果发生变化则将进入集群状态发布阶段,将最新的集群状态广播到所有节点

//发布集群状态
publish(clusterChangedEvent, taskOutputs, publicationStartTime);
protected void publish(ClusterChangedEvent clusterChangedEvent, TaskOutputs taskOutputs, long startTimeMillis) {
        final PlainActionFuture<Void> fut = new PlainActionFuture<Void>() {
            @Override
            protected boolean blockingAllowed() {
                return isMasterUpdateThread() || super.blockingAllowed();
            }
        };
        //发布集群状态
        clusterStatePublisher.publish(clusterChangedEvent, fut, taskOutputs.createAckListener(threadPool, clusterChangedEvent.state()));

        // indefinitely wait for publication to complete
        //无限期等待发布完成
        try {
            FutureUtils.get(fut);
            onPublicationSuccess(clusterChangedEvent, taskOutputs);
        } catch (Exception e) {
            onPublicationFailed(clusterChangedEvent, taskOutputs, startTimeMillis, e);
        }
    }
@Override
    public void publish(ClusterChangedEvent clusterChangedEvent, ActionListener<Void> publishListener, AckListener ackListener) {
        //新的集群状态
        ClusterState newState = clusterChangedEvent.state();
        assert newState.getNodes().isLocalNodeElectedMaster() : "Shouldn't publish state when not master " + clusterChangedEvent.source();

        try {

            // state got changed locally (maybe because another master published to us)
            if (clusterChangedEvent.previousState() != this.committedState.get()) {
                throw new FailedToCommitClusterStateException("state was mutated while calculating new CS update");
            }

            pendingStatesQueue.addPending(newState);
            //发布集群状态
            publishClusterState.publish(clusterChangedEvent, electMaster.minimumMasterNodes(), ackListener);
        } catch (FailedToCommitClusterStateException t) {
            // cluster service logs a WARN message
            logger.debug("failed to publish cluster state version [{}] (not enough nodes acknowledged, min master nodes [{}])",
                newState.version(), electMaster.minimumMasterNodes());

            synchronized (stateMutex) {
                pendingStatesQueue.failAllStatesAndClear(
                    new ElasticsearchException("failed to publish cluster state"));

                rejoin("zen-disco-failed-to-publish");
            }

            publishListener.onFailure(t);
            return;
        }

        final DiscoveryNode localNode = newState.getNodes().getLocalNode();
        final AtomicBoolean processedOrFailed = new AtomicBoolean();
        pendingStatesQueue.markAsCommitted(newState.stateUUID(),
            new PendingClusterStatesQueue.StateProcessedListener() {
                @Override
                public void onNewClusterStateProcessed() {
                    processedOrFailed.set(true);
                    publishListener.onResponse(null);
                    ackListener.onNodeAck(localNode, null);
                }

                @Override
                public void onNewClusterStateFailed(Exception e) {
                    processedOrFailed.set(true);
                    publishListener.onFailure(e);
                    ackListener.onNodeAck(localNode, e);
                    logger.warn(() -> new ParameterizedMessage(
                            "failed while applying cluster state locally [{}]", clusterChangedEvent.source()), e);
                }
            });

        synchronized (stateMutex) {
            if (clusterChangedEvent.previousState() != this.committedState.get()) {
                publishListener.onFailure(
                        new FailedToCommitClusterStateException("local state was mutated while CS update was published to other nodes")
                );
                return;
            }
            //经过二阶段提交状态已经发布到了集群,但不能保证所有节点都成功了,下面处理提交后的集群状态
            boolean sentToApplier = processNextCommittedClusterState("master " + newState.nodes().getMasterNode() +
                " committed version [" + newState.version() + "] source [" + clusterChangedEvent.source() + "]");
            if (sentToApplier == false && processedOrFailed.get() == false) {
                assert false : "cluster state published locally neither processed nor failed: " + newState;
                logger.warn("cluster state with version [{}] that is published locally has neither been processed nor failed",
                    newState.version());
                publishListener.onFailure(new FailedToCommitClusterStateException("cluster state that is published locally has neither " +
                        "been processed nor failed"));
            }
        }
    }

首先准备发送集群状态的目标节点列表,剔除本节点。构建增量发布或全量发布集群状态,然后执行序列化并压缩,以便将状态发布出去

public void publish(final ClusterChangedEvent clusterChangedEvent, final int minMasterNodes,
                        final Discovery.AckListener ackListener) throws FailedToCommitClusterStateException {
        final DiscoveryNodes nodes;
        final SendingController sendingController;
        final Set<DiscoveryNode> nodesToPublishTo;
        final Map<Version, BytesReference> serializedStates;
        final Map<Version, BytesReference> serializedDiffs;
        final boolean sendFullVersion;
        try {
            //需要发送目的节点
            nodes = clusterChangedEvent.state().nodes();
            nodesToPublishTo = new HashSet<>(nodes.getSize());
            DiscoveryNode localNode = nodes.getLocalNode();
            final int totalMasterNodes = nodes.getMasterNodes().size();
            for (final DiscoveryNode node : nodes) {
                if (node.equals(localNode) == false) {
                    nodesToPublishTo.add(node);
                }
            }
            sendFullVersion = !discoverySettings.getPublishDiff() || clusterChangedEvent.previousState() == null;
            //全量状态
            serializedStates = new HashMap<>();
            //增量状态
            serializedDiffs = new HashMap<>();

            // we build these early as a best effort not to commit in the case of error.
            // sadly this is not water tight as it may that a failed diff based publishing to a node
            // will cause a full serialization based on an older version, which may fail after the
            // change has been committed.
            //构建序列化后的结果
            buildDiffAndSerializeStates(clusterChangedEvent.state(), clusterChangedEvent.previousState(),
                    nodesToPublishTo, sendFullVersion, serializedStates, serializedDiffs);
            //发布状态返回结果处理
            final BlockingClusterStatePublishResponseHandler publishResponseHandler =
                new AckClusterStatePublishResponseHandler(nodesToPublishTo, ackListener);
            sendingController = new SendingController(clusterChangedEvent.state(), minMasterNodes,
                totalMasterNodes, publishResponseHandler);
        } catch (Exception e) {
            throw new FailedToCommitClusterStateException("unexpected error while preparing to publish", e);
        }

        try {
            //发布
            innerPublish(clusterChangedEvent, nodesToPublishTo, sendingController, ackListener, sendFullVersion, serializedStates,
                serializedDiffs);
        } catch (FailedToCommitClusterStateException t) {
            throw t;
        } catch (Exception e) {
            // try to fail committing, in cause it's still on going
            if (sendingController.markAsFailed("unexpected error", e)) {
                // signal the change should be rejected
                throw new FailedToCommitClusterStateException("unexpected error", e);
            } else {
                throw e;
            }
        }
    }

全量状态保存在serializedStates,增量状态保存在serializedDiffs。每个集群状态都有自己为一个版本好,在发布集群状态时允许相邻版本好之间只发送增量内容

构造需要发送的状态,如果上次发布集群状态的节点不存在或设置了全量发送配置,则构建全量状态否则构建增量状态然后进行序列化并压缩

private void buildDiffAndSerializeStates(ClusterState clusterState, ClusterState previousState, Set<DiscoveryNode> nodesToPublishTo,
                                             boolean sendFullVersion, Map<Version, BytesReference> serializedStates,
                                             Map<Version, BytesReference> serializedDiffs) {
        Diff<ClusterState> diff = null;
        for (final DiscoveryNode node : nodesToPublishTo) {
            try {
                //发送全量
                if (sendFullVersion || !previousState.nodes().nodeExists(node)) {
                    // will send a full reference
                    if (serializedStates.containsKey(node.getVersion()) == false) {
                        serializedStates.put(node.getVersion(), serializeFullClusterState(clusterState, node.getVersion()));
                    }
                } else {
                    //发送增量
                    // will send a diff
                    if (diff == null) {
                        diff = clusterState.diff(previousState);
                    }
                    if (serializedDiffs.containsKey(node.getVersion()) == false) {
                        serializedDiffs.put(node.getVersion(), serializeDiffClusterState(diff, node.getVersion()));
                    }
                }
            } catch (IOException e) {
                throw new ElasticsearchException("failed to serialize cluster_state for publishing to node {}", e, node);
            }
        }
    }

es使用二阶段提交来实现状态发布,第一步是push及先将状态数据发送到node节点,但不应用,如果得到超过半数的节点的返回确认,则执行第二步commit及发送提交请求,二阶段提交不能保证节点收到commit请求后可以正确应用,也就是它只能保证发了commit请求,但是无法保证单个节点上的状态应用是成功还是失败的

  • push阶段发送集群状态数据
private void innerPublish(final ClusterChangedEvent clusterChangedEvent, final Set<DiscoveryNode> nodesToPublishTo,
                              final SendingController sendingController, final Discovery.AckListener ackListener,
                              final boolean sendFullVersion, final Map<Version, BytesReference> serializedStates,
                              final Map<Version, BytesReference> serializedDiffs) {

        final ClusterState clusterState = clusterChangedEvent.state();
        final ClusterState previousState = clusterChangedEvent.previousState();
        //发布超时时间
        final TimeValue publishTimeout = discoverySettings.getPublishTimeout();
        //发布起始时间
        final long publishingStartInNanos = System.nanoTime();
        //遍历节点异步发送全量或增量状态数据
        for (final DiscoveryNode node : nodesToPublishTo) {
            // try and serialize the cluster state once (or per version), so we don't serialize it
            // per node when we send it over the wire, compress it while we are at it...
            // we don't send full version if node didn't exist in the previous version of cluster state
            //发生全量状态
            if (sendFullVersion || !previousState.nodes().nodeExists(node)) {
                sendFullClusterState(clusterState, serializedStates, node, publishTimeout, sendingController);
            } else {
                //发布增量状态
                sendClusterStateDiff(clusterState, serializedDiffs, serializedStates, node, publishTimeout, sendingController);
            }
        }
        //等待提交,等待第一阶段完成收到足够的响应或达到了超时时间
        sendingController.waitForCommit(discoverySettings.getCommitTimeout());

        final long commitTime = System.nanoTime() - publishingStartInNanos;

        ackListener.onCommit(TimeValue.timeValueNanos(commitTime));

        try {
            long timeLeftInNanos = Math.max(0, publishTimeout.nanos() - commitTime);
            final BlockingClusterStatePublishResponseHandler publishResponseHandler = sendingController.getPublishResponseHandler();
            sendingController.setPublishingTimedOut(!publishResponseHandler.awaitAllNodes(TimeValue.timeValueNanos(timeLeftInNanos)));
            if (sendingController.getPublishingTimedOut()) {
                DiscoveryNode[] pendingNodes = publishResponseHandler.pendingNodes();
                // everyone may have just responded
                if (pendingNodes.length > 0) {
                    logger.warn("timed out waiting for all nodes to process published state [{}] (timeout [{}], pending nodes: {})",
                        clusterState.version(), publishTimeout, pendingNodes);
                }
            }
            // The failure is logged under debug when a sending failed. we now log a summary.
            Set<DiscoveryNode> failedNodes = publishResponseHandler.getFailedNodes();
            if (failedNodes.isEmpty() == false) {
                logger.warn("publishing cluster state with version [{}] failed for the following nodes: [{}]",
                    clusterChangedEvent.state().version(), failedNodes);
            }
        } catch (InterruptedException e) {
            // ignore & restore interrupt
            Thread.currentThread().interrupt();
        }
    }

无论是发送全量数据还是发送增量数据最终都会调用到sendClusterStateToNode方法

private void sendClusterStateToNode(final ClusterState clusterState, BytesReference bytes,
                                        final DiscoveryNode node,
                                        final TimeValue publishTimeout,
                                        final SendingController sendingController,
                                        final boolean sendDiffs, final Map<Version, BytesReference> serializedStates) {
        try {
            //调用底层的传输层发送
            transportService.sendRequest(node, SEND_ACTION_NAME,
                    new BytesTransportRequest(bytes, node.getVersion()),
                    stateRequestOptions,
                    new EmptyTransportResponseHandler(ThreadPool.Names.SAME) {

                        @Override
                        public void handleResponse(TransportResponse.Empty response) {
                            //发布超时
                            if (sendingController.getPublishingTimedOut()) {
                                logger.debug("node {} responded for cluster state [{}] (took longer than [{}])", node,
                                    clusterState.version(), publishTimeout);
                            }
                            //检查收到的响应是否过半,然后执行commit
                            sendingController.onNodeSendAck(node);
                        }

                        @Override
                        public void handleException(TransportException exp) {
                            if (sendDiffs && exp.unwrapCause() instanceof IncompatibleClusterStateVersionException) {
                                logger.debug("resending full cluster state to node {} reason {}", node, exp.getDetailedMessage());
                                sendFullClusterState(clusterState, serializedStates, node, publishTimeout, sendingController);
                            } else {
                                logger.debug(() -> new ParameterizedMessage("failed to send cluster state to {}", node), exp);
                                sendingController.onNodeSendFailed(node, exp);
                            }
                        }
                    });
        } catch (Exception e) {
            logger.warn(() -> new ParameterizedMessage("error sending cluster state to {}", node), e);
            sendingController.onNodeSendFailed(node, e);
        }
    }

调用transportService的sendRequest方法异步发送数据,rpc请求为internal:discovery/zen/publish/send对应节点注册的处理器为SendClusterStateRequestHandler

//发送处理
        transportService.registerRequestHandler(SEND_ACTION_NAME, ThreadPool.Names.SAME, false, false, BytesTransportRequest::new,
            new SendClusterStateRequestHandler());

private class SendClusterStateRequestHandler implements TransportRequestHandler<BytesTransportRequest> {

        @Override
        public void messageReceived(BytesTransportRequest request, final TransportChannel channel, Task task) throws Exception {
            //处理状态变更请求
            handleIncomingClusterStateRequest(request, channel);
        }
    }

protected void handleIncomingClusterStateRequest(BytesTransportRequest request, TransportChannel channel) throws IOException {
        Compressor compressor = CompressorFactory.compressor(request.bytes());
        StreamInput in = request.bytes().streamInput();
        final ClusterState incomingState;
        synchronized (lastSeenClusterStateMutex) {
            try {
                if (compressor != null) {
                    in = compressor.streamInput(in);
                }
                in = new NamedWriteableAwareStreamInput(in, namedWriteableRegistry);
                in.setVersion(request.version());
                // If true we received full cluster state - otherwise diffs
                //true:全量状态,false:增量
                if (in.readBoolean()) {
                    incomingState = ClusterState.readFrom(in, transportService.getLocalNode());
                    fullClusterStateReceivedCount.incrementAndGet();
                    logger.debug("received full cluster state version [{}] with size [{}]", incomingState.version(),
                        request.bytes().length());
                } else if (lastSeenClusterState != null) {
                    Diff<ClusterState> diff = ClusterState.readDiffFrom(in, lastSeenClusterState.nodes().getLocalNode());
                    incomingState = diff.apply(lastSeenClusterState);
                    compatibleClusterStateDiffReceivedCount.incrementAndGet();
                    logger.debug("received diff cluster state version [{}] with uuid [{}], diff size [{}]",
                        incomingState.version(), incomingState.stateUUID(), request.bytes().length());
                } else {
                    logger.debug("received diff for but don't have any local cluster state - requesting full state");
                    throw new IncompatibleClusterStateVersionException("have no local cluster state");
                }
            } catch (IncompatibleClusterStateVersionException e) {
                incompatibleClusterStateDiffReceivedCount.incrementAndGet();
                throw e;
            } catch (Exception e) {
                logger.warn("unexpected error while deserializing an incoming cluster state", e);
                throw e;
            } finally {
                IOUtils.close(in);
            }
            //触发监听器
            incomingClusterStateListener.onIncomingClusterState(incomingState);
            lastSeenClusterState = incomingState;
        }
        //发送发回空结果
        channel.sendResponse(TransportResponse.Empty.INSTANCE);
    }

保存集群状态,然后返回空结果

继续回到主节点发送数据的回调函数中,检查响应是否足够

public synchronized void onNodeSendAck(DiscoveryNode node) {
            if (committed) {//提交状态
                assert sendAckedBeforeCommit.isEmpty();
                sendCommitToNode(node, clusterState, this);
            } else if (committedOrFailed()) {
                logger.trace("ignoring ack from [{}] for cluster state version [{}]. already failed", node, clusterState.version());
            } else {
                // we're still waiting
                sendAckedBeforeCommit.add(node);
                if (node.isMasterNode()) {
                    checkForCommitOrFailIfNoPending(node);
                }
            }
        }

  //检查返回ack的节点数,如果超过了半数就执行commit
        private synchronized void checkForCommitOrFailIfNoPending(DiscoveryNode masterNode) {
            logger.trace("master node {} acked cluster state version [{}]. processing ... (current pending [{}], needed [{}])",
                    masterNode, clusterState.version(), pendingMasterNodes, neededMastersToCommit);
            neededMastersToCommit--;
            if (neededMastersToCommit == 0) {
                if (markAsCommitted()) {
                    for (DiscoveryNode nodeToCommit : sendAckedBeforeCommit) {
                        sendCommitToNode(nodeToCommit, clusterState, this);
                    }
                    sendAckedBeforeCommit.clear();
                }
            }
            decrementPendingMasterAcksAndChangeForFailure();
        }
  • commit阶段

接收到了足够的响应后开始执行commit逻辑

private void sendCommitToNode(final DiscoveryNode node, final ClusterState clusterState, final SendingController sendingController) {
        try {
            logger.trace("sending commit for cluster state (uuid: [{}], version [{}]) to [{}]",
                clusterState.stateUUID(), clusterState.version(), node);
            transportService.sendRequest(node, COMMIT_ACTION_NAME,
                    new CommitClusterStateRequest(clusterState.stateUUID()),
                    stateRequestOptions,
                    new EmptyTransportResponseHandler(ThreadPool.Names.SAME) {

                        @Override
                        public void handleResponse(TransportResponse.Empty response) {
                            if (sendingController.getPublishingTimedOut()) {
                                logger.debug("node {} responded to cluster state commit [{}]", node, clusterState.version());
                            }
                            sendingController.getPublishResponseHandler().onResponse(node);
                        }

                        @Override
                        public void handleException(TransportException exp) {
                            logger.debug(() -> new ParameterizedMessage("failed to commit cluster state (uuid [{}], version [{}]) to {}",
                                    clusterState.stateUUID(), clusterState.version(), node), exp);
                            sendingController.getPublishResponseHandler().onFailure(node, exp);
                        }
                    });
        } catch (Exception t) {
            logger.warn(() -> new ParameterizedMessage("error sending cluster state commit (uuid [{}], version [{}]) to {}",
                    clusterState.stateUUID(), clusterState.version(), node), t);
            sendingController.getPublishResponseHandler().onFailure(node, t);
        }
    }

同样通过transportService发生RPC请求,内部请求的url为internal:discovery/zen/publish/commit

接收数据的节点注册的处理器为CommitClusterStateRequestHandler

//提交处理
        transportService.registerRequestHandler(COMMIT_ACTION_NAME, ThreadPool.Names.SAME, false, false, CommitClusterStateRequest::new,
            new CommitClusterStateRequestHandler());

//提交集群状态处理
    private class CommitClusterStateRequestHandler implements TransportRequestHandler<CommitClusterStateRequest> {
        @Override
        public void messageReceived(CommitClusterStateRequest request, final TransportChannel channel, Task task) throws Exception {
            handleCommitRequest(request, channel);
        }
    }

节点应用集群状态

@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) {
                //应用新的集群状态
                processNextCommittedClusterState("master " + state.nodes().getMasterNode() +
                    " committed version [" + state.version() + "]");
            }
        }
    }

 //集群应用新的集群状态
        clusterApplier.onNewClusterState("apply cluster state (from master [" + reason + "])",
            this::clusterState,
            new ClusterApplyListener() {
                @Override
                public void onSuccess(String source) {
                    try {
                        pendingStatesQueue.markAsProcessed(newClusterState);
                    } catch (Exception e) {
                        onFailure(source, e);
                    }
                }

                @Override
                public void onFailure(String source, Exception e) {
                    logger.error(() -> new ParameterizedMessage("unexpected failure applying [{}]", reason), e);
                    try {
                        // TODO: use cluster state uuid instead of full cluster state so that we don't keep reference to CS around
                        // for too long.
                        pendingStatesQueue.markAsFailed(newClusterState, e);
                    } catch (Exception inner) {
                        inner.addSuppressed(e);
                        logger.error(() -> new ParameterizedMessage("unexpected exception while failing [{}]", reason), inner);
                   }
                }
            });

最终调用到ClusterApplierService的runTask方法

private void runTask(UpdateTask task) {
        if (!lifecycle.started()) {
            logger.debug("processing [{}]: ignoring, cluster applier service not started", task.source);
            return;
        }

        logger.debug("processing [{}]: execute", task.source);
        //获取之前的集群状态
        final ClusterState previousClusterState = state.get();
        //任务执行起始时间
        long startTimeMS = currentTimeInMillis();
        //简单的秒表,允许对许多任务进行计时
        final StopWatch stopWatch = new StopWatch();
        final ClusterState newClusterState;
        try {
            try (Releasable ignored = stopWatch.timing("running task [" + task.source + ']')) {
                newClusterState = task.apply(previousClusterState);
            }
        } catch (Exception e) {
            TimeValue executionTime = TimeValue.timeValueMillis(Math.max(0, currentTimeInMillis() - startTimeMS));
            logger.trace(() -> new ParameterizedMessage(
                "failed to execute cluster state applier in [{}], state:\nversion [{}], source [{}]\n{}",
                executionTime, previousClusterState.version(), task.source, previousClusterState), e);
            warnAboutSlowTaskIfNeeded(executionTime, task.source, stopWatch);
            task.listener.onFailure(task.source, e);
            return;
        }

        if (previousClusterState == newClusterState) {
            TimeValue executionTime = TimeValue.timeValueMillis(Math.max(0, currentTimeInMillis() - startTimeMS));
            logger.debug("processing [{}]: took [{}] no change in cluster state", task.source, executionTime);
            warnAboutSlowTaskIfNeeded(executionTime, task.source, stopWatch);
            task.listener.onSuccess(task.source);
        } else {
            if (logger.isTraceEnabled()) {
                logger.debug("cluster state updated, version [{}], source [{}]\n{}", newClusterState.version(), task.source,
                    newClusterState);
            } else {
                logger.debug("cluster state updated, version [{}], source [{}]", newClusterState.version(), task.source);
            }
            try {
                //执行状态更新
                applyChanges(task, previousClusterState, newClusterState, stopWatch);
                TimeValue executionTime = TimeValue.timeValueMillis(Math.max(0, currentTimeInMillis() - startTimeMS));
                logger.debug("processing [{}]: took [{}] done applying updated cluster state (version: {}, uuid: {})", task.source,
                    executionTime, newClusterState.version(),
                    newClusterState.stateUUID());
                warnAboutSlowTaskIfNeeded(executionTime, task.source, stopWatch);
                task.listener.onSuccess(task.source);
            } catch (Exception e) {
                TimeValue executionTime = TimeValue.timeValueMillis(Math.max(0, currentTimeInMillis() - startTimeMS));
                assert applicationMayFail();
                task.listener.onFailure(task.source, e);
            }
        }
    }

遍历所有状态应用者,调用集群状态的应用者的应用集群状态方法

//发送集群状态应用者
callClusterStateAppliers(clusterChangedEvent, stopWatch);
private void callClusterStateAppliers(ClusterChangedEvent clusterChangedEvent, StopWatch stopWatch) {
        clusterStateAppliers.forEach(applier -> {
            logger.trace("calling [{}] with change to version [{}]", applier, clusterChangedEvent.state().version());
            try (Releasable ignored = stopWatch.timing("running applier [" + applier + "]")) {
                applier.applyClusterState(clusterChangedEvent);
            }
        });
    }

遍历所有集群状态监听器,调用集群状态变更回调函数

//发送集群状态监听器
callClusterStateListeners(clusterChangedEvent, stopWatch);
//执行集群状态变更后的回调
    private void callClusterStateListeners(ClusterChangedEvent clusterChangedEvent, StopWatch stopWatch) {
        Stream.concat(clusterStateListeners.stream(), timeoutClusterStateListeners.stream()).forEach(listener -> {
            try {
                logger.trace("calling [{}] with change to version [{}]", listener, clusterChangedEvent.state().version());
                try (Releasable ignored = stopWatch.timing("notifying listener [" + listener + "]")) {
                    listener.clusterChanged(clusterChangedEvent);
                }
            } catch (Exception ex) {
                logger.warn("failed to notify ClusterStateListener", ex);
            }
        });
    }

回到主节点执行回调函数handleResponse和handleException两个回调函数执行相同的处理逻辑,将latch减一,如果有的节点执行失败也不会执行修复逻辑。

Elasticsearch集群状态的发布与应用 文章目录简介集群状态的发布二段提交总流程阶段一:发布过程阶段二:提交过程增量发布的实现原理集群状态的应用 简介 如果产生了新的集群状态,则主节点会把它广播到其他节点。主节点和其他节点的通信使用最广泛的方式,就是通过下发集群状态让从节点执行相应的处理。控制信息、变更信息都存储在集群状态中。 集群状态在ES中封装为ClusterState类。可以通过_cluster/state API来获取集群状态。 curl -X GET "localhost:9200/_cluster/state" 响应信息中提供了集群 阅读详情

相关推荐

技术挑战如何转化为成长阶梯:从问题解决到系统学习的实践框架

在软件工程实践中,开发者常面临各种技术挑战。理解这些挑战背后的通用原理,是构建扎实技术能力的基础。从概念上看,无论是数据库性能瓶颈还是系统设计争议,其本质往往涉及底层机制、系统架构或工程规范等核心原理。掌握从现象到本质的分析方法,例如通过观察、假设、验证、抽象、记录的闭环学习回路,能够将离散的问题解决转化为系统性的知识积累。这种学习方式的技术价值在于,它能帮助开发者超越“灭火式”的应急处理,建立起可复用的排查框架和决策模型,从而提升工程效率和系统稳定性。在实际应用场景中,面对线上数据库CPU飙升这类系统性挑

weixin_34252686的博客 386

Elasticsearch分布式一致性原理剖析(二)-Meta篇

Elasticsearch分布式一致性原理剖析”系列将会对Elasticsearch的分布式一致性原理进行详细的剖析,介绍其实现方式、原理以及其存在的问题等(基于6.2版本)。前一篇的内容包括了ES的集群组成、节点发现与Master选举、错误检测与集群扩缩容等。本篇将在前一篇的基础上,重点分析ES中meta更新的一致性问题,为了便于读者理解 ,本文还介绍了Master管理集群的方式、meta组成、存储方式等等。Master如何管理集群Meta组成、存储和恢复ClusterState的更新流程。

大叶子不小的博客 2090

.NET技术博客的深度写作方法论:从源码到生产实践

.NET作为成熟的企业级开发平台,其技术传播长期面临版本漂移、原理黑箱与业务脱节三大挑战。理解.NET Runtime底层机制(如IL指令、GC代际行为、async/await状态机)是构建系统性认知的前提;而真实问题驱动的内容生产(如HttpClient连接池泄漏、LOH碎片化、配置绑定陷阱)则让知识具备可验证性与工程穿透力。技术价值不仅在于解释‘怎么用’,更在于揭示‘为什么这样设计’和‘不这样做的代价’,从而支撑架构决策。典型应用场景覆盖微服务治理、云原生部署、高并发中间件调优及遗留系统现代化迁移。本文

世范水晶 186

RocketMQ源码分析(一)NameServer

1. 源码分析 1.1 环境搭建 1.1.1 源码拉取 源码目录结构: broker: broker 模块(broke 启动进程) client :消息客户端,包含消息生产者、消息消费者相关类 common :公共包 dev :开发者信息(非源代码) distribution :部署实例文件夹(非源代码) example: RocketMQ 例代码 filter :消息过滤相关基础类 filtersrv:消息过滤服务器实现相关

YHJ 380

揭秘GitHub Copilot的gpt-5.4:不是新模型,而是128K context的工程优化

大语言模型的上下文长度(context length)是影响代码补全质量的核心约束,其本质是模型输入窗口的token容量限制,由基础架构与推理成本共同决定。技术价值在于平衡响应速度、内存开销与语义完整性,典型应用场景包括长文件理解、跨文件类型推导和复杂配置联动。当前主流开发工具如GitHub Copilot实际依赖gpt-4o等128K context模型,所谓'GPT-5.4'和'1M context'实为前端路由别名与RAG索引规模的误读。本文深入拆解Copilot七层推理链路,揭示M3上下文裁剪策略如

weixin_34343000的博客 452

elasticsearch中节点都启动但是无法形成集群问题

近日,单台机器,8个节点的es集群,8个节点都正常started了,但是就是无法形成集群,后来看日志,日志中出现一堆的MasterNotDiscoveredException这种异常,完整日志如下: [2016-04-27 15:08:22,445][DEBUG][action.admin.cluster.health] [es.10.16.66.152.0] no known master n

jianjun200607的专栏 3万+

res-downloader macOS 资源下载完整指南:从证书安装到代理配置

证书装好了,浏览器却还在提示"不受信任";代理开起来,网页反而打不开;等半天,res-downloader 的资源列表还是空的。本文按"证书信任、代理设置、捕获验证"三步,带你把 macOS 上的 res-downloader 从装好走到第一个资源出现,并给出常见故障的处理方式。 ## 动手之前,先确认这3件事 - res-downloader 已放入 Applications 文件夹 - 系

gitblog_00652的博客 274

【Es】Elasticsearch 7.x 新的集群协调层

1.概述 转载:https://www.easyice.cn/archives/332 ES 7.x 重构了一个新的集群协调层,他实际上是 Raft 的实现,但并非严格按照 Raft 论文实现,而是做了一些调整,不过话说自己实现 Raft 协议的时候一般也都会做一些修改。下面我们看一下 ES 实现的 Raft 与标准 Raft 的异同,先简单介绍一些 Raft 中的一些基本概念,完整的Raft中文翻译参考文末。 1. Raft 基础 Raft是工程上使用较为广泛分布式共识协议,是多个节点对某个事情达成一致.

九师兄 2462

技术人如何用博客构建个人知识图谱:从记录到复用

技术博客本质上是一种面向工程实践的知识管理方式,其核心在于将临时性工作记忆固化为可检索、可关联、可演进的长期知识资产。基于认知科学中的遗忘曲线原理,持续记录真实问题场景、环境快照与决策链条,能有效对抗技术细节的快速衰减;通过结构化标签与时间戳锚定,形成带上下文的个人知识图谱,显著提升问题复现与跨项目迁移效率。这种以‘Record’为驱动的技术写作范式,已广泛应用于Kubernetes排障、MySQL死锁分析、Nginx配置调试等高频实战场景,成为中高级开发者构建第二大脑的关键基础设施。

天下无双 392

使用两阶段提交实现 Elasticsearch 中的事务功能

然而,通过使用两阶段提交(Two-Phase Commit,简称 2PC)协议,我们可以在 Elasticsearch 中模拟事务功能。使用以上提供的示例代码,您可以开始在 Elasticsearch 中实现事务功能,并根据需要进行自定义扩展和改进行自定义扩展和改进。然后,将所有需要参与事务的操作(例如索引、更新或删除文档)添加到参与者列表中,并将参与者列表更新到 Elasticsearch 中。在准备阶段,我们首先生成一个唯一的事务 ID,并将其保存到 Elasticsearch 中。

JieLun_C的博客 430

手把手教你搭建幽冥传奇手游:从环境配置到常见Bug修复(附完整避坑指南)

本文详细指导如何从零搭建幽冥传奇手游,涵盖服务器选型、数据库部署、双区架构配置等关键步骤,并提供通天塔闪退、技能显示异常等常见Bug的修复方案。附完整避坑指南,帮助开发者高效解决90%的典型问题,确保游戏稳定运行。

weixin_29232121的博客 240

程序员技术成长的动态闭环:从知识输入到认知校准

技术成长不是线性积累,而是一个涉及阅读、实践、表达与反馈的动态系统。理解‘知识加工-输出-反馈’的延迟性与耦合性,是突破‘学了不会用’‘写了没提升’困境的关键。本文聚焦程序员最常失真的两个环节——技术博客写作与论坛互动,揭示其本质是元认知训练与可证伪假设的构建过程。通过决策快照表、反流量写作、四段式提问等实操方法,将抽象能力转化为可测量、可迭代的行为单元。适用于希望摆脱无效勤奋、提升技术判断力与架构思辨能力的中高级开发者。

SailingAptech的专栏 467

ArcGIS 10.4 Desktop安装步骤。

步骤 1 :安装 LicenseManager 有了安装包以后,我们进入安装包文件目录,如图所示,首先需要安装ArcGIS license manager,即点击打开图中红色框中exe文件。 PS:安装 LicenseManager,关于 License 的安装路径一般默认即可,因为很小的,装C 盘也无所谓了,当然了自己改了也可以的,但是不建议! 即便修改路径,建议只改盘符,不修改相对路径方式! 如:C:\Program Files (x86)\ArcGIS\License10.6 修改为 D:\.

IT爱好者 2万+

大数据实操篇 No.15-Elasticsearch集群高可用部署(含Elasticsearch head+Kibana)

第1章 ELK简介 ELK(Elasticsearch、Logstash、Kibana)提供了一整套日志分析解决方案,其中elasticsearch是开源分布式搜索引擎。Logstash是一个开源的日志采集工具。Kibana也是开源的数据分析可视化工具。三者结合,Logstash采集系统日志信息后,上传到Elasticsearch中,kibana在利用Elasticsearch分布式高效的检索引擎,将数据可视化的展示到Web界面上。 对于一些中小型的系统,可以用ELK快速搭建一套日志分析系统。本篇文章为

pezynd的专栏 2066

实战指南 | IC模拟版图设计从零到精通,手把手带你掌握EDA工具与GDSII输出

本文提供了一份从零开始的IC模拟版图设计实战指南。详细介绍了如何搭建Linux环境、安装Cadence Virtuoso等EDA工具,并深入解析了PDK设计规则。通过手把手绘制CMOS反相器版图的实例,系统讲解了版图绘制、DRC/LVS验证、寄生参数提取、匹配设计以及ESD防护等核心技能,最终指导如何正确生成交付芯片制造厂的标准GDSII文件,帮助读者掌握完整的模拟版图设计流程。

weixin_29214691的博客 389

TDSQL-单机安装

根据腾讯云官方要求,TDSQL安装至少需要三台机器。本人想要学习TDSQL但是没有足够的机器资源,经过仔细分析研究TDSQL安装过程,尝试发现单台机器也可以成功安装TDSQL,现将本人单台机器安装TDSQL的过程以及安装过程中遇到的问题在此分享。具体安装过程可参考腾讯官方文档,本文仅分享与官方文档不一致的步骤。

u012607648的博客 6805

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

elasticsearch是如何构建集群服务框架的呢?

thomas0yang的专栏 4656

第18届全国大学生智能汽车竞赛四轮车开源讲解【2.5】-- 图像处理实战:从二值化到边缘检测的工程优化

本文深入讲解了第18届全国大学生智能汽车竞赛四轮车开源项目中图像处理的核心实战技术。文章聚焦于二值化与边缘检测两大关键环节,详细对比了大津法与索贝尔算子的工程应用与优化策略,并分享了遮光、偏振片等物理抗干扰技巧,旨在帮助参赛者构建稳定高效的图像预处理流水线,为赛道识别奠定坚实基础。

weixin_29279047的博客 100

部署k8s时,对node节点加入master出错问题

[ERROR FileAvailable–etc-kubernetes-kubelet.conf]: /etc/kubernetes/kubelet.conf already exists 解决办法: 在node节点中输入: kubeadm reset

pinkCoderMonkey的博客 925

Geogebra中级篇016—矩阵(Matrices)

在GeoGebra中,矩阵是一种强大的工具,广泛应用于线性代数、几何变换和数据处理等领域。通过矩阵,我们可以轻松实现复杂的计算和图形变换,如旋转、平移和缩放等操作。不仅如此,GeoGebra的矩阵功能还支持多种运算,如加法、乘法、求逆等,使得用户能够高效地处理线性方程组和向量空间问题。在本文中,我们将深入探讨GeoGebra中的矩阵功能,详细介绍如何创建矩阵、执行常见的矩阵运算,并通过实例展示这些操作在数学和工程应用中的实际价值。这篇文章将为你提供关于GeoGebra矩阵操作的全面指南。

八荒启-交互动画的博客 2579

国密GmSSL v2版本命令行方式生成国密sm2私钥、公钥、签名和验证签名

GmSSL是国密算法的工具库(主要包含SM2、SM3、SM4和国密SSL证书生成等功能),项目本身是OpenSSL的分支,但是截至文章发布为止,OpenSSL主分支的国密算法并不完善,目前并不支持签名和解签,所以国密算法这块还是需要使用GmSSL工具库。网上现在并没有v2版本的GmSSL可用的命令行文章,本章用于记录这些命令。本文原创自csdn博客,其他网站看到这篇文章都是盗,版,爬。虫。网站,支持原创,远离盗版。

eguid音视频方向博客,分享经过实践检验的硬核音视频技术。FFmpeg、openCV和JavaCV音视频和图像处理系列教程发布十周年,众多开发者的严格验证。欢迎关注,一起交流探索学习最新音视频和图像处理技术 4601

Elasticsearch部分节点不能发现集群(脑裂)问题处理

Elasticsearch部分节点不能发现集群(脑裂)问题处理

胡杰的专栏 3万+

【YOLOv11改进 - 特征融合】DySample :超轻量级且高效的动态上采样器

【YOLOv11改进 - 特征融合】DySample :超轻量级且高效的动态上采样器

专注于图像领域,主要研究内容包括计算机视觉和深度学习,特别是在图像分类、目标检测和图像生成等方面有深入的研究和实践经验。 1070

elasticsearch源码分析之索引操作(九)

介绍es索引的基本操作功能(create、exist、delete),用来进一步细化es集群是如果工作的

thomas0yang的专栏 3050

精雕软件(JDpaint) 5.5 绿色版

精雕软件(JDpaint) 5.5 绿色版精雕软件(JDpaint) 5.5 绿色版精雕软件(JDpaint) 5.5 绿色版

lwip-2.0.3

lwip是瑞典计算机科学院(SICS)的Adam Dunkels 开发的一个小型开源的TCP/IP协议栈。实现的重点是在保持TCP协议主要功能的基础上减少对RAM 的占用。

Elasticsearch-7.8.0 创建索引底层源码分析(1)

1、前言 本专栏系列基于 elasticsearch-7.8 版本分析 陆续打算推出的博文列表如下: 1、创建索引底层源码分析 2、更新索引底层源码分析 3、删除索引底层源码分析 4、elasticsearch中的线程池实现 5、elasticsearch启动过程源码流程分析 6、high level rest client请求流程分析 7、elastic search数据副本模型、读写模型 8、lucene学习总结系列 全文检索的基本原理 lucene的总体结构 lucene的索引文件格式..

dkjhl的博客 1628

复分析 阿尔福斯(Ahlfors) 第三版答案 英文

答案分为十章,有部分习题没有答案,全英文复分析阿尔福斯(Ahlfors) 第三版答案

kubernetes集群node加入不了master错误处理

#如果node加入不了master或者加入成功但是,在master中显示不出来。排查错误:1. 运行,kubelet, 查看日志,一般是kubelet的运行和docker启动方式不匹配。调整:vim /etc/docker/daemon.json修改:native.cgroupdriver=cgroupfs#重启dockersystemctl daemon-reloadsystemct...

weixin_30477293的博客 1734

ET2012新版免狗免安装绿色软件

ET2012新版免狗免安祖昂绿色软件。,新界面,新视觉,

上一篇: elasticsearch源码分析-03选举集群状态
下一篇: elasticsearch源码分析-05分片分配
胖柯G
博客等级 码龄10年 453粉丝 57原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

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

抵扣说明:

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

余额充值