Skip to content

02. 分区 Leader 选举 ​

目录 ​


1. Leader 选举概述 ​

1.1 选举的重要性 ​

text
Leader 选举的核心目标:

1. 高可用性 (High Availability)
   ├── Leader 故障时快速切换
   ├── 最小化服务中断时间
   └── 保证持续提供服务

2. 数据一致性 (Data Consistency)
   ├── 新 Leader 有所有已提交数据
   ├── 避免数据丢失
   └── 避免数据冲突

3. 负载均衡 (Load Balancing)
   ├── Leader 分布在不同 Broker
   ├── 避免热点
   └── 优化集群性能
```text

### 1.2 选举类型
```scala
/**
 * Leader 选举类型
 */

object LeaderElectionType {
  // 1. Controller 触发的选举
  //    - 分区创建时
  //    - 优先副本选举
  //    - 副本重新分配
  val ControllerTriggered = 1

  // 2. Broker 触发的选举
  //    - Leader 故障检测
  //    - 自动选举
  val BrokerTriggered = 2

  // 3. Admin 触发的选举
  //    - 手动触发
  //    - 管理员干预
  val AdminTriggered = 3
}
```bash

### 1.3 选举参与者

```mermaid
graph TB
    Controller[Controller] -->|发起选举| Partition[Partition]
    Broker1[Broker 1<br/>Candidate] -->|参与选举| Partition
    Broker2[Broker 2<br/>ISR Member] -->|参与选举| Partition
    Broker3[Broker 3<br/>ISR Member] -->|参与选举| Partition

    Partition -->|选择| NewLeader[新 Leader]

    Controller -->|通知| ZK[Metadata Log]
    ZK -->|持久化| LeaderInfo[Leader 信息]

    Controller -->|广播| AllBrokers[所有 Broker]

    NewLeader -->|开始服务| Clients[Producer/Consumer]

    style Controller fill:#f9f,stroke:#333,stroke-width:4px
    style NewLeader fill:#9f9,stroke:#333,stroke-width:4px
```scala

---

## 2. 选举触发条件

### 2.1 Controller 触发选举

```scala
/**
 * Controller 触发的选举场景
 */

object LeaderElectionTrigger {

  // 1. 分区创建时
  def onPartitionCreated(topic: String, partitionId: Int): Unit = {
    // 选择 AR 中的第一个副本作为 Leader
    val assignedReplicas = replicaAssignment(topic, partitionId)
    val leader = assignedReplicas.head

    electLeader(
      topicPartition = new TopicPartition(topic, partitionId),
      newLeader = leader,
      leaderEpoch = epoch
    )
  }

  // 2. 优先副本选举
  //    - 将 Leader 迁移到优先副本 (AR[0])
  //    - 用于负载均衡
  def onPreferredReplicaElection(partitions: Set[TopicPartition]): Unit = {
    partitions.foreach { tp =>
      val assignedReplicas = replicaAssignment(tp)
      val preferredReplica = assignedReplicas.head

      if (currentLeader(tp) != preferredReplica) {
        // 触发选举, 迁移到优先副本
        electLeader(tp, preferredReplica)
      }
    }
  }

  // 3. 副本重新分配
  def onPartitionReassignment(tp: TopicPartition,
                             newAssignment: Seq[Int]): Unit = {
    // 根据新分配方案选择 Leader
    // 优先选择新 AR 中仍在新 ISR 里的副本
    val currentISR = partitionISR(tp)
    val newLeader = newAssignment.find(currentISR.contains).getOrElse(
      newAssignment.head
    )

    electLeader(tp, newLeader)
  }
}
```bash

### 2.2 Broker 故障触发

```scala
/**
 * Broker 故障检测与选举
 */

class Controller {
  // ========== Broker 监控 ==========
  val brokerWatcher = new BrokerWatcher()

