Kafka 的数据复制机制 ISR(In-Sync Replicas)

1,066 阅读4分钟

Kafka 的数据复制机制旨在确保数据的高可用性和容错性。其核心机制之一是 ISR(In-Sync Replicas),即同步副本。

Kafka 数据复制机制

Kafka 中的每个主题(Topic)可以分为多个分区(Partition),每个分区都有一个唯一的主副本(Leader),以及若干个从副本(Follower)。Leader 负责处理所有的读写请求,而 Follower 负责从 Leader 同步数据。

ISR(In-Sync Replicas)

ISR 是指一组与 Leader 同步的副本集合。具体来说,ISR 包含了 Leader 和所有在一定时间范围内(由参数 replica.lag.time.max.ms 控制)与 Leader 保持同步的 Follower 副本。ISR 的成员资格是动态的,Kafka 通过定期检查 Follower 是否与 Leader 保持同步来维护 ISR 的成员列表。

具体实现过程

  1. Leader 维护日志:Leader 负责维护分区的日志,并处理所有的写请求。每次有新的消息写入 Leader 的日志后,Leader 会将该消息发送给所有的 Follower。

  2. Follower 同步数据:Follower 从 Leader 拉取数据并将其写入自己的本地日志。Follower 会定期向 Leader 发送心跳消息,报告其日志的最新偏移量。

  3. ISR 的维护:Kafka 会定期检查每个 Follower 是否与 Leader 保持同步。如果某个 Follower 在一定时间内没有与 Leader 保持同步(即其日志偏移量落后于 Leader 超过一定阈值),则该 Follower 会被移出 ISR。

  4. Leader 选举:当当前的 Leader 失效(如崩溃或网络分区)时,Kafka 会从 ISR 中选举一个新的 Leader。因为 ISR 中的副本是与 Leader 保持同步的,所以选出的新 Leader 能够保证数据的一致性和完整性。

重要参数

  • replica.lag.time.max.ms:定义了 Follower 落后于 Leader 的最大允许时间。如果超过这个时间,Follower 将被移出 ISR。
  • min.insync.replicas:定义了一个分区在接受写操作之前必须在 ISR 中存在的最小副本数。这有助于确保在写操作时有足够的副本来保证数据的持久性。

优点

  • 高可用性:即使某些副本失效,只要 ISR 中还有副本存在,Kafka 就可以继续提供服务。
  • 数据一致性:通过 ISR 机制,Kafka 能够确保在发生故障时,选出的新 Leader 拥有完整的数据。

总结

Kafka 的数据复制机制通过 Leader 和 Follower 的协作以及 ISR 的动态维护,实现了数据的高可用性和一致性。ISR 是确保数据同步和可靠性的关键概念,它动态地反映了当前与 Leader 保持同步的副本集合,从而在 Leader 失效时能够迅速选出新的 Leader 以继续提供服务。

ISR维护逻辑

了解 Kafka 的 ISR 源码可以帮助我们更深入地理解其实现机制。Kafka 是用 Scala 编写的,以下是关于 ISR 的一些关键源码片段和解释。

ISR 维护逻辑

ISR 的维护主要发生在 Kafka 的 ReplicaManagerPartition 类中。以下是一些关键点:

  1. ISR 的数据结构: ISR 是一个 scala.collection.mutable.Set,它存储了所有与 Leader 同步的副本的 ID。

  2. 添加和移除 ISR: 当 Follower 副本与 Leader 同步或不同步时,Kafka 会更新 ISR 集合。

  3. 定期检查 ISR: Kafka 定期检查每个 Follower 的同步状态,并根据结果更新 ISR。

以下是一些关键源码片段:

Partition 类中的 ISR 维护

class Partition(val topicPartition: TopicPartition, val replicaManager: ReplicaManager) {
  private val inSyncReplicas: mutable.Set[Int] = mutable.Set.empty

  def addReplicaIfNotExists(replicaId: Int): Unit = {
    inSyncReplicas synchronized {
      if (!inSyncReplicas.contains(replicaId)) {
        inSyncReplicas += replicaId
      }
    }
  }

  def removeReplica(replicaId: Int): Unit = {
    inSyncReplicas synchronized {
      inSyncReplicas -= replicaId
    }
  }

  def maybeUpdateIsr(): Unit = {
    inSyncReplicas synchronized {
      // 逻辑:检查每个 Follower 的同步状态,并更新 ISR
      val newInSyncReplicas = inSyncReplicas.filter { replicaId =>
        val replica = getReplica(replicaId)
        replica.isInSync
      }

      if (newInSyncReplicas != inSyncReplicas) {
        inSyncReplicas.clear()
        inSyncReplicas ++= newInSyncReplicas
        // 通知 ISR 变更
        replicaManager.isrChangeListener.onIsrChange(this)
      }
    }
  }
}

Replica 类中的同步状态检查

class Replica(val brokerId: Int, val log: Log) {
  def isInSync: Boolean = {
    val lastCaughtUpTimeMs = log.lastCaughtUpTimeMs
    val currentTimeMs = System.currentTimeMillis()
    (currentTimeMs - lastCaughtUpTimeMs) <= replicaManager.config.replicaLagTimeMaxMs
  }
}

ReplicaManager 类中的 ISR 变更通知

class ReplicaManager(val config: KafkaConfig) {
  val isrChangeListener: IsrChangeListener = new IsrChangeListener {
    override def onIsrChange(partition: Partition): Unit = {
      // 逻辑:处理 ISR 变更,可能包括更新 Zookeeper 或其他元数据存储
      updateIsrInZookeeper(partition)
    }
  }

  def updateIsrInZookeeper(partition: Partition): Unit = {
    // 逻辑:将新的 ISR 集合更新到 Zookeeper
  }
}

关键点总结

  • ISR 数据结构:ISR 是一个可变集合,存储了所有与 Leader 同步的副本 ID。
  • 添加/移除 ISR:当副本的同步状态发生变化时,Kafka 会更新 ISR 集合。
  • 同步状态检查:通过检查 Follower 副本的同步状态(如日志的最后同步时间),Kafka 决定是否将其保留在 ISR 中。
  • ISR 变更通知:当 ISR 发生变更时,Kafka 会通过 IsrChangeListener 通知其他组件,并更新元数据存储(如 Zookeeper)。

这些源码片段展示了 Kafka 如何通过定期检查副本的同步状态并动态维护 ISR 集合,从而实现数据的一致性和高可用性。