Skip to content

03. QuorumController 核心实现 ​

本文档导读

本文档深入分析 QuorumController 的核心实现,包括单线程事件处理模型和请求处理机制。

预计阅读时间: 45 分钟

相关文档:


3. QuorumController 核心实现 ​

3.1 单线程事件模型 ​

java
/**
 * QuorumController 设计哲学:
 *
 * 1. 单线程事件处理
 *    - 所有操作都在同一个事件队列中执行
 *    - 避免复杂的锁机制
 *    - 保证操作的顺序性
 *
 * 2. 异步 API
 *    - 所有公开 API 都返回 CompletableFuture
 *    - 调用者不会阻塞
 *    - 结果在事件完成后通过 Future 返回
 *
 * 3. Raft 集成
 *    - 元数据变更通过 Raft 协议复制
 *    - 只有 Leader 可以写入元数据
 *    - Follower 只能读取
 */

// org/apache/kafka/controller/QuorumController.java

public final class QuorumController implements Controller {
    /**
     * 事件队列: 所有操作都在这个队列中串行执行
     */
    private final KafkaEventQueue eventQueue;

    /**
     * Raft 客户端: 用于读写元数据日志
     */
    private final RaftClient<ApiMessageAndVersion> raftClient;

    /**
     * 元数据快照: 当前集群元数据的完整视图
     */
    private final MetadataDelta metadataDelta;

    /**
     * 集群控制管理器: 管理 Broker 注册、心跳等
     */
    private final ClusterControlManager clusterControl;

    /**
     * 分区管理器:管理 Topic、 Partition、 副本分配
     */
    private final PartitionReplicationReplicaManager partitionManager;

    /**
     * 配置管理器: 管理动态配置
     */
    private final ConfigurationControlManager configurationControl;

    /**
     * 特性管理器: 管理特性版本
     */
    private final FeatureControlManager featureControl;

    // ... 其他管理器
}

3.2 QuorumController 初始化 ​

java
// QuorumController.Builder.build()

public QuorumController build() {
    // ========== 1. 创建快照注册表 ==========
    /**
     * SnapshotRegistry 用于管理元数据快照
     * 支持时间旅行查询和回滚
     */
    SnapshotRegistry snapshotRegistry = new SnapshotRegistry(logContext);

    // ========== 2. 创建事件队列 ==========
    /**
     * KafkaEventQueue 是一个单线程事件队列
     * 所有操作都在这个队列中执行
     */
    KafkaEventQueue eventQueue = new KafkaEventQueue(
        Time.SYSTEM,
        logContext,
        new EarliestDeadlineFunction(),
        "quorum-controller-" + nodeId + "-",
        true  // 单线程模式
    );

    // ========== 3. 创建各个管理器 ==========
    ClusterControlManager clusterControl = new ClusterControlManager.Builder()
        .setNodeId(nodeId)
        .setTime(time)
        .setThreadNamePrefix(threadNamePrefix)
        .setSnapshotRegistry(snapshotRegistry)
        .setLogContext(logContext)
        .setSessionTimeoutNs(sessionTimeoutNs)
        .setBrokerHeartbeatIntervalNs(brokerHeartbeatIntervalNs)
        .setFatalFaultHandler(fatalFaultHandler)
        .build();

    PartitionReplicationReplicaManager partitionManager =
        new PartitionReplicationReplicaManager.Builder()
            .setNodeId(nodeId)
            .setTime(time)
            .setThreadNamePrefix(threadNamePrefix)
            .setSnapshotRegistry(snapshotRegistry)
            .setLogContext(logContext)
            .setDefaultReplicationFactor(defaultReplicationFactor)
            .setDefaultNumPartitions(defaultNumPartitions)
            .setReplicaPlacer(replicaPlacer)
            .setLeaderImbalanceCheckIntervalNs(leaderImbalanceCheckIntervalNs)
            .build();

    ConfigurationControlManager configurationControl =
        new ConfigurationControlManager.Builder()
            .setNodeId(nodeId)
            .setTime(time)
            .setSnapshotRegistry(snapshotRegistry)
            .setLogContext(logContext)
            .setConfigSchema(configSchema)
            .build();

    FeatureControlManager featureControl = new FeatureControlManager.Builder()
        .setNodeId(nodeId)
        .setTime(time)
        .setThreadNamePrefix(threadNamePrefix)
        .setSnapshotRegistry(snapshotRegistry)
        .setLogContext(logContext)
        .setQuorumFeatures(quorumFeatures)
        .build();

    // ... 其他管理器

    // ========== 4. 创建 QuorumController ==========
    QuorumController controller = new QuorumController(
        logContext,
        nodeId,
        clusterId,
        time,
        threadNamePrefix,
        snapshotRegistry,
        eventQueue,
        raftClient,
        maxRecordsPerBatch,
        // ... 各个管理器
    );

    // ========== 5. 初始化 Raft 客户端 ==========
    /**
     * 设置 Raft 回调:
     * - 当成为 Leader 时调用
     * - 当有新记录可以读取时调用
     * - 当快照需要创建时调用
     */
    raftClient.register(listener);

    return controller;
}

3.3 CreateTopic 请求处理流程 ​

java
/**
 * CreateTopic 完整流程:
 *
 * 1. ControllerApis 接收请求
 * 2. 转发到 QuorumController
 * 3. 在事件队列中处理
 * 4. 生成元数据记录
 * 5. 通过 Raft 写入日志
 * 6. 等待多数节点确认
 * 7. 更新内存中的元数据
 * 8. 通知所有 Publishers
 * 9. 返回结果给客户端
 */