  /**
   * 检测到 Broker 下线
   */
  def onBrokerFailure(deadBrokerId: Int): Unit = {
    info(s"Broker $deadBrokerId has been shutdown")

    // ========== 步骤1: 找到该 Broker 上的所有 Leader 分区 ==========
    val leaderPartitionsOnDeadBroker = allPartitions.filter { case (tp, assignment) =>
      assignment.leader == deadBrokerId
    }.keys

    // ========== 步骤2: 对每个分区发起新的选举 ==========
    leaderPartitionsOnDeadBroker.foreach { tp =>
      electNewLeader(tp)
    }
  }

  /**
   * 选举新 Leader
   */
  def electNewLeader(tp: TopicPartition): Unit = {
    val partition = getPartition(tp)
    val assignment = partition.replicaAssignment
    val isr = partition.inSyncReplicaIds

    // ========== 步骤3: 从 ISR 中选择新 Leader ==========
    // 策略: 选择 AR 中排在最前面的 ISR 副本
    val newLeaderOpt = assignment.replicas.find(isr.contains)

    newLeaderOpt match {
      case Some(newLeader) =>
        // 找到合适的新 Leader
        info(s"Selected new leader $newLeader for partition $tp")
        makeLeader(tp, newLeader)

      case None =>
        // ISR 中没有可用副本
        // 根据 unclean.leader.election.enable 决定是否允许 OSR 成为 Leader
        if (config.uncleanLeaderElectionEnabled) {
          val newLeader = assignment.replicas.head
          warn(s"Unclean leader election for partition $tp, new leader: $newLeader")
          makeLeader(tp, newLeader)
        } else {
          error(s"No ISR replica available for partition $tp, unclean election disabled")
          // 分区不可用, 等待 ISR 恢复
        }
    }
  }
}
```scala

### 2.3 网络分区触发

```scala
/**
 * 网络分区导致的选举
 *
 * 场景: ISR 中副本无法与 Leader 通信
 */

class ReplicaManager {
  /**
   * 检测 Leader 是否失效
   */
  def checkLeaderFailure(): Unit = {
    leaderPartitionsIterator.foreach { partition =>
      val currentLeader = partition.leaderReplicaIdOpt

      // ========== 检查 Leader 是否存活 ==========
      // 1. 检查 Leader Epoch 是否过期
      // 2. 检查 Leader 是否还在 ISR 中
      // 3. 检查是否收到 Controller 的 Leader 变更通知

      if (isLeaderExpired(partition)) {
        // Leader 已过期, 触发选举
        info(s"Leader $currentLeader for partition ${partition.topicPartition} is expired")
        requestLeaderElection(partition.topicPartition)
      }
    }
  }

  /**
   * 判断 Leader 是否过期
   */
  private def isLeaderExpired(partition: Partition): Boolean = {
    // 检查 Leader Epoch
    val currentLeaderEpoch = partition.leaderEpoch
    val expectedLeaderEpoch = controllerContext.epochFor(partition.topicPartition)

    currentLeaderEpoch < expectedLeaderEpoch
  }
}
```mermaid

---

## 3. 选举流程

### 3.1 选举流程图

```mermaid
sequenceDiagram
    participant Controller as Controller
    participant ISR as ISR 副本集合
    participant Candidate as 候选 Leader
    participant Metadata as Metadata Log
    participant Brokers as 所有 Broker

    Controller->>Controller: 检测到 Leader 失效

    Controller->>ISR: 检查 ISR 状态
    ISR-->>Controller: 返回 ISR 列表

    Controller->>Controller: 选择新 Leader
    Note over Controller: 策略: AR ∩ ISR 中优先级最高的

    Controller->>Candidate: 选举为新 Leader
    activate Candidate

    Candidate->>Candidate: makeLeader()
    Candidate->>Candidate: 初始化 Leader Epoch
    Candidate->>Candidate: 加载 HW

    Candidate-->>Controller: 成为 Leader 成功
    deactivate Candidate

    Controller->>Metadata: 持久化 Leader 信息
    Metadata-->>Controller: 写入成功

    Controller->>Brokers: 广播 Leader 变更
    Brokers-->>Controller: 确认

    Controller->>Controller: 更新元数据缓存

    Brokers->>Candidate: 开始同步
    Note over Candidate: 开始接受读写请求

    style Controller fill:#f9f,stroke:#333,stroke-width:4px
    style Candidate fill:#9f9,stroke:#333,stroke-width:4px
