Kafka面试题

46 阅读11分钟

1.构成

Kafka 将消息以 topic 为单位进行归纳,发布消息的程序称为 Producer,消费消息的程序称为 Consumer。它是以集群的方式运行,可以由一个或多个服务组成,每个服务叫做一个 Broker,Producer 通过网络将消息发送到 kafka 集群,集群向消费者提供消息,broker 在中间起到一个代理保存消息的中转站。

kafka架构图.png Kafka 中重要的组件

1)Producer:消息生产者,发布消息到Kafka集群的终端或服务

2)Broker:一个 Kafka 节点就是一个 Broker,多个Broker可组成一个Kafka 集群。

如果某个 Topic 下有 n 个Partition 且集群有 n 个Broker,那么每个 Broker会存储该 Topic 下的一个 Partition

如果某个 Topic 下有 n 个Partition 且集群中有 m+n 个Broker,那么只有 n 个Broker会存储该Topic下的一个 Partition

如果某个 Topic 下有 n 个Partition 且集群中的Broker数量小于 n,那么一个 Broker 会存储该 Topic 下的一个或多个 Partition,这种情况尽量避免,会导致集群数据不均衡

3)Topic:消息主题,每条发布到Kafka集群的消息都会归集于此,Kafka是面向Topic 的

4)Partition:Partition 是Topic在物理上的分区,一个Topic可以分为多个Partition,每个Partition是一个有序的不可变的记录序列。单一主题中的分区有序,但无法保证主题中所有分区的消息有序。

5)Consumer:从Kafka集群中消费消息的终端或服务

6)Consumer Group:每个Consumer都属于一个Consumer Group,每条消息只能被Consumer Group中的一个Consumer消费,但可以被多个Consumer Group消费。

7)Replica:Partition 的副本,用来保障Partition的高可用性。

8)Controller: Kafka 集群中的其中一个服务器,用来进行Leader election以及各种 Failover 操作。

9)Zookeeper/Kraft:Kafka 通过Zookeeper/Kraft来存储集群中的 meta 消息

2.Kafka消息丢失的原因

消息发送时

消息发送有两种方式:同步 - sync 和 异步 - async。默认是同步的方式,可以通过 producer.type 属性进行配置,kafka 也可以通过配置 acks 属性来确认消息的生产

0:表示不进行消息接收是否成功的确认

1:表示当 leader 接收成功时的确认

-1:表示 leader 和 follower 都接收成功的确认

当 acks = 0 时,不和 Kafka 进行消息接收确认,可能会因为网络异常,缓冲区满的问题,导致消息丢失

当 acks = 1 时,只有 leader 同步成功而 follower 尚未完成同步,如果 leader 挂了,就会造成数据丢失

消息消费时

Kafka 有两个消息消费的 consumer 接口,分别是 low-level 和 hign-level

low-level:消费者自己维护 offset 等值,可以实现对 kafka 的完全控制

high-level:封装了对 partition 和 offset,使用简单

如果使用高级接口,可能存在一个消费者提取了一个消息后便提交了 offset,那么还没来得及消费就已经挂了,下次消费时的数据就是 offset + 1 的位置,那么原先 offset 的数据就丢失了。

2.如何确保消息不丢失

1. 生产者(Producer)配置

