Kafka 复习


什么是Kafka

Apache Kafka是一个高性能的分布式流处理平台,最初由LinkedIn公司内部开发用于处理大规模实时数据流,后来于2011年成为Apache基金会的开源项目。它作为一个强大的消息中间件,专门设计用于处理高容量、低延迟的实时数据流处理场景。Kafka架构优雅且可扩展,能够无缝地处理数百万条消息,同时保持毫秒级的传输延迟。它具有分布式、可分区、可复制的特性,不仅提供了极高的吞吐量和可靠性,还支持数据持久化和容错能力,使其成为现代数据管道和流处理应用的基础组件。

Kafka的核心概念

  • Producer(生产者):发布消息到Kafka的客户端应用程序

  • Consumer(消费者):订阅并处理来自Kafka的消息流的客户端应用程序

  • Topic(主题):Topic 是 Kafka 中对消息进行逻辑分类的单元。所有发布到 Kafka 集群的消息都必须归属于某一个 Topic。

  • Partition(分区):每个主题可以分为多个分区,允许并行处理,提高吞吐量

  • Broker(代理):一个独立的 Kafka 服务器实例被称为一个 Broker。多个 Broker 组合在一起构成一个 Kafka 集群。

  • Consumer Group(消费者组):一组协同工作的消费者,共同消费主题中的消息

  • Offset(偏移量):分区内每条消息的唯一标识符

  • Zookeeper:管理和协调Kafka broker的服务

Kafka的特点

  • 高吞吐量:能够处理数百万条消息

  • 低延迟:消息传递的延迟可以控制在毫秒级

  • 可扩展性:可以无缝扩展为处理更多数据

  • 持久性:消息被持久化到磁盘,防止数据丢失

  • 容错性:在节点失败的情况下继续运行

  • 高并发:支持数千个客户端同时读写

  • 实时处理:能够实时处理流数据

Kafka的应用场景

  • 消息队列:作为传统消息中间件的替代

  • 日志收集:收集分布式系统中的日志

  • 流处理:实时处理数据流

  • 事件溯源:记录状态变更事件

  • 活动跟踪:跟踪用户行为和活动

  • 指标监控:收集和监控运营数据

一个服务器只能有一个 Broker 吗?

这是一个关于“理论”与“最佳实践”的问题。

  • 从理论上讲:不是。
    你完全可以在一台物理或虚拟服务器上,启动多个不同的 Broker 进程。只要你为每个 Broker 进程配置不同的端口号(例如 9092, 9093, 9094)、不同的 broker.id 和不同的数据存储目录,它们就可以在同一台机器上共存。

  • 从生产实践上讲:是的,最佳实践是一台服务器只运行一个 Broker 实例。
    为什么呢?

    1. 资源隔离: Kafka 是一个重度依赖磁盘 I/O 和内存的系统。将一个服务器的全部资源(CPU、内存、磁盘、网络带宽)都分配给一个 Broker 实例,可以避免多个 Broker 实例之间争抢资源,从而获得最稳定和可预测的性能。
    2. 简化运维: 一台服务器对应一个 Broker,使得监控、管理和故障排查都变得非常简单明了。如果一台服务器上的某个 Broker 出了问题,你立刻就能定位到是哪台机器。
    3. 故障隔离: 如果一台服务器因为硬件故障宕机了,你只会损失一个 Broker。如果你在一台服务器上运行了三个 Broker,那一次宕机就会同时损失三个 Broker,对集群的冲击更大。

小结:在生产环境中,标准的最佳实践是一台服务器只部署和运行一个 Broker 实例,以确保资源隔离和简化运维。虽然技术上可以在一台服务器上运行多个 Broker,但这是一种不被推荐的做法。

Topic 只能存在于多 Broker 组合中的其中一个是吗?

对于这个问题,答案恰恰相反。

  • 准确地说:一个 Topic 的数据,不仅可以,而且通常就应该分布在集群中的多个 Broker 上。

这是通过 Topic 的 分区(Partition)机制 来实现的,这也是 Kafka 得以实现高吞吐量的关键。

我们用一个具体的例子来说明:
假设你有一个由 3 个 Broker 组成的 Kafka 集群(Broker 1, Broker 2, Broker 3)。