// QuorumController.createTopics()

public CompletableFuture<CreateTopicsResponseData> createTopics(
    CreateTopicsRequestData request
) {
    // ========== 1. 创建异步操作 ==========
    /**
     * ControllerOperation 是一个封装了操作逻辑的对象
     * 它会在事件队列中执行
     */
    CreateTopicsOperation op = new CreateTopicsOperation(
        request,
        deadline,
        apiTimeoutTimeNs
    );

    /**
     * 将操作放入事件队列
     * 不会阻塞当前线程
     */
    appendEvent(op);
    return op.future();
}

// ========== 2. 操作在事件队列中执行 ==========

private class CreateTopicsOperation extends ControllerOperation {
    @Override
    public void run() throws Exception {
        // ========== 2.1 检查是否是 Leader ==========
        /**
         * 只有 Leader 可以处理写请求
         * Follower 会将请求转发给 Leader
         */
        if (!isActiveController()) {
            completeFuture(new ApiError(
                NOT_CONTROLLER,
                "This controller is not the active controller."
            ));
            return;
        }

        // ========== 2.2 验证请求 ==========
        /**
         * 检查:
         * - Topic 名称是否合法
         * - Topic 是否已存在
         * - 副本因子是否合理
         * - 分区数是否合理
         */
        ApiError error = validateCreateTopics(request);
        if (error.isFailure()) {
            completeFuture(error);
            return;
        }

        // ========== 2.3 生成元数据记录 ==========
        /**
         * 为每个 Topic 生成元数据记录:
         * - TopicRecord: Topic 元数据
         * - PartitionRecord: 分区元数据
         */
        List<ApiMessageAndVersion> records = new ArrayList<>();

        for (CreatableTopic topic : request.topics()) {
            // 生成 TopicRecord
            records.add(new ApiMessageAndVersion(
                new TopicRecord()
                    .setName(topic.name())
                    .setTopicId(topicId),
                topic.topicId() == null ? (short) 0 : (short) 1
            ));

            // 为每个分区生成 PartitionRecord
            for (CreatablePartition partition : topic.assignments()) {
                // 计算副本分配
                List<Integer> replicas = partitionManager.assignReplicas(
                    partition.brokerIds(),
                    partition.replicationFactor()
                );

                records.add(new ApiMessageAndVersion(
                    new PartitionRecord()
                        .setTopicId(topicId)
                        .setPartitionId(partition.partitionIndex())
                        .setReplicas(replicas)
                        .setIsr(replicas)
                        .setLeader(replicas.get(0)),
                    (short) 0
                ));
            }
        }

        // ========== 2.4 写入元数据日志 ==========
        /**
         * 通过 Raft 协议写入记录
         * 这个操作会:
         * 1. 写入本地日志
         * 2. 复制到 Follower
         * 3. 等待多数节点确认
         * 4. 返回写入结果
         */
        CompletableFuture<Long> appendFuture = raftClient.append(
            records,
            AppendRequest.DEFAULT_TIMEOUT_MS,
            false  // 是否需要全部确认
        );

        // ========== 2.5 等待写入完成 ==========
        appendFuture.whenComplete((offset, exception) -> {
            if (exception != null) {
                completeFuture(new ApiError(
                    UNKNOWN_SERVER_ERROR,
                    "Failed to append to metadata log: " + exception.getMessage()
                ));
            } else {
                // ========== 2.6 创建响应 ==========
                completeFuture(null);  // 成功
            }
        });
    }
}

3.4 元数据记录类型 ​

java
/**
 * Kafka 的元数据通过多种类型的记录表示
 * 每种记录对应一种元数据变更
 */

// ===== 核心元数据记录 =====

// 1. TopicRecord: Topic 元数据
class TopicRecord {
    String name;        // Topic 名称
    Uuid topicId;       // Topic 唯一 ID
}

// 2. PartitionRecord: 分区元数据
class PartitionRecord {
    Uuid topicId;           // 所属 Topic ID
    int partitionId;        // 分区 ID
    List<Integer> replicas; // 副本列表
    List<Integer> isr;      // In-Sync Replicas
    int leader;             // Leader 副本
    int leaderEpoch;        // Leader 版本号
    int partitionEpoch;     // 分区版本号
}

// 3. RegisterBrokerRecord: Broker 注册
class RegisterBrokerRecord {
    int brokerId;           // Broker ID
    Uuid brokerUuid;        // Broker 唯一 ID
    String rack;            // 机架信息
    long epoch;             // Broker 版本号
    boolean fenced;         // 是否被隔离
}

// 4. BrokerRegistrationChangeRecord: Broker 变更
class BrokerRegistrationChangeRecord {
    int brokerId;
    long brokerEpoch;
    boolean fenced;
}

// 5. ConfigRecord: 配置变更
class ConfigRecord {
    String resourceType;    // 资源类型 (Topic/Broker)
    String resourceName;    // 资源名称
    String configKey;       // 配置键
    String configValue;     // 配置值
}

// 6. AccessControlEntryRecord: ACL
class AccessControlEntryRecord {
    String resourceType;
    String resourceName;
    String principal;
    String host;
    String operation;
    String permissionType;
}

// 7. FeatureLevelRecord: 特性版本
class FeatureLevelRecord {
    String name;        // 特性名称
    long featureLevel;  // 特性版本号
}

// ... 还有 20+ 种其他记录类型