```scala

### 3.2 选举算法

```scala
/**
 * Partition Leader 选举算法
 */

object LeaderElectionAlgorithm {

  /**
   * 选择新 Leader
   *
   * 策略:
   * 1. 优先选择 AR (Assigned Replica) 中排在最前面的副本
   * 2. 必须在 ISR 中 (除非允许 Unclean 选举)
   * 3. 必须在线
   */
  def selectNewLeader(
    partition: Partition,
    uncleanElectionEnabled: Boolean
  ): Option[Int] = {

    val assignedReplicas = partition.replicaAssignment.replicas
    val isr = partition.inSyncReplicaIds
    val aliveBrokers = controllerContext.liveBrokerIds

    // ========== 策略 1: 从 ISR 中选择 ==========
    // 选择 AR ∩ ISR 中优先级最高的
    val isrLeader = assignedReplicas.find { replicaId =>
      isr.contains(replicaId) && aliveBrokers.contains(replicaId)
    }

    if (isrLeader.isDefined) {
      info(s"Selected leader ${isrLeader.get} from ISR for partition ${partition.topicPartition}")
      return isrLeader
    }

    // ========== 策略 2: Unclean 选举 ==========
    // 如果允许,从 AR 中选择第一个在线副本
    if (uncleanElectionEnabled) {
      val arLeader = assignedReplicas.find(aliveBrokers.contains)

      if (arLeader.isDefined) {
        warn(s"Unclean election: selected leader ${arLeader.get} from AR for partition ${partition.topicPartition}")
        return arLeader
      }
    }

    // ========== 策略 3: 无可用 Leader ==========
    error(s"No available leader for partition ${partition.topicPartition}")
    None
  }
}
```bash

### 3.3 Partition.makeLeader()

```scala
/**
 * Partition.makeLeader() - 成为 Leader
 *
 * 被选为 Leader 后的初始化流程
 */
def makeLeader(
  controllerEpoch: Int,
  partitionLeaderEpoch: Int,
  isNewLeader: Boolean
): Boolean = {
  // ========== 步骤1: 更新 Leader Epoch ==========
  if (leaderEpoch >= partitionLeaderEpoch) {
    info(s"Leader ${localBrokerId} is already the leader for partition $topicPartition " +
         s"with epoch $leaderEpoch, ignoring request with epoch $partitionLeaderEpoch")
    return false
  }

  leaderEpoch = partitionLeaderEpoch

  // ========== 步骤2: 初始化 Leader 副本 ==========
  val leaderReplica = localReplica()
  if (leaderReplica.isDefined) {
    leaderReplica.get.convertToLeaderIfLocal()
  }

  // ========== 步骤3: 初始化 HW ==========
  // 从 Leader Epoch 文件中恢复 HW
  val hw = logManager.highWatermark(topicPartition)
  if (hw.isPresent) {
    log.updateHighWatermark(hw.get())
  } else {
    // 如果没有 HW, 从 LEO 开始
    log.updateHighWatermark(log.logEndOffset)
  }

  // ========== 步骤4: 清空延迟操作 ==========
  // 移除 DelayedProducePurgatory 中等待的请求
  replicaManager.delayedProducePurgatory.checkAndComplete(topicPartition)

  // ========== 步骤5: 标记为可提供服务的 Leader ==========
  // 开始接受 Produce/Fetch 请求

  stateChangeLogger.info(
    s"Completed leader transition for partition $topicPartition\n" +
    s"Leader epoch: $partitionLeaderEpoch\n" +
    s"ISR: ${inSyncReplicaIds.mkString(",")}\n" +
    s"HW: $highWatermark"
  )

  true
}

3.4 Partition.makeFollower() ​