现在,你创建了一个名为 order-topic 的主题,并设定它有 3 个分区(我们称之为 P0, P1, P2)。

Kafka 在创建这个 Topic 时,会自动将这些分区尽可能均匀地分布到集群的所有 Broker 上,以实现负载均衡。一个可能的结果是:

  • order-topic 的分区 P0 被存放在 Broker 1 上。

  • order-topic 的分区 P1 被存放在 Broker 2 上。

  • order-topic 的分区 P2 被存放在 Broker 3 上。

这样做的好处是:
当生产者往 order-topic 发送大量消息时,这些消息可以被同时发往三个不同的分区(P0, P1, P2),也就是同时写入了三台不同的服务器(Broker 1, 2, 3)。消费者也可以同时从这三个分区读取数据。这样一来,读写的压力就被分摊到了整个集群,而不是集中在一台机器上,从而极大地提升了性能。

所以,Topic 是一个逻辑概念,它的物理存储(即它的所有分区)是分散在整个集群的多个 Broker 上的。

分区 (Partition) - Kafka 高性能的基石

1. 技术定义

  • 分区 (Partition): 每个 Topic 都可以被划分为一个或多个 Partition。从物理上讲,一个 Partition 就是一个有序的、只能追加写入的日志文件 (Append-only Log)。当生产者发布消息到某个 Topic 时,消息实际上是被追加到该 Topic 的某个 Partition 的末尾。

2. 分区的核心特性

  • 有序性 (Ordering):单个 Partition 内部,消息是严格按照其被写入的顺序进行存储和读取的。这意味着,如果你发送消息 M1,然后发送 M2 到同一个 Partition,那么消费者也一定会先读到 M1,再读到 M2。这是 Kafka 提供的最重要的顺序保证。

  • 不可变性 (Immutability): 一旦消息被写入 Partition,它就不能被修改。这种设计简化了系统,并提高了性能。

  • 偏移量 (Offset): Partition 中的每一条消息都有一个唯一的、从 0 开始单调递增的序列号,这个序列号被称为 Offset。Offset 唯一地标识了 Partition 中的一条消息。消费者通过 Offset 来追踪自己消费到了哪个位置。

3. 为什么要设计分区?

设计分区的核心目的有两个:

  • 可伸缩性/水平扩展 (Scalability):

    • 存储层面: 一个 Topic 的数据可以被分散存储在集群的多个 Broker 上(如我们上一步所讨论的)。这打破了单台服务器的磁盘容量和 I/O 性能的限制。如果数据量增长,你可以通过增加 Broker 节点来水平扩展整个集群的存储能力。
    • 性能层面: 读写操作可以并行地在多个 Broker 上的多个 Partition 上进行,极大地提高了整个 Topic 的总吞吐量。
  • 并行处理 (Parallelism):

    • 分区的存在,使得消费者可以实现并行消费。一个消费者组(Consumer Group)可以有多个消费者实例,每个实例负责消费一个或多个 Partition。这样,一个 Topic 的总消费负载就被分摊到了多个消费者上,大大提高了数据处理能力。

总结一下第三步的关键点:

Topic 是一个逻辑概念,而 Partition 是物理上的实现。一个 Topic 由多个 Partition 组成,这些 Partition 分布在不同的 Broker 上。消息在单个 Partition 内是有序的,并通过 Offset 进行唯一标识。分区的设计是 Kafka 实现高吞吐量和高可伸缩性的核心原因。

副本 (Replika) 与 Leader/Follower 模型 - Kafka 可靠性的保障

1. 技术定义

  • 副本 (Replica): Kafka 允许为每个分区(Partition)创建多个数据副本,这些副本被分布在集群中不同的 Broker 上。这就是副本机制。副本的数量是可以配置的,这个数量被称为副本因子 (Replication Factor)。例如,如果副本因子为 3,那么每个分区就会有 1 个主副本和 2 个次副本。

2. Leader 与 Follower 的角色分工

