kafka是什么
定义
Apache Kafka 是一个开源的分布式流处理平台,由 LinkedIn 开发并于 2011 年贡献给 Apache 软件基金会,随后成为 Apache 的顶级项目。Kafka 主要用于构建高性能、可扩展的实时数据管道和流式应用程序,广泛应用于消息队列、日志聚合、事件流处理和实时数据分析等场景。
基本术语
- 主题(Topic):主题是 Kafka 中消息的分类,类似于传统消息队列中的队列。生产者将消息发送到特定的主题,消费者从主题中读取消息。
- 分区(Partition):主题被划分为多个分区,每个分区是一个有序的、不可变的消息队列。同一个主题的分区可以分布在不同的 Broker 上。分区是并行处理的基本单位,分区的数量决定了Topic的并行处理能力。分区内的消息按顺序存储,每条消息分配一个唯一 Offset(偏移量)。
- 消息(Message):消息是 Kafka 中的最小数据单元,由键(Key)、值(Value)和时间戳(Timestamp)组成。
- 生产者(Producer):向 Kafka 主题发送消息的应用程序。
- 消费者(Consumer):从 Kafka 主题读取消息的应用程序。消费者可以订阅一个或多个主题,并以顺序方式消费消息。
- 消费者组(Consumer Group):一组消费者可以组成一个消费者组,共同消费一个主题中的消息。
- 偏移量(Offset):消费者在分区中的读取位置,记录了消费者已经消费到的消息的位置。偏移量确保消费者可以按照顺序消费消息,并在消费者重启时继续从上次的位置开始消费。
- LEO(Log End Offset):标识当前日志文件中下一条待写入消息的offset,LEO的大小相当于当前日志分区中最后一条消息的offset值加1。
- HW(High Watermark):俗称高水位,它标识了一个特定的消息偏移量(offset),消费者只能拉取到这个offset之前的消息。分区ISR集合中的每个副本都会维护自身的LEO,而ISR集合中最小的LEO即为分区的HW。
- 代理(Broker):Kafka 集群中的服务器实例,负责存储消息和处理生产者和消费者的请求。
- 日志(Log):Kafka 存储消息的物理结构,每个主题的每个分区对应一个日志文件。与传统意义上的日志有一些区别。
- 日志分段(Log Segment):一个partition当中由多个segment组成,每个segment主要包含一个日志文件和两个索引文件,一个是 .log 文件,另外一个是 .index 文件、.timeindex文件,其中 .log 文件包含了我们发送的数据存储,.index 文件,记录的是我们.log文件的数据索引值,以便于我们加快数据的查询速度。
- 副本(Replica):分区的多个副本机制,用于防止数据丢失和提高系统的可靠性。副本之间是一主多从关系,leader 负责写请求,follower同步消息。leader失败后从follower选举 leader 对外服务。所有副本的集合称为AR(All Replicas);与leader副本保持同步的副本集合称为ISR(In-Sync Replicas);与leader副本同步滞后过多的副本集合称为OSR(Out-of-Sync Replicas);AR=ISR+OSR ,正常情况下 AR=ISR。
- ZooKeeper:Kafka 使用 ZooKeeper 来管理集群元数据和协调集群操作。ZooKeeper 存储 Kafka 集群的元数据,如主题、分区、Broker 信息等,并用于选举 Leader 副本、管理消费者组等。
生产者
核心组件
- 生产者客户端:负责与 Kafka 集群进行通信,将数据发送到指定的主题。
- 生产者拦截器:在消息发送前做一些准备工作。
- 序列化器:将应用程序数据序列化为字节格式,以便 Kafka 能够存储和传输。
- 分区器:确定消息应该发送到主题的哪个分区。
重要生产者参数:ACKS
- acks: 指定分区中必须要有多少个副本接受这条消息,生产者才被认为写成功。 acks 是生产者客户端中一个非常重要 的参数 ,它涉及消息的可靠性和吞吐 量之间的权衡。 acks参数有3种类型的值(都是字符串类型)
- acks="1" (默认)。生产者发送消息之后,只要分区的 leader副本成功写入消息,那么它就会收到来自服务端的成功响应。 折中方案。消息写入 leader 副本并 返回成功响应给生产者,且在被其他 follower 副本拉取之前 leader 副本崩溃,那么此 时消息还是会丢失
- acks="0" 。生产者发送消息之后不需要等待任何服务端的响应。最大吞吐
- acks="-1/all"。生产者在消息发送之后,需要等待 ISR 中的所有副本都成功 写入消息之后才能够收到来自服务端的成功响应。(最高可靠,leader宕机也不丢失,生产者收到异常告知此次发送失败)
- max.request.size: 限制生产者客户端能发送的消息的最大值。默认 1M
- retries, retry.backoff.ms: 生产者重试次数和间隔。在需要保证消息顺序的场合建议把参数 max.in.flight . requests .per.connection 配置为 1
- compression.type: 指定消息压缩方式,默认 "none",还可以配置为 "gzip", "snappy", "lz4"
kafka发送消息的三种模式
- 发后即忘(fire-and-forget): 直接调用 send()。 性能最高,可靠性最差
- 同步(sync): 调用 send 之后调用 get() 等待
- 异步(async): 传入回调 callback。Kafka 在返回响应时调用该函数来实现异步的发送确认,并且回调函数的的调用也保证分区有序。
kafka怎么配置发送消息的三种模式
通过配置acks参数与调用send()方法后,等待结果返回的方式实现。
- Fire-and-forget
acks=0:生产者发送消息后,不需要等待Broker的确认即可继续发送下一条消息。
无需额外配置回调函数或等待发送结果。 - sync
acks=all或acks=1:建议设置为acks=all以确保消息被所有ISR副本确认,提高可靠性。
调用send()方法后,使用Future对象的get()方法等待结果返回。 - async
acks配置可以根据需求选择,如acks=1或acks=all。
调用send()方法时,指定一个回调函数(实现Callback接口)。
分区器
分区器根据分区算法计算分区号决定消息发送到哪个分区。
- 默认的分区器(DefaultPartitioner.java):
如果 key 不为 null,那 么默认的分区器会对 key 进行哈希(采 用 MurmurHash2 算法 ,具备高运算性能及低碰撞率), 最终根据得到 的哈希值来计算分区号, 拥有相同 key 的消息会被写入同一个分区 。 如果 key 为 null,那么消息将会以顺序轮询的方式发往主题内的各个可用分区。 - 自定义分区器
实现Partitioner接口,并显性配置生产者属性参数partitioner.class
工作流程
-
1.配置生产者客户端参数及创建相应的生产者实例
Properties props = new Properties();//属性对象 props.put("bootstrap.servers", "localhost:9092"); //指定 broker 的地址清单 props.put("key.serializer", "org.apache.kafka.common.serialization.StringSerializer"); // 指定key的序列化转换实现类 props.put("value.serializer", "org.apache.kafka.common.serialization.StringSerializer");// 指定value的序列化转换实现类 Producer<String, String> producer = new KafkaProducer<>(props);//创建生成者实例
-
2.构建待发送的消息:
ProducerRecord<String, String> record = new ProducerRecord<>("topic-name", "key", "value");
-
3.消息序列化:使用配置的序列化器将消息的键和值转换为字节格式。
-
4.选择分区:
生产者使用分区器确定消息应该发送到主题的哪个分区。默认情况下,Kafka 使用 DefaultPartitioner,根据消息的键进行哈希计算,选择分区。 -
5.发送消息:调用 send() 方法将消息发送到 Kafka 集群。这是异步操作,生产者会将消息发送到分区的 Leader 副本。
Future<RecordMetadata> future = producer.send(record);
-
6.应答机制:Kafka 根据配置的应答机制参数(acks),控制生产者如何确认消息已成功发送。
-
7.处理应答:使用 Future 对象获取 Kafka 的确认结果。
//同步的发送模式 try { RecordMetadata metadata = future.get(); System.out.println("Message sent to partition " + metadata.partition() + " with offset " + metadata.offset()); } catch (Exception e) { e.printStackTrace(); }
-
4.关闭生产者实例。
消费者
消费者和消费者组
每个消费者都有一个对应的消费者组(逻辑概念),消息发布到topic 后,只会投递给订阅它的每个消费组中的一个消费者。 消费者组是Kafka实现单播和广播两种消息模型的手段。同一个topic,每个消费者组都可以拿到相同的全部数据。
- 如果所有的消费者都隶属于同一个消费组,那么所有的消息都会被均衡地投递给每一个消费者,即每条消息只会被一个消费者处理,这就相当于点对点模式的应用。
- 如果所有的消费者都隶属于不同的消费组,那么所有的消息都会被广播给所有的消费者,即每条消息会被所有的消费者处理,这就相当于发布/订阅模式的应用。
相同消费者组 id 的都是同属一个消费者组,消费者组内消费者数量如果多于分区数,多余的消费者会无法消费(无法分配分区)。 不同消费者组会接收到同样的消息。(广播)
多线程消费
KafkaProducer是线程安全的, 然而KafkaConsumer却是非线程安全的。
实现多线程消费主要有两种常见方式:每个线程维护一个 KafkaConsumer 实例 和 单KafkaConsumer 实例 + 多 worker线程。
工作流程
-
(1)配置消费者客户端参数及创建相应的消费者实例:配置消费者客户端参数后,消费者需要创建一个 Kafka 消费者实例,指定 Kafka 集群的地址和消费者组 ID。
Properties props = new Properties(); props.put("bootstrap.servers", "localhost:9092"); // Kafka 集群地址 props.put("group.id", "test-group"); // 消费者组 ID props.put("key.deserializer", "org.apache.kafka.common.serialization.StringDeserializer"); props.put("value.deserializer", "org.apache.kafka.common.serialization.StringDeserializer"); Consumer<String, String> consumer = new KafkaConsumer<>(props);
-
(2)订阅主题:消费者通过 subscribe() 方法订阅一个或多个主题,Kafka 会动态分配分区到消费者。
consumer.subscribe(Arrays.asList("topic-name"));
-
(3)拉取消息并消费。(拉取模式):消费者通过 poll() 方法从 Kafka 集群拉取消息,poll() 方法会阻塞直到收集到一批数据或超时。消费者可以对拉取到的消息进行解析、存储或进一步处理。
//拉取消息 ConsumerRecords<String, String> records = consumer.poll(Duration.ofMillis(100)); //消费消息 for (ConsumerRecord<String, String> record : records) { System.out.printf("offset = %d, key = %s, value = %s%n", record.offset(), record.key(), record.value()); }
-
(4)提交消费位移:消费者需要定期提交处理过的消息的偏移量到 Kafka 集群,以便 Kafka 能够跟踪消费者的消费进度。
- 自动提交(默认)
props.put("enable.auto.commit", "true"); // 启用自动提交 props.put("auto.commit.interval.ms", "1000"); // 自动提交间隔
- 手动提交
props.put("enable.auto.commit", "false"); // 关闭自动提交 consumer.commitSync(); // 同步提交 consumer.commitAsync(); // 异步提交
- 自动提交(默认)
-
(5)关闭消费者实例。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· DeepSeek 开源周回顾「GitHub 热点速览」
· 物流快递公司核心技术能力-地址解析分单基础技术分享
· .NET 10首个预览版发布:重大改进与新特性概览!
· AI与.NET技术实操系列(二):开始使用ML.NET
· 单线程的Redis速度为什么快?