Qdrant 集群下的共识服务(中)

详解 Qdrant 集群健康检查机制,通过对比各节点的 commit_log_index 确保集群一致性,包含超时重试和退避策略。

作者:npc001 · 2026-06-27T10:00:00.000000000+00:00

Qdrant 集群下的共识服务(中)

qdrant 集群在启动的时候会进行健康检查 其中最重要是同步日志即保证commitLogIndex是一致。

1.健康检查

    // 集群模式会做健康检查
    let health_checker = Arc::new(common::health::HealthChecker::spawn(
        toc_arc.clone(),
        consensus_state.clone(),
        &runtime_handle,
        consensus_state.is_new_deployment() && bootstrap.is_some(),
    ));

    // 设置当前启动实例的一些状态,同时将检查的操作封装到Task中,调用exec() 来处理
    pub fn spawn(
        toc: Arc<TableOfContent>,
        consensus_state: ConsensusStateRef,
        runtime: &runtime::Handle,
        wait_for_bootstrap: bool,
    ) -> Self {
        let task = Task {
            toc,
            consensus_state,
            is_ready: Default::default(),
            is_ready_signal: Default::default(),
            check_ready_signal: Default::default(),
            cancel: Default::default(),
            wait_for_bootstrap,
        };

        let health_checker = Self {
            is_ready: task.is_ready.clone(),
            is_ready_signal: task.is_ready_signal.clone(),
            check_ready_signal: task.check_ready_signal.clone(),
            _cancel: task.cancel.clone().drop_guard(),
        };
        // 生成任务
        let task = runtime.spawn(task.exec());
    }
    // 具体实现
        async fn exec_impl(&self) {
        // 从集群中的响应中取出一个作为当前日志的索引 不一定是最终 
        let Some(mut cluster_commit_index) = self.cluster_commit_index(true).await else {
            self.set_ready();
            return;
        };

        // 根据raft的算法给所有的其他节点确保至少一半的正确回复,循环当前的实例与其他的节点的
        // commmit_log_index 达到相同
        loop {
   
            match self.cluster_commit_index(false).await {
                // 相同结束
                Some(new_index) if cluster_commit_index == new_index => break,
                // 更新当前实例的index
                Some(new_index) => cluster_commit_index = new_index,
                // 错误退出
                None => break,
            }
        }
    }



        async fn cluster_commit_index(&self, one_peer: bool) -> Option<u64> {
 
        // 获取集群对应的节点信息并发送请求
        let peer_address_by_id = self.consensus_state.peer_address_by_id();
        let transport_channel_pool = &self.toc.get_channel_service().channel_pool;
        let this_peer_id = self.toc.this_peer_id;
        let this_peer_uri = peer_address_by_id.get(&this_peer_id);
        let mut requests = peer_address_by_id
            .values()
            .filter(|&uri| Some(uri) != this_peer_uri)

            .unique()
            .map(
                |uri| 
                // 发送请求 最终是调用函数with_channel_timeout
                get_consensus_commit(transport_channel_pool, uri)
                )
            .collect::<FuturesUnordered<_>>()
            .inspect_err(|err| log::error!("GetConsensusCommit request failed: {err}"))
            .filter_map(|res| future::ready(res.ok()));      
        // Raft 共识操作要求至少达到大多数(所有节点/ 2 + 1)
        let sufficient_commit_indices_count = if !one_peer {
            // 当前节点算一个节点 所以 就是直接除以2
            peer_address_by_id.len() / 2
        } else {
            1
        };

        // 等待 `total nodex / 2` 成功响应
        let mut commit_indices: Vec<_> = (&mut requests)
            .take(sufficient_commit_indices_count)
            .collect()
            .await;
        // 找到所有所有响应中最大commit_index作为同步的commit_index
        let cluster_commit_index = commit_indices
            .into_iter()
            .map(|resp| resp.into_inner().commit)
            .max()
            .unwrap_or(0);

        Some(cluster_commit_index as _)
    }

    
    pub async fn with_channel_timeout<T, O: Future<Output = Result<T, Status>>>(
        &self,
        uri: &Uri,
        f: impl Fn(InterceptedService<Channel, AddTimeout>) -> O,
        timeout: Option<Duration>,
        retries: usize,
    ) -> Result<T, RequestError<Status>> {
        // 重试
        let mut retries_left = retries;
        let mut attempt = 0;
        let max_timeout = timeout.unwrap_or_else(|| self.grpc_timeout + self.connection_timeout);

        loop {
            let request_result: Result<T, _> = self.make_request(uri, &f, max_timeout).await;

            let error_result = match request_result {
                Ok(body) => return Ok(body),
                Err(err) => err,
            };

            let action = match error_result {
                RequestFailure::HealthCheck(healthcheck_error) => {
                    // 根据不同的错误类型来处理 
                }

            let (backoff_time, fallback_status) = match action {
                RetryAction::Fail(err) => return Err(RequestError::FromClosure(err)),
                RetryAction::RetryImmediately(fallback_status) => (Duration::ZERO, fallback_status),
                RetryAction::RetryWithBackoff(fallback_status) => {
                    //backoff  失败后会加上随机时间来重试 
                    let backoff = DEFAULT_BACKOFF * 2u32.pow(attempt as u32)
                        + Duration::from_millis(rng().random_range(0..100));

                    if backoff > max_timeout {
                        return Err(RequestError::FromClosure(fallback_status));
                    }
                    (backoff, fallback_status)
                }
                RetryAction::RetryOnce(fallback_status) => {
                    if retries_left > 1 {
                        retries_left = 1;
                    }
                    (Duration::ZERO, fallback_status)
                }
            };

            attempt += 1;
            // 多次重试失败后直接异常
            if retries_left == 0 {
                return Err(RequestError::FromClosure(fallback_status));
            }
            retries_left = retries_left.saturating_sub(1);
            // 休眠
            tokio::time::sleep(backoff_time).await;
        }
        }
    }