一个分区的多个副本之间,并不是平等的,它们有明确的主从关系。

  • Leader (领导者 / 主副本):

    • 职责: 每个分区在任意时刻,有且仅有一个副本是 Leader。它负责处理所有来自客户端(生产者和消费者)的读和写请求。
    • 类比: 它是唯一对外营业的“正本”柜台。
  • Follower (跟随者 / 次副本):

    • 职责: Leader 以外的其他副本都是 Follower。Follower 不与客户端直接交互。它们唯一的任务就是被动地、持续地从 Leader 那里拉取最新的数据,以保持和 Leader 的数据同步。
    • 类比: 它们是内部使用的“备份”柜台,只负责抄写正本柜台的数据,不对外服务。

3. 数据同步与故障转移的流程

这个主从模型是如何工作的?

  • 数据写入流程 (正常情况):
  1. 生产者发送一条消息到某个分区。
  2. 请求被直接发送到该分区的 Leader 所在的 Broker 上。
  3. Leader 将消息写入自己的本地日志。
  4. 各个 Follower 定期向 Leader 发送拉取请求,将 Leader 的新消息复制到自己的本地日志中。
  • 故障转移流程 (Leader 宕机):
    1. 分区的 Leader 所在的 Broker 突然宕机。
    2. Kafka 控制器 (Controller) 检测到这个情况。
    3. 控制器会从所有与 Leader 保持“同步”的 Follower 列表中,选举出一个新的 Leader。
    4. 选举成功后,这个曾经的 Follower 就成为了新的 Leader,开始对外提供读写服务。客户端会被告知新的 Leader 地址。整个过程对用户来说是自动完成的。

现在,我们用一个非常具体、直观的例子来说明这个过程。

场景设定:

  • 集群: 我们有一个由 3 台服务器组成的 Kafka 集群,分别是 Broker 1Broker 2Broker 3

  • Topic: 我们创建一个名为 payment_topic 的主题,用来处理支付消息。

  • 分区和副本: 为了让例子简单清晰,我们假设 payment_topic 只有 1 个分区,就是 Partition 0。我们设置副本因子为 3,这意味着 Partition 0 会有 1 个 Leader 和 2 个 Follower。

阶段一:正常运行

系统启动后,Kafka 会自动进行选举和分配。一个可能的结果是:

  • Partition 0Leader 被分配在了 Broker 1 上。

  • Partition 0 的两个 Follower 分别被分配在了 Broker 2Broker 3 上。

现在,一个生产者要发送一条支付消息:**{ "paymentId": "abcde", "amount": 50 }**

  1. 生产者的请求被直接发送到 Broker 1(因为它是 Leader)。

  2. Broker 1 收到消息,将它写入自己的磁盘。

  3. Broker 2Broker 3 上的 Follower,像往常一样,从 Broker 1 拉取数据,发现有一条新消息,于是也把这条消息写入各自的磁盘。

  4. 当数据成功同步到足够数量的副本后(这个数量可以配置),Broker 1 向生产者返回一个“发送成功”的确认。

在这个阶段,所有读写都由 Broker 1 处理,Broker 2 和 3 只是默默地在后台备份。

阶段二:故障发生

突然,Broker 1 所在的服务器因为电源故障,宕机了!

现在的情况是:

  • Partition 0 的 Leader 消失了。

  • 生产者和消费者无法再连接到 Broker 1。

阶段三:自动故障转移 (Failover)

Kafka 的高可用机制现在开始启动:

  1. 集群的控制器 (Controller)(通常是集群中的某一个 Broker)通过心跳机制,很快就发现 Broker 1 失联了。

  2. 控制器立刻查看 Partition 0 的 Follower 列表,发现 Broker 2Broker 3 上的副本都处于“同步”状态。

  3. 控制器在这两个 Follower 中发起一次新的选举。假设选举结果是 Broker 2 胜出。

  4. 控制器发布命令:“从现在起,Broker 2 成为 **Partition 0** 的新 Leader!”

  5. Broker 2 的角色从 Follower 转变为 Leader。Broker 3 保持 Follower 不变,但它现在开始从新的 Leader(Broker 2)那里同步数据。

  6. Kafka 会通知生产者客户端:“payment_topicPartition 0 的 Leader 地址已经变更为 Broker 2。”

结果:

  • 现在,当生产者要发送下一条消息 { "paymentId": "fghij", "amount": 80 } 时,它会直接将请求发送到 Broker 2

文章作者: Sean
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 Sean !
  目录