scala
/**
 * Partition.makeFollower() - 成为 Follower
 *
 * 不再是 Leader 后转为 Follower
 */
def makeFollower(
  controllerEpoch: Int,
  partitionLeaderEpoch: Int,
  newLeaderBrokerId: Int,
  isNewLeader: Boolean
): Boolean = {
  // ========== 步骤1: 检查 Leader Epoch ==========
  if (leaderEpoch >= partitionLeaderEpoch) {
    info(s"Broker $localBrokerId is already a follower for partition $topicPartition " +
         s"with epoch $leaderEpoch, ignoring request with epoch $partitionLeaderEpoch")
    return false
  }

  leaderEpoch = partitionLeaderEpoch

  // ========== 步骤2: 更新状态 ==========
  stateChangeLogger.info(
    s"Adding follower for partition $topicPartition\n" +
    s"Current leader epoch: $leaderEpoch\n" +
    s"New leader: $newLeaderBrokerId"
  )

  // ========== 步骤3: 初始化 Follower 副本 ==========
  val followerReplica = localReplica()
  if (followerReplica.isDefined) {
    followerReplica.get.convertToFollowerIfLocal()
  }

  // ========== 步骤4: 开始从 Leader 拉取数据 ==========
  // ReplicaFetcherManager 会处理
  replicaManager.replicaFetcherManager.addFetcherForPartitions(
    Set(topicPartition)
  )

  // ========== 步骤5: 清空未确认的消息 ==========
  // 移除 DelayedProducePurgatory 中等待的请求
  replicaManager.delayedProducePurgatory.checkAndComplete(topicPartition)

  stateChangeLogger.info(
    s"Completed follower transition for partition $topicPartition\n" +
    s"Leader epoch: $partitionLeaderEpoch\n" +
    s"Leader: $newLeaderBrokerId"
  )

  true
}

4. Unclean Leader 选举 ​

4.1 什么是 Unclean 选举 ​

Unclean Leader 选举定义:

正常选举 (Clean Election):
├── 从 ISR 中选择新 Leader
├── 新 Leader 有所有已提交数据
└── 保证数据一致性

Unclean 选举:
├── ISR 中没有可用副本
├── 从 OSR (Out-of-Sync Replicas) 中选择
├── 新 Leader 可能缺少数据
└── 可能导致:
    ├── 数据丢失 (未确认数据)
    ├── 数据冲突 (Consumer 重复消费)
    └── 一致性问题
```bash

### 4.2 Unclean 选举触发条件

```scala
/**
 * Unclean 选举条件
 */

object UncleanLeaderElection {

  /**
   * 检查是否需要 Unclean 选举
   */
  def shouldTriggerUncleanElection(
    partition: Partition,
    config: KafkaConfig
  ): Boolean = {

    // ========== 条件 1: 配置允许 ==========
    if (!config.uncleanLeaderElectionEnable) {
      info(s"Unclean leader election is disabled for partition ${partition.topicPartition}")
      return false
    }

    // ========== 条件 2: ISR 中没有可用副本 ==========
    val isr = partition.inSyncReplicaIds
    val aliveBrokers = controllerContext.liveBrokerIds
    val availableISR = isr.filter(aliveBrokers.contains)

    if (availableISR.nonEmpty) {
      // ISR 中还有可用副本, 不需要 Unclean 选举
      return false
    }

    // ========== 条件 3: AR 中有可用副本 ==========
    val assignedReplicas = partition.replicaAssignment.replicas
    val availableAR = assignedReplicas.filter(aliveBrokers.contains)

    if (availableAR.isEmpty) {
      // 所有副本都不可用, 无法选举
      error(s"No available replicas for partition ${partition.topicPartition}")
      return false
    }

    // ========== 满足所有条件, 触发 Unclean 选举 ==========
    warn(s"Triggering unclean leader election for partition ${partition.topicPartition}")
    true
  }
}
```scala

### 4.3 Unclean 选举流程

```scala
/**
 * Unclean 选举实现
 */

