结构
Kafka 是一个分布式流处理平台,主要用于构建实时数据管道和流应用。其结构可以分为以下几个主要部分:
- Producer:生产者负责将数据发布到 Kafka 中的主题(Topic)。消息会按照某种逻辑(如轮询、键值哈希等)分发到该主题的不同分区,生产者可以发送单个消息或者批量消息到指定的主题。
- Consumer:消费者从主题中的特定分区读取消息。消费者可以是单个实例,也可以组成一个消费者组(Consumer Group)。Kafka 支持多消费者模型,每个消费者组能够独立读取主题中的数据。Kafka 通过消费者组(Consumer Group)管理消费进度,确保消息被所有需要的消费者组消费。
- Broker:Kafka 集群中的每个节点称为一个 Broker。Broker 负责接收、存储和提供来自生产者的消息。一个 Kafka 集群通常由多个 Broker 组成,每个主题的分区会分布在不同的 Broker 上,分布式存储消息,以实现高可用性和容错性。
- Topic:数据在 Kafka 中是通过主题组织的。生产者将数据发送到主题中,消费者从主题中读取数据。主题可以分为多个分区(Partition),分区使得主题能够水平扩展,以提高吞吐量和并行处理能力。
- Partition:每个主题都可以被分为多个分区,分区是 Kafka 中的基本并行单元。每个分区中的消息是有序的,但是跨分区的消息没有全局顺序。通过分区,Kafka 能够在集群中分布负载,并实现高并发的数据处理。
- Zookeeper:Kafka 使用 Zookeeper 来管理集群的元数据、Broker 状态、主题配置等信息。在新的 Kafka 版本中,Zookeeper 被逐渐替代为 Kafka 自带的集群协调功能,但在旧的版本中,Zookeeper 是必需的。
- Leader 和 Follower:每个分区都有一个 Leader 和若干 Follower。Leader 负责处理生产者和消费者的读写请求,而 Follower 只负责同步 Leader 中的数据。当 Leader 失效时,Kafka 通过 Zookeeper 或自带的协调功能选举新的 Leader。
消息可靠性
Kafka 通过多种机制来保证消息可靠性,确保消息在生产、传输、消费的各个环节中不丢失、不重复,并且达到高可用性。
消息确认机制(ACK)
- 当生产者发送消息时,Kafka 会通过 ACK 来确认消息是否成功写入。生产者可以配置不同的 ACK 级别来决定消息确认的方式:
- acks=0:生产者不等待任何确认。消息一旦发送就认为已成功,不论 Kafka 是否接收。这种方式最快,但可靠性最低。
- acks=1:生产者等待 Leader 确认消息已写入。Leader 成功写入后立即响应生产者,但如果 Leader 在 Follower 完成同步前崩溃,消息可能丢失。
- acks=all:生产者等待所有副本(Leader 和所有 ISR 中的 Follower)确认消息写入成功。只有当所有副本都写入成功后,生产者才会收到确认,这是最高级别的可靠性保障。
ISR 机制(In-Sync Replicas,同步副本)
- Kafka 的每个分区都有多个副本,通过数据复制机制来保证消息的持久性和容错性,包括一个 Leader 副本 和多个 Follower 副本。使用 ISR(In-Sync Replicas)机制来追踪与 Leader 保持同步的 Follower 副本。如果某个分区的 Leader 副本失效,Kafka 会通过 Zookeeper 或 KRaft 机制进行 Leader 选举,从该分区的 ISR 中选择一个新的 Leader 副本来继续处理读写请求。选举过程通常非常快速,保证了 Kafka 的高可用性。
持久化存储与顺序写入
- Kafka 将消息以顺序写入的方式持久化到磁盘中,并且通过文件系统缓存来提升写入性能。这种顺序写入相比随机写入更高效,减少了磁盘的 I/O 操作,并且即使系统崩溃,已写入磁盘的消息也不会丢失。
- Kafka 在磁盘上以日志文件的形式存储数据,并为每个分区的消息维护一个 offset(偏移量)。消费者通过 offset 来追踪自己消费的进度,确保能够从正确的位置继续消费。
幂等性生产者(Idempotent Producer)
- Kafka 支持幂等性生产者,即生产者可以确保每条消息在同一分区中只写入一次,即使因为网络故障或重试机制导致同一消息被多次发送,Kafka 也能确保该消息只被写入一次,避免消息重复。
- 开启幂等性后,Kafka 给每个生产者分配一个唯一的 PID(Producer ID)并为每个消息分配序列号,通过这种机制 Kafka 能够检测到重复的消息写入,并丢弃重复的消息。
事务性生产者(Transactional Producer)
- Kafka 支持事务性生产者,允许生产者在多个分区上实现全局的事务操作,确保一组消息要么全部成功写入,要么全部失败。这对于保证跨分区、跨主题的操作一致性非常重要。
- 事务性生产者通过 beginTransaction、commitTransaction、abortTransaction 等 API 来管理事务操作,这种机制能够避免部分消息被写入的情况,从而确保数据的一致性和完整性。
消费者的 Offset 管理
- 消费者 offset 是消费者在分区中消费的进度标记。Kafka 提供两种方式来管理 offset:
- 自动提交(auto commit):Kafka 会在特定时间间隔(通过 auto.commit.interval.ms 配置)自动提交消费者的消费进度。如果系统崩溃,可能会导致消费的部分消息未被提交,导致消息重复消费。
- 手动提交:消费者可以手动管理 offset,只有在确认处理完当前消息后,才会提交消费进度。这种方式允许消费者在系统崩溃后,从上一次成功消费的位置继续消费,保证消息不丢失。
日志保留策略(Log Retention Policy)
- Kafka 提供灵活的日志保留策略来管理消息的存储时间和空间:
- 基于时间的保留:可以配置 Kafka 保留消息的时间长度,例如 log.retention.hours 指定 Kafka 将消息保留一定时间后自动删除。
- 基于大小的保留:可以设置日志文件的大小上限,达到上限后 Kafka 会自动删除旧的日志文件。
- 基于 Log Compaction(日志压缩):Kafka 支持日志压缩,Kafka 会保留每个消息键的最新版本,从而减少存储空间的占用,同时保留历史数据的最新状态。
流处理的可靠性(Exactly Once 语义)
- Kafka 通过幂等性生产者和事务性生产者的结合,提供了 Exactly Once 语义(EOS),确保消息在生产、传输和消费时不会出现丢失或重复的情况。
- 这种语义尤其重要于流式处理场景,比如使用 Kafka Streams 框架时,可以确保在流处理应用中,消息处理的结果是精确一致的。
Kafka 通过 ACK 机制、ISR 副本同步、持久化存储、Leader 选举、幂等性、事务性支持以及消费者的 offset 管理等多个机制共同确保了消息在生产、传输、存储和消费各个环节的可靠性。这些设计让 Kafka 能够在分布式环境中提供高可用、高可靠的消息传输服务。
消息有序性
Kafka 在主题(Topic)层面上不能保证全局消息有序,但它能在分区(Partition) 内保证消息的严格顺序。具体来说,Kafka 保证消息在同一个分区内按照生产的顺序被写入,并且消费者也会按照相同的顺序消费这些消息。在 Kafka 中,消息的顺序性是以同一个分区和同一个消费者组为基础的。
在 Kafka 中,如果有多个生产者同时向同一个分区发送消息,那么消息的有序性将无法得到保证。这种情况下可以考虑引入第三方进行判断控制,比如:在消息中加上时间戳字段,不过这样的话会导致性能严重下降,得不偿失。
分区级别的消息顺序
分区内顺序保证:Kafka 确保同一分区内的消息是按生产者的写入顺序存储的。无论生产者发送多少消息到这个分区,消息在写入时都会严格按照发送的顺序被追加到该分区的日志文件中。因此,消费者读取消息时,会按照消息的顺序依次消费。
多分区的无序性:如果一个主题有多个分区,不同分区之间的消息顺序并不保证。因为 Kafka 在不同的分区上并行处理数据,跨分区的消息没有全局顺序。因此,只有分区内是有序的。
使用消息键
生产者在发送消息时可以指定消息键。Kafka 使用该键来决定消息要被发送到哪个分区。
使用相同的键可以保证所有相关的消息都会路由到同一个分区。这意味着如果生产者对某一类型的消息(例如同一用户的操作或同一个订单的事件)使用相同的键,则这些消息会被写入到同一个分区,并保证它们的顺序性。
通过这种方式,Kafka 可以在某个特定粒度(如按用户、订单等)上保证消息的有序性。
单分区的应用场景
对于必须严格保证消息全局有序的场景,通常使用单个分区的主题。单分区保证所有消息都顺序写入并顺序读取。
但使用单分区意味着吞吐量受到限制,因为 Kafka 的并行处理能力依赖于多个分区。如果应用对性能有较高的要求,这种方式的扩展性会受到限制。
生产者的幂等性(Idempotent Producer)
Kafka 支持 幂等性生产者,使得生产者可以确保在分区中按顺序发送消息并且消息不会重复。这对有序性有帮助,因为幂等性生产者会跟踪消息的顺序,并保证即使在重试的情况下,消息依然按顺序写入。
幂等性生产者通过每个分区上的序列号来确保消息的有序性和不重复。当开启幂等性时,Kafka 确保每