202535| Kafka架构与重要概念+幂等性+事务
好的!以下是关于 Kafka 架构 以及其 重要概念 的详细介绍,结合 Mermaid 图形 和 表格,帮助你更好地理解各个概念的关系和作用。
Kafka 架构与重要概念
Kafka 是一个分布式消息系统,广泛应用于日志收集、流处理、事件驱动架构等场景。它采用高吞吐量、可扩展的架构,支持多个组件进行数据的发布、订阅、存储和消费。
一、Kafka 架构图(Mermaid 格式)
二、Kafka 重要概念详细解释
概念 | 说明 |
---|---|
Broker | Kafka 服务器节点,负责存储和管理消息。每个 Broker 处理多个 Topic 和 Partition 的数据读写。 |
ZooKeeper | 分布式协调服务,Kafka 用它来管理集群的元数据,协调 Broker 状态,选举 Leader 等。 |
Producer | 消息的生产者,将数据发布到指定的 Topic 中。 |
Consumer | 消息的消费者,从 Kafka 中获取数据并进行处理。 |
Consumer Group | 由多个 Consumer 组成,Kafka 会确保每个分区在 Consumer Group 中只被一个消费者消费。 |
Topic | Kafka 中的主题,用于组织和分类消息。 |
Partition | 每个 Topic 被划分为多个分区,分区是 Kafka 存储消息的基本单位。 |
Replica | 分区的副本,确保数据的高可用性。每个分区可以有多个副本,其中一个为 Leader。 |
Offset | Kafka 中每条消息在分区内的唯一标识,消费者通过 Offset 来追踪自己消费的进度。 |
三、Kafka 架构中的各个组件
1. Broker(Kafka 代理)
- 定义:Kafka 中的 Broker 是负责存储消息并处理客户端请求的节点。每个 Broker 负责多个 Topic 和 Partition 的数据读写。
- 角色:Kafka 集群由多个 Broker 组成,集群中的 Broker 节点共同工作,确保数据的存储、消费和高可用性。
- 作用:Broker 接受来自 Producer 的消息,将其存储在本地磁盘中,并提供 Consumer 按需读取。
2. ZooKeeper(协调服务)
- 定义:ZooKeeper 是一个分布式协调服务,用于管理 Kafka 集群的元数据和协调 Broker 的状态。Kafka 使用 ZooKeeper 来进行 Broker 的注册、Leader 选举等管理操作。
- 作用:它帮助 Kafka 保持一致性、选举 Leader 和管理集群状态。ZooKeeper 集群中可以有多个节点,提供高可用性。
3. Producer(生产者)
- 定义:Producer 是向 Kafka 集群发送消息的客户端。它将消息发送到指定的 Topic 中。
- 作用:Producer 可以选择发送到特定的 Partition,也可以选择由 Kafka 自动分配 Partition。
- 分区选择:Producer 可以通过指定一个 key 来确保某一类消息始终发送到同一个分区,或者由 Kafka 根据负载均衡策略自动选择。
4. Consumer(消费者)
- 定义:Consumer 是从 Kafka 中读取消息并处理的客户端。它可以订阅一个或多个 Topic,读取分区中的消息。
- 作用:Consumer 从 Kafka 中消费消息,通常会从消息的 Offset 开始读取。消费者通常会在自己的 Offset 位置继续消费。
5. Consumer Group(消费组)
- 定义:Consumer Group 是由多个 Consumer 组成的一组消费者。每个 Consumer Group 内的消费者共同消费同一个 Topic 的消息,但每个分区的消息只有一个消费者消费。
- 作用:通过消费组,Kafka 可以实现消息的并行消费。每个消费组都会维护自己的 Offset 独立于其他消费组。
6. Topic(主题)
- 定义:Topic 是 Kafka 中的消息分类标签。Producer 将消息发送到特定的 Topic,Consumer 可以订阅一个或多个 Topic 来消费消息。
- 作用:Topic 是 Kafka 消息的逻辑分组,可以帮助管理不同种类的消息流。
7. Partition(分区)
- 定义:每个 Topic 会被划分成多个 Partition,Partition 是 Kafka 存储消息的最小单位。每个分区存储消息的顺序,并且每个 Partition 都有一个 Leader 和若干个 Follower 副本。
- 作用:分区提供了水平扩展的能力,可以增加分区来处理更高的消息吞吐量和并发消费。
8. Replica(副本)
- 定义:每个分区都有多个副本(Replica),其中一个副本是 Leader,负责处理所有的读写请求。其他副本是 Follower,负责同步数据。
- 作用:副本保证了 Kafka 集群的数据高可用性和容错性。即使某个 Broker 节点失败,其他副本仍然可以保证数据不丢失。
9. Offset(偏移量)
- 定义:Offset 是 Kafka 中每条消息在分区内的唯一标识。每个消费者在消费消息时都会记录自己的 Offset 位置。
- 作用:Offset 使得 Kafka 的消息消费具有高可控性。消费者可以从特定的 Offset 开始消费消息,也可以重新从头消费历史消息。
Kafka生产者幂等性
Kafka 生产者的幂等性功能是为了保证消息的 一次性写入。即使在网络超时、生产者重试等情况下,消息依然能够保证 只被写入一次。为了实现这一目标,Kafka 利用一系列机制来控制消息的重复发送,并对每条消息进行唯一标识。
Kafka 生产者幂等性核心概念
- Producer ID (PID): 每个 Kafka 生产者实例都会在启动时获取一个唯一的 Producer ID。这是 Kafka 用来区分不同生产者的标识符,确保每个生产者发送的消息在 Kafka 集群中都有唯一标识。
- Sequence Number (序列号): 每个生产者发送的消息都有一个递增的序列号,Sequence Number 是 Kafka 用来判定消息是否重复的关键参数。序列号的递增可以确保即使是同一个生产者发送多条消息,Kafka 也能区分它们。
- Message Key & Partition: 消息的 Key 用来决定消息将发送到哪个分区,确保同一个 Key 的消息总是发送到同一个分区。分区内的消息顺序对于幂等性没有影响,但消息的重复性检查依赖于 Producer ID 和 Sequence Number。
生产者幂等性工作流程
为了解释 Kafka 是如何确保消息不被重复写入,我们可以将其工作过程分解为几个步骤:
- 生产者初始化:
- 每个生产者在启动时会向 Kafka 集群申请一个唯一的 Producer ID (PID),并且会从 Kafka 中获得一个初始的 Sequence Number。
- 生产者为每条发送的消息附上递增的序列号。
- 消息发送:
- 生产者向 Kafka 发送消息时,消息携带两个关键字段:Producer ID 和 Sequence Number。这些字段帮助 Kafka 区分不同生产者发送的消息,以及同一生产者发送的不同消息。
- 消息接收与存储:
- Kafka Broker 在接收到消息后,会先检查 Producer ID 和 Sequence Number。如果该消息的 Sequence Number 对应的消息已经存在,说明这是一个 重复消息,则 Kafka 会 忽略 该消息,不进行存储。
- 如果消息是新的,Kafka 会将其存储,并将该消息的 Producer ID 和 Sequence Number 存储在元数据中。
- 消息重试:
- 在网络延迟或其他故障的情况下,生产者可能会重试发送相同的消息。由于生产者会附带相同的 Producer ID 和 Sequence Number,Kafka Broker 会识别到这是同一条消息,并 忽略 重试请求,确保消息只被写入一次。
- 消息确认:
- 在消息成功写入 Kafka 后,Broker 会将写入成功的确认返回给生产者。若发生故障或超时,生产者会继续重试,直到成功或者达到重试次数的上限。
Kafka 生产者幂等性详细流程图
为了让你更直观地理解这一过程,下面是一个更详细的图解,展示了生产者发送消息、重试以及如何避免重复消息写入的流程:
工作原理解读
1. 生产者启动时
当生产者启动时,Kafka 为该生产者分配一个 Producer ID(如 123),并且给该生产者分配一个递增的 Sequence Number(从 0 开始)。每个消息都携带这些信息,确保 Kafka 能识别该生产者的每一条消息。
2. 生产者发送消息(消息 1)
生产者发送 消息 1 到 Kafka 集群,消息的内容包括:Producer ID: 123 和 Sequence Number: 0。Kafka 会根据这个信息将消息存储并传递给消费者。
3. 网络故障与重试
由于网络延迟或其他原因,生产者可能会重试发送相同的消息。此时,生产者再次发送 消息 1(Producer ID: 123, Sequence: 0)。Kafka 在收到这条消息时,会检查消息的 Producer ID 和 Sequence Number,发现这条消息已经存在,因此会 忽略 重复的消息。
4. 发送新的消息(消息 2)
当生产者发送 消息 2 时,Sequence Number 增加为 1,Kafka 会识别为新消息,并将其存储和传递给消费者。
Kafka 幂等性实现的关键因素
- Producer ID (PID):
- 每个生产者在 Kafka 中都有一个唯一的 Producer ID,它用来标识消息的来源。即使网络重试,Kafka 也能根据 Producer ID 来区分不同的生产者。
- Sequence Number:
- 每个生产者发送的消息都有一个递增的序列号。这个序列号使 Kafka 能够区分同一生产者发送的不同消息,确保消息的顺序性。
- 去重机制:
- Kafka 使用 Producer ID 和 Sequence Number 来判断消息是否重复。如果收到相同 Producer ID 和 Sequence Number 的消息,Kafka 会认为这是一条重复消息,进而忽略它。
- 跨分区的一致性:
- Kafka 的幂等性机制是 跨分区的,即使消息被发送到多个分区,只要生产者的 Producer ID 和 Sequence Number 相同,Kafka 就会确保消息不重复写入。
生产者幂等性机制的优缺点
优点
- 保证消息准确性:即使发生网络重试、故障等问题,生产者也能保证消息 只写一次,避免了数据的重复和不一致。
- 提升容错能力:在发生故障或超时的情况下,生产者能够安全重试,并且 Kafka 会确保不会重复写入消息,增强了系统的容错性。
- 避免消费者重复消费:幂等性机制确保了消费者不会收到重复的消息,避免了重复消费和数据错误。
缺点
- 性能开销:启用幂等性会增加一定的性能开销,尤其在高吞吐量的场景下,Kafka 需要进行更多的检查和存储操作来确保消息唯一性。
- 对多生产者的支持有限:幂等性机制仅对单一生产者实例有效,不同生产者间无法共享幂等性状态。
Kafka 生产者幂等性配置示例
以下是一个典型的 Kafka 生产者配置,启用了幂等性功能:
java复制编辑
Properties properties = new Properties();
properties.put("bootstrap.servers", "localhost:9092");
properties.put("key.serializer", "org.apache.kafka.common.serialization.StringSerializer");
properties.put("value.serializer", "org.apache.kafka.common.serialization.StringSerializer");// 启用幂等性
properties.put("acks", "all"); // 等待所有副本确认
properties.put("retries", Integer.MAX_VALUE); // 设置最大重试次数为最大值
properties.put("max.in.flight.requests.per.connection", 1); // 防止并发请求导致乱序
properties.put("enable.idempotence", "true"); // 启用幂等性KafkaProducer<String, String> producer = new KafkaProducer<>(properties);
配置详解:
acks=all
:等待所有副本确认后才返回确认,提供了最佳的消息可靠性。retries=Integer.MAX_VALUE
:允许生产者无限次重试,直到成功。通过幂等性机制,Kafka 确保即使消息重试,也不会重复写入。max.in.flight.requests.per.connection=1
:为了确保消息的顺序性,生产者一次只允许发送一个未确认的消息。enable.idempotence=true
:启用幂等性,确保消息只写一次,即使发生重试,消息也不会重复。
KafKa事务
Kafka 事务简介
Kafka 的事务是一个强大的特性,它允许用户将多个消息的发送操作封装在一个 原子操作 中。通过事务,生产者可以确保多条消息的发送要么全部成功,要么全部失败。Kafka 的事务能够保证数据一致性,特别是在分布式环境下,避免了部分消息写入而其他消息丢失的问题。
Kafka 事务不仅仅用于生产者,也为消费者提供了 精确一次消费(Exactly Once Semantics, EOS)的能力。事务在 Kafka 中的应用解决了消息重复消费和漏消费的难题,提升了消息流的可靠性。
Kafka 事务的核心特点
- 原子性(Atomicity): Kafka 事务保证一组消息的发送要么全部成功,要么全部失败。若消息在事务内的某些操作失败,那么整个事务会被回滚,确保消息的原子性。
- 可靠性(Durability): 一旦事务提交,Kafka 会保证消息持久化,并且可以恢复事务内的消息。
- 一致性(Consistency): Kafka 确保事务内的消息要么全都提交,要么全都回滚,避免了数据不一致的情况。
- 隔离性(Isolation): Kafka 确保事务内的消息在事务提交之前对消费者是不可见的。即消费者只能看到已经提交的消息,未提交的事务消息对消费者是不可见的。
Kafka 事务的实现
Kafka 使用以下两种类型的标记来管理事务:
- 事务开始:生产者发出开始事务的信号,所有后续发送的消息都属于这个事务。
- 事务提交:生产者发出提交事务的信号,Kafka 将事务中的所有消息持久化,确保消息可供消费者读取。
- 事务中止(回滚):如果生产者遇到错误并决定放弃事务,可以发出中止事务的信号,所有事务内的消息都不会被提交。
Kafka 会在事务日志中记录每个事务的状态。事务状态包括提交、回滚或未决(进行中)。
Kafka 事务的关键配置
acks
:- 设置为
all
,确保所有副本都确认消息,以提高消息可靠性和容错性。
- 设置为
transactional.id
:- 这是 Kafka 生产者的一个关键配置,标识一个生产者实例的事务标识符。每个事务性生产者都需要设置这个唯一的标识符,以便 Kafka 能追踪事务状态。
retries
:- 在事务中,重试机制非常重要,设置
retries
可以确保生产者在事务中的消息如果失败,可以重试。
- 在事务中,重试机制非常重要,设置
transaction.timeout.ms
:- 这个配置用于设置事务的超时时间,默认值是 60000 毫秒。若一个事务未在指定时间内提交或回滚,Kafka 会认为事务超时并自动中止事务。
enable.idempotence
:- 在启用事务的同时,还需要启用幂等性,这样可以确保即使在网络中断或生产者重试的情况下,也不会重复发送相同的消息。
Kafka 事务的生产者使用流程
- 开启事务: 在生产者代码中,需要使用
beginTransaction()
方法显式地开始一个事务。 - 发送消息: 在事务中发送消息时,所有发送的消息都会被标记为事务的一部分,直到事务被提交或回滚。
- 提交事务: 通过
commitTransaction()
方法提交事务,Kafka 会将事务中的所有消息持久化。 - 回滚事务: 如果在事务过程中发生了错误,生产者可以使用
abortTransaction()
方法回滚事务,所有未提交的消息都不会被持久化。
事务的生产者代码示例
以下是一个使用 Kafka 事务的 Java 代码示例,演示如何开启事务、发送消息并提交事务。
Properties props = new Properties();
props.put("bootstrap.servers", "localhost:9092");
props.put("key.serializer", "org.apache.kafka.common.serialization.StringSerializer");
props.put("value.serializer", "org.apache.kafka.common.serialization.StringSerializer");
props.put("acks", "all");
props.put("retries", Integer.MAX_VALUE);
props.put("transactional.id", "my-transaction-id");// 创建 KafkaProducer 实例
KafkaProducer<String, String> producer = new KafkaProducer<>(props);try {// 开始事务producer.beginTransaction();// 发送消息producer.send(new ProducerRecord<>("my-topic", "key1", "value1"));producer.send(new ProducerRecord<>("my-topic", "key2", "value2"));// 提交事务producer.commitTransaction();
} catch (ProducerFencedException | OutOfMemoryError | KafkaException e) {// 在发生异常时回滚事务producer.abortTransaction();
} finally {producer.close();
}
代码解释:
- 初始化生产者:配置
bootstrap.servers
、acks
等基本参数,并设置transactional.id
来启用事务。 - 开始事务:通过
beginTransaction()
方法启动一个事务。 - 发送消息:消息会被添加到当前事务中,直到事务提交或回滚。
- 提交事务:
commitTransaction()
方法将事务中的所有消息持久化到 Kafka 集群中。 - 异常处理和回滚:如果发生异常(如网络中断、写入失败等),调用
abortTransaction()
方法回滚事务,确保事务内的消息不被写入。
Kafka 事务的消费者
Kafka 的事务不仅对生产者有效,还影响消费者的行为。启用事务后,消费者只能看到已经提交的消息,这意味着未提交的事务对消费者是不可见的。这保证了 精确一次消费(Exactly Once Semantics,EOS),确保消费者不会消费到重复或不完整的数据。
消费者的事务性读取:
- 如果消费者消费到一个尚未提交的消息,它会等待直到该消息所在的事务提交。
- 如果事务被回滚,消费者不会读取到该事务中的消息。
启用精确一次消费:
为了启用精确一次的消费,消费者需要设置 isolation.level
配置项,通常设置为 read_committed
,这意味着消费者只会读取已提交的消息。
Properties consumerProps = new Properties();
consumerProps.put("bootstrap.servers", "localhost:9092");
consumerProps.put("group.id", "my-consumer-group");
consumerProps.put("key.deserializer", "org.apache.kafka.common.serialization.StringDeserializer");
consumerProps.put("value.deserializer", "org.apache.kafka.common.serialization.StringDeserializer");
consumerProps.put("isolation.level", "read_committed");KafkaConsumer<String, String> consumer = new KafkaConsumer<>(consumerProps);
consumer.subscribe(Collections.singletonList("my-topic"));while (true) {ConsumerRecords<String, String> records = consumer.poll(Duration.ofMillis(1000));for (ConsumerRecord<String, String> record : records) {System.out.println("Consumed: " + record.value());}
}
总结:Kafka 事务的优势
- 消息的原子性:保证一组消息的发送操作要么完全成功,要么完全失败。避免了部分消息写入成功而其他消息丢失的情况。
- 精确一次语义(EOS):通过生产者事务和消费者的事务性读取,Kafka 提供了精确一次的消息传递语义,确保数据的一致性。
- 高可靠性:在分布式环境下,事务能够确保即使在故障和重试的情况下,数据不会重复或者丢失,保证了消息传递的可靠性。
- 简化消费者逻辑:启用事务后,消费者无需额外的逻辑去处理重复数据,Kafka 会自动保证消费者只处理已提交的消息。
通过 Kafka 事务,用户可以构建更加稳定和一致的数据流系统,尤其适用于金融、订单等要求高数据一致性的场景。