def electUncleanLeader(partition: Partition): Option[Int] = {
  val assignedReplicas = partition.replicaAssignment.replicas
  val aliveBrokers = controllerContext.liveBrokerIds

  // ========== 步骤1: 从 AR 中选择第一个在线副本 ==========
  val newLeader = assignedReplicas.find(aliveBrokers.contains)

  newLeader match {
    case Some(leaderId) =>
      // ========== 步骤2: 选举新 Leader ==========
      warn(
        s"Unclean leader election for partition ${partition.topicPartition}\n" +
        s"New leader: $leaderId (not in ISR)\n" +
        s"ISR: ${partition.inSyncReplicaIds.mkString(",")}\n" +
        s"Data loss is possible!"
      )

      // ========== 步骤3: 记录 Unclean 选举指标 ==========
      uncleanLeaderElectionEnabled.mark()

      Some(leaderId)

    case None =>
      // ========== 步骤4: 无可用副本 ==========
      error(s"No available replicas for unclean election of partition ${partition.topicPartition}")
      None
  }
}
```bash

### 4.4 Unclean 选举的影响

```text
Unclean 选举的影响:

数据丢失:
├── 旧 Leader 有未确认数据
├── 新 Leader (OSR) 没有这些数据
└── 结果: 永久丢失

数据冲突:
├── Consumer 已读取部分未确认数据
├── 新 Leader 没有这些数据
└── 结果: Consumer 重复消费

一致性问题:
├── HW 回退
├── 已提交数据可能丢失
└── 违反一致性保证

示例:

时间线:
T1: Producer 发送 M1, M2, M3 (acks=all)
T2: Leader 确认 M1, M2 (HW=2)
T3: Leader 写入 M3 (未确认, HW=2)
T4: Leader 故障
T5: Unclean 选举, OSR 成为新 Leader
T6: 新 Leader HW=0 (M1, M2, M3 都丢失!)
```bash

### 4.5 配置建议

```text
unclean.leader.election.enable 配置建议:

| 配置项 | 默认值 | 说明 |
|-------|-------|------|
| unclean.leader.election.enable | false | 是否允许非 ISR 副本成为 Leader |

默认值: false (不启用)

适用场景:

启用 (true):
├── 测试环境
├── 数据可丢失的场景
├── 优先保证可用性
└── 灾难恢复 (最后手段)

禁用 (false): [推荐]
├── 生产环境
├── 数据一致性要求高
├── 金融/支付场景
└── 宁可不可用, 不丢数据

替代方案:
├── 增加副本数 (降低 ISR 全故障概率)
├── 使用 min.insync.replicas > 1
├── 多机房部署
└── 及时修复故障 Broker```

---

## 5. Leader Epoch 机制

### 5.1 Leader Epoch 概述

```scala
/**
 * Leader Epoch - Leader 的版本号
 *
 * 作用:
 * 1. 识别过期的 Leader
 * 2. 防止"脑裂" (Split-Brain)
 * 3. 保证数据一致性
 * 4. 确定日志截断位置
 *
 * 示例:
 * Leader Epoch 0: Broker-A 是 Leader
 * Leader Epoch 1: Broker-B 是 Leader (A 故障)
 * Leader Epoch 2: Broker-A 是 Leader (B 故障, A 恢复)
 *
 * 如果 Epoch 2 的 Leader 收到 Epoch 1 的请求
 * → 识别为过期 Leader
 * → 拒绝请求
 * → 避免数据不一致
 */

5.2 Leader Epoch 文件 ​

scala
/**
 * Leader Epoch 文件格式
 *
 * 文件名: leader-epoch-checkpoint
 *
 * 格式:
 * ┌──────────────┬──────────────┐
 * │ Leader Epoch │  End Offset  │
 * ├──────────────┼──────────────┤
 * │      0       │    100       │  Epoch 0 的最后 Offset
 * │      1       │    200       │  Epoch 1 的最后 Offset
 * │      2       │    350       │  Epoch 2 的最后 Offset
 * │     ...      │    ...       │
 * └──────────────┴──────────────┘
 *
 * 用途:
 * - 崩溃恢复时确定截断位置
 * - 判断 Leader 是否过期
 * - 解决数据不一致
 */

