1.构成
Kafka 将消息以 topic 为单位进行归纳,发布消息的程序称为 Producer,消费消息的程序称为 Consumer。它是以集群的方式运行,可以由一个或多个服务组成,每个服务叫做一个 Broker,Producer 通过网络将消息发送到 kafka 集群,集群向消费者提供消息,broker 在中间起到一个代理保存消息的中转站。
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.hours、log.retention.bytes等参数合理,避免消息因过期被删除。 - 禁用
unclean.leader.election(默认false),防止非 ISR 副本成为 Leader 导致数据丢失。
- 确保
-
磁盘 I/O 优化:
- 使用高性能磁盘(如 SSD),并配置
num.io.threads和num.network.threads优化吞吐量。
- 使用高性能磁盘(如 SSD),并配置
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(例如:
orderId、userId)。Kafka 默认使用一个 Hash 函数 对这个 Key 进行计算,然后将消息路由到对应的分区。示例:
Producer.send(Key=“order_123”, Value=“创建订单”)Partition AProducer.send(Key=“order_123”, Value=“支付订单”)Partition AProducer.send(Key=“order_123”, Value=“发货订单”)Partition A效果: 任何与
order_123相关的操作消息,都将落入同一个分区,并由该分区上的单个消费者实例按顺序消费。配置: 确保生产者发送消息时设置了 Key。
策略二:手动指定分区
如果您对消息的分区有精确的控制要求,可以手动指定分区。
原理: 生产者在发送消息时,显式地指定
Topic和Partition 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):
接收订单请求。
执行本地事务:在订单数据库中创建订单(状态:待支付)。
发布事件到Kafka:OrderCreatedEvent(包含订单 ID)。
2.库存服务(Inventory Service):
消费
OrderCreatedEvent。
执行本地事务:扣减库存。
发布事件到 Kafka:InventoryReservedEvent或InventoryFailedEvent。
3.支付服务(Payment Service):
消费
InventoryReservedEvent。执行本地事务:进行支付操作。
发布事件到Kafka:
PaymentSuccessEvent。
4.最终一致性:
订单服务消费
PaymentSuccessEvent,最终更新订单状态为“已支付”。
如何处理失败(补偿):
如果流程中的任何一步失败,它会发布一个 补偿事件(Compensation Event) 。
- 例如,如果库存服务失败(库存不足),它会发布
InventoryFailedEvent。 - 其他服务(如订单服务)消费这个失败事件,执行
补偿性事务(如:将订单状态改为“已取消”,释放锁定资源)。