生产者是消息的源头,需通过以下配置确保消息可靠传输:

  • acks 参数

    • acks=1(默认):Leader 节点确认收到消息后返回成功。若 Leader 崩溃且未同步到 Follower,可能导致消息丢失。
    • acks=all(或 -1 :要求所有副本(ISR 列表中的节点)确认收到消息后才返回成功。这是最安全的配置,但会降低吞吐量。
    • acks=0:不等待确认,消息可能丢失(适用于对可靠性要求低的场景)。
  • 重试机制

    • 启用 retries 参数(如 retries=3),生产者会在临时错误(如网络抖动)时自动重试。
    • 结合 retry.backoff.ms 控制重试间隔。
  • 幂等性生产者

    • 设置 enable.idempotence=true,确保重试时不会生成重复消息(Kafka 内部通过序列号去重)。
  • 事务支持(Kafka 0.11+):

    • 启用事务(transactional.id),确保一组消息要么全部成功,要么全部失败(适用于跨分区或跨主题的原子操作)。

2. Broker(服务器)配置

Broker 负责存储消息,需通过以下配置保证持久性:

  • 副本机制

    • 每个分区(Partition)有多个副本(Replication Factor ≥ 2),默认存储在不同 Broker 上。
    • 只有 ISR(In-Sync Replicas)列表中的副本能参与选举为 Leader,确保数据一致性。
  • min.insync.replicas

    • 设置 min.insync.replicas=2(假设副本数为 3),要求至少 2 个副本确认写入后才算成功。若未满足,生产者会抛出异常。
  • 持久化存储

    • 确保 log.retention.hourslog.retention.bytes 等参数合理,避免消息因过期被删除。
    • 禁用 unclean.leader.election(默认 false),防止非 ISR 副本成为 Leader 导致数据丢失。
  • 磁盘 I/O 优化

    • 使用高性能磁盘(如 SSD),并配置 num.io.threads 和 num.network.threads 优化吞吐量。

3. 消费者(Consumer)配置

消费者需正确处理消息偏移量(Offset),避免因故障导致重复或丢失:

  • 手动提交 Offset

    • 禁用自动提交(enable.auto.commit=false),改为手动调用 consumer.commitSync() 或 consumer.commitAsync() 确保消息处理完成后再提交。
  • 幂等消费

    • 消费者端实现幂等逻辑(如基于业务 ID 去重),防止因重复消费导致业务错误。
  • 偏移量回溯

    • 若消费者崩溃,可通过 auto.offset.reset=earliest 从最早消息重新消费(需结合业务容忍度)。

4. 监控与运维

  • 监控指标

    • 跟踪 UnderReplicatedPartitions(未完全同步的分区)、RequestLatency(请求延迟)等指标。
    • 使用工具如 Prometheus + Grafana 或 Confluent Control Center。
  • 故障恢复

    • 定期检查 Broker 健康状态,确保副本同步正常。
    • 备份重要主题的元数据(如 __consumer_offsets)。

3.如何确保消息的有序性

Kafka 确保消息有序性的机制是基于其核心设计原则:在单个分区(Partition)内,消息是严格有序的。 但是,Kafka 不保证 跨越不同分区或不同 Topic 的消息之间的顺序。

要确保业务逻辑上的消息有序性,关键在于如何利用 Kafka 的分区特性。

1. 核心保障:分区内有序性

  • 生产者写入顺序: 消息一旦被生产者写入到某个特定的分区,它们在该分区内的顺序是固定的,并且按照写入顺序编号(Offset)。

  • 消费者消费顺序: 消费者在消费这个分区时,会严格按照这些 Offset 的顺序来读取消息。

总结: Kafka 的有序性保证是 “Partial Ordering”(部分有序性) ,即一个分区内的有序性。

2. 实现全局有序性或业务相关有序性的策略

如果您需要确保某个业务实体(例如一个订单、一个用户)的所有相关操作是按顺序处理的,您需要将这些操作的消息都发送到同一个分区

策略一:使用 Key 进行分区(推荐)

这是实现有序性的最常用且最有效的方法。

原理:

生产者发送消息时,为消息设置一个 Key(例如:orderIduserId)。Kafka 默认使用一个 Hash 函数 对这个 Key 进行计算,然后将消息路由到对应的分区。

示例:

Producer.send(Key=“order_123”, Value=“创建订单”) \rightarrow Partition A Producer.send(Key=“order_123”, Value=“支付订单”) \rightarrow Partition A Producer.send(Key=“order_123”, Value=“发货订单”) \rightarrow Partition A

效果: 任何与 order_123 相关的操作消息,都将落入同一个分区,并由该分区上的单个消费者实例按顺序消费。

配置: 确保生产者发送消息时设置了 Key。

策略二:手动指定分区

如果您对消息的分区有精确的控制要求,可以手动指定分区。

原理: 生产者在发送消息时,显式地指定 TopicPartition ID

优点: 绝对的控制权。

缺点: 缺乏灵活性,如果分区数量发生变化或负载不均衡,需要手动调整发送逻辑。

策略三:使用单个分区(慎用)

如果您的 Topic 只有一个分区 (partitions=1),那么所有的消息都会被发送到这个分区,从而实现 全局有序性

优点: 简单地实现全局有序。

缺点:

  • 吞吐量瓶颈: 所有的生产者写入和消费者读取都集中在一个分区上,失去了 Kafka 的并行处理能力。

  • 可用性风险: 如果该分区所在的 Broker 发生故障,整个 Topic 将不可用。

4.如何确保消息不被重复消费

1. 生产者端(Producer):启用幂等性(Idempotence)

从 Kafka 2.0.0 版本开始,生产者可以启用幂等性,这是实现精确一次语义的基础。

  • 原理: 生产者发送的每条消息都会包含一个 Sequence Number(序列号) 。Kafka Broker 会存储这个序列号。如果生产者因为网络问题重试发送同一条消息(即序列号相同),Broker 会识别出这是重复发送,并只存储一次。
  • 作用: 确保消息在从生产者到 Kafka Broker 的过程中不会因为重试而重复写入。
  • 配置: 在生产者配置中设置 enable.idempotence=true

2. Kafka 事务(Transactions)

Kafka 的事务功能允许生产者以原子性的方式向多个分区写入消息,并且消费者可以配置成只读取已提交(Committed)的事务消息。

  • 原理: 生产者在事务中发送消息,并包含一个 Transaction ID(事务 ID) 。消费者可以配置 isolation.level 参数为 read_committed
  • 作用: 确保消费者只看到那些已经成功写入并提交事务的消息,可以防止消费者读到未完成的或回滚的事务中的消息。

3. 消费者端:实现消费幂等性(Consumer Idempotence)

这是解决重复消费问题的最关键最根本的方法,因为它发生在数据处理和存储环节。即使 Kafka 完美地只投递了一次,如果消费者在处理完消息后但在提交 Offset 之前崩溃,重启后也会重新消费这条消息。

  • 关键: 确保消息的处理逻辑(如写入数据库、发送通知等)是幂等的。

实现消费幂等性的常见方法:

  • 唯一键(Primary Key)/业务 ID: - 在消息体中包含一个 全局唯一 的业务 ID(例如:订单 ID、交易流水号)。 - 消费者在处理消息前,先尝试根据这个 ID 写入数据库或进行更新操作。 - 数据库实现: 利用数据库的唯一索引主键约束。如果记录已存在,则写入失败,表示重复消费;如果不存在,则成功写入,完成消费。

  • 版本号/状态机: - 对于更新操作,使用 乐观锁版本号 机制。只接受版本号递增或状态流转正确的更新请求。

  • 外部存储记录(例如 Redis 或数据库): - 在处理消息时,先将消息的唯一 ID(或者 Kafka 的 Topic-Partition-Offset)存入 Redis 或专门的去重表。 - 在处理前,先检查该 ID 是否已存在。如果存在,则跳过(重复消费);如果不存在,则先记录 ID,再处理消息。

5.如何实现分布式事务

基于 Kafka 的最终一致性(Saga 模式)

在涉及 Kafka 和外部系统(如数据库)的分布式场景中,最常用的“事务”解决方案是 Saga 模式,它基于 事件驱动最终一致性

Saga 模式的原理:

一个大的分布式事务被分解为一系列小的、本地的事务(Local Transactions)。每个本地事务完成时,都会发布一个 事件(Event) 到 Kafka,触发下一个参与者执行其本地事务。

步骤示例(电商订单流程): 1.订单服务(Order Service):

接收订单请求

执行本地事务:在订单数据库中创建订单(状态:待支付)。

发布事件到KafkaOrderCreatedEvent(包含订单 ID)。

2.库存服务(Inventory Service):

消费 OrderCreatedEvent

执行本地事务:扣减库存。

发布事件到 KafkaInventoryReservedEventInventoryFailedEvent

3.支付服务(Payment Service):

消费 InventoryReservedEvent

执行本地事务:进行支付操作。

发布事件到Kafka:PaymentSuccessEvent

4.最终一致性:

订单服务消费 PaymentSuccessEvent,最终更新订单状态为“已支付”。

如何处理失败(补偿):

如果流程中的任何一步失败,它会发布一个 补偿事件(Compensation Event)

  • 例如,如果库存服务失败(库存不足),它会发布 InventoryFailedEvent
  • 其他服务(如订单服务)消费这个失败事件,执行 补偿性事务(如:将订单状态改为“已取消”,释放锁定资源)。