case class LeaderEpochEntry(
  epoch: Int,        // Leader Epoch 编号
  startOffset: Long  // 该 Epoch 起始 Offset
)

/**
 * Leader Epoch Cache
 */
class LeaderEpochFile {
  private val epochs = new ConcurrentHashMap[Int, Long]()

  /**
   * 添加新的 Epoch
   */
  def assign(epoch: Int, offset: Long): Unit = {
    epochs.put(epoch, offset)
    flush()
  }

  /**
   * 查找 Offset 对应的 Epoch
   */
  def epochFor(offset: Long): Int = {
    epochs.entrySet()
      .filter(_.getValue <= offset)
      .maxBy(_.getKey)
      .getKey
  }

  /**
   * 查找 Epoch 对应的结束 Offset
   */
  def endOffsetFor(epoch: Int): Option[Long] = {
    // 查找下一个 Epoch 的起始 Offset
    val nextEpoch = epochs.keySet().filter(_ > epoch).minOption
    nextEpoch.map(epochs.get)
  }
}
```bash

### 5.3 Leader Epoch 在选举中的应用

```scala
/**
 * Leader Epoch 在选举中的应用
 */

class Partition {
  /**
   * 检查 Leader Epoch 是否有效
   */
  def validateLeaderEpoch(requestEpoch: Int): Boolean = {
    val currentEpoch = leaderEpoch

    if (requestEpoch < currentEpoch) {
      // 请求的 Epoch 过期
      warn(
        s"Received request with stale epoch $requestEpoch " +
        s"for partition $topicPartition (current epoch: $currentEpoch)"
      )
      return false
    }

    if (requestEpoch > currentEpoch) {
      // 请求的 Epoch 更新,可能是新的 Leader
      info(
        s"Received request with newer epoch $requestEpoch " +
        s"for partition $topicPartition (current epoch: $currentEpoch)"
      )
    }

    true
  }
}
```scala

### 5.4 Leader Epoch 与日志截断

```scala
/**
 * Leader Epoch 用于日志截断
 *
 * 场景: Follower 恢复后需要截断日志
 */

class ReplicaFetcherThread {
  /**
   * Follower 启动时的截断流程
   */
  private def truncateOnFetch(): Unit = {
    val partition = replicaMgr.getPartition(topicPartition)

    // ========== 步骤1: 发送 OffsetForLeaderEpochRequest ==========
    val request = new OffsetForLeaderEpochRequest(
      partitions = Map(topicPartition -> new LeaderEpochOffset())
    )

    val response = fetchFromLeader(request)

    // ========== 步骤2: 获取 Leader 的 Epoch 和 Offset ==========
    val leaderEpoch = response.leaderEpoch(topicPartition)
    val leaderEndOffset = response.endOffset(topicPartition)

    // ========== 步骤3: 截断到该 Epoch 的结束 Offset ==========
    partition match {
      case Some(p) =>
        val localLog = p.localLogOrException
        val truncationOffset = localLog.truncationOffsetFor(leaderEpoch)

        if (truncationOffset.isPresent && localLog.logEndOffset > truncationOffset.get()) {
          info(
            s"Truncating partition $topicPartition to offset $truncationOffset " +
            s"(leader epoch: $leaderEpoch)"
          )

          // 截断日志
          localLog.truncateTo(truncationOffset.get())
        }

      case None =>
        error(s"Partition $topicPartition not found")
    }
  }
}
```text

---

## 6. 选举实战案例

### 6.1 案例 1: Broker 故障选举
```text
场景: 3 副本集群, Broker 1 故障

初始状态:
├── Topic: test-partition-0
├── AR: [1, 2, 3]
├── Leader: Broker 1
└── ISR: [1, 2, 3]

T1: Broker 1 故障
T2: Controller 检测到故障
T3: 从 ISR [2, 3] 中选择新 Leader
    ├── 选择策略: AR 中优先级最高的
    └── 结果: Broker 2 成为新 Leader
T4: 更新元数据
T5: 广播到所有 Broker
T6: Broker 3 开始从 Broker 2 同步

最终状态:
├── Leader: Broker 2
├── ISR: [2, 3]
└── 数据: 无丢失 (Broker 1 的未确认数据除外)

验证命令:
kafka-leader-election.sh \
  --bootstrap-server localhost:9092 \
  --topic test \
  --partition 0 \
  --election-type PREFERRED
```bash

### 6.2 案例 2: ISR 全故障选举

```text
场景: ISR 中所有副本故障, 需要 Unclean 选举

初始状态:
├── Topic: test-partition-0
├── AR: [1, 2, 3]
├── Leader: Broker 1
└── ISR: [1, 2]

T1: Broker 1 和 Broker 2 同时故障
T2: ISR: []
T3: Controller 检测到 ISR 为空
T4: 检查 unclean.leader.election.enable
    ├── 如果 = false: 分区不可用
    └── 如果 = true: 从 OSR 选择 Leader
T5: 选择 Broker 3 (OSR) 成为 Leader
T6: HW 回退到 0
T7: 可能丢失数据

恢复后:
├── Broker 1 恢复
├── Broker 1 成为 Follower
├── 截断日志到 Broker 3 的 HW
└── 丢失未确认数据

避免方法:
├── 增加副本数 (5 副本)
├── 设置 min.insync.replicas > 1
├── 多机房部署
└── 禁用 unclean 选举```bash

### 6.3 案例 3: 优先副本选举

```text
场景: 负载不均衡, 需要迁移到优先副本

初始状态:
├── Topic: test-partition-0
├── AR: [1, 2, 3]  (1 是优先副本)
├── Leader: Broker 2 (非优先)
└── ISR: [2, 3]

问题: Broker 2 负载过高

解决方案: 优先副本选举

步骤:
1. 生成选举文件
cat > preferred-replica-election.json <<EOF
{
  "partitions": [
    {
      "topic": "test",
      "partition": 0
    }
  ]
}
EOF

2. 执行选举
kafka-preferred-replica-election.sh \
  --bootstrap-server localhost:9092 \
  --path-to-json-file preferred-replica-election.json

3. 验证
kafka-metadata-shell --snapshot <metadata-quorum.properties> \
  --csv-select-record Partition:test:0

结果:
├── Leader: Broker 1 (优先副本)
├── ISR: [1, 2, 3]
└── 负载: 更均衡

最佳实践:
├── 定期执行优先副本选举
├── 在低峰期执行
└── 监控 Leader 分布
```bash

### 6.4 案例 4: 手动 Leader 选举

```text
场景: 需要手动指定 Leader

使用场景:
├── 维护窗口
├── 性能优化
└── 故障恢复

KIP-631 (新版本):
kafka-leader-election.sh \
  --bootstrap-server localhost:9092 \
  --topic test \
  --partition 0 \
  --election-type UNCLEAN \
  --leader 3

指定 Leader 为 Broker 3```

---

## 7. 总结

### 7.1 Leader 选举要点

| 要点 | 说明 |
|-----|------|
| **选举目标** | 高可用 + 数据一致性 |
| **选举策略** | AR ∩ ISR 中优先级最高 |
| **Unclean 选举** | 可能丢失数据, 生产环境禁用 |
| **Leader Epoch** | 防止脑裂, 保证一致性 |
| **快速切换** | 通常秒级完成 |

### 7.2 最佳实践

```text
1. 合理配置副本数
   └── 推荐 3 副本或 5 副本

2. 禁用 Unclean 选举
   └── unclean.leader.election.enable=false

3. 定期执行优先副本选举
   └── 保持负载均衡

4. 监控选举频率
   └── 频繁选举说明有问题

5. 监控 ISR 状态
   └── 确保 ISR 健康```

---

**下一步**: [03. 副本拉取机制](./03-replica-fetcher.md)