kafka工作流程?
生产者生产好消息之后调用send()方法发送到broker端,broker将收到的消息存储的对应topic中的patition中,而broker中的消息实际上是存储在了commit-log文件中,消费者监听定时循环拉取消息
一、生产者发送消息流程
参考代码:
package com.example;
import org.apache.kafka.clients.producer.KafkaProducer;
import org.apache.kafka.clients.producer.ProducerRecord;
import org.apache.kafka.clients.producer.RecordMetadata;
import java.util.Properties;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future;
public class MyProductor {
public static void main(String[] args) throws ExecutionException, InterruptedException {
//kafka的配置
Properties properties = new Properties();
//kafka服务器地址和端口
properties.put("bootstrap.servers", "localhost:9092");
//Producer的压缩算法使用的是GZIP
//为什么要压缩?
properties.put("compression.type","gzip");
//指定发送消息的key和value的序列化类型
properties.put("key.serializer", "org.apache.kafka.common,serialization.StringSerializer");
properties.put("value.serializer", "org.apache.kafka.common,serialization.StringSerializer");
//补充:为什么要序列化/反序列化?
//实例化一个生产者对象,指定发送的主题、key、value、分区号等
KafkaProducer<Object, Object> producer = new KafkaProducer<>(properties);
//发送100条消息
for (int i = 0; i < 100; i++) {
//调用send方法,向kafka发送数据,并返回一个Future对象,通过该对象来获取结果
Future<RecordMetadata> result = producer.send(new ProducerRecord<>("my-topic", Integer.toString(i),
Integer.toString(i)));
RecordMetadata recordMetadata = result.get();
}
//关闭生产者对象
producer.close();
}
}
第一步、生产者配置参数
指定生产消息要达到的kafka服务器地址,压缩方式、序列化方式
①、为什么要进行压缩?
Producer生产的每个消息都经过GZIP压缩,在传输的过程中能够节省网络传输带宽和Broker磁盘占用
②、为什么要进行序列化/反序列化?
数据在网络传输过程中都是以字节流的形式传输的,在生产者发送消息的时候需要将消息先进行序列化
第二步、拦截器
生产者在发送消息前会对请求的消息进行拦截,起到过滤和处理的作用。
我们可以自定义拦截器,拦截器中定义自己需要的逻辑,满足个性化配置。比方说对消息进行加密解密、消息格式转换、消息路由等等
第三步、序列化器
数据在网络传输过程中都是以字节流的形式传输的,在生产者发送消息的时候需要将消息先进行序列化
第四步、分区器
- 如果ProducerRecord对象提供了分区号,使用提供的分区号
- 如果没有提供分区号,提供了key,则使用key序列化后的值的hash值对分区数量取模
- 如果没有提供分区号、key,采用轮询方式分配分区号(默认)
第五步、send()发送消息
通过上面的操作生产者已经知道该往哪个主题、哪个分区发送这条消息了。
第六步、获取发送消息响应
①、如果消息发送成功:broker收到消息之后会返回一个Future类型RecordMetadata对象,可以通过该对象来获取发送的结果,对象中记录了此条消息发送到的topic、partition、offset。
②、消息发送失败:错误消息。在收到错误消息之后会有尝试机制,尝试重新发送消息
但直接使用send(msg)会出现问题,调用之后会立即返回,如果因为网络等外界因素影响导致消息没有发送到broker,出现生产者程序丢失数据问题,只能通过处理返回的Future对象处理才能感知到。
对应的解决方案是我们可以使用send(msg,callbakc)的方式发哦是那个消息并设置回调函数
在发送消息后,会立即调用回调函数来处理发送结果,回调函数中定义了处理逻辑
二、broker收发消息流程
1. 分区机制(主题-分区-消息)
前文中提到生产者发送到broker的消息都是基于topic进行分类的(逻辑上),而topic中的消息是以partition为单位存储的(物理上),每条消息都有自己的offset
①、 分区中的数据存储在哪儿?
每个partition都有一个commit log文件
②、 为什么要分区(好处)存储?
如果commitlog文件很大的话可能导致一台服务器无法承担所有的数据量,机器无法存储,分区之后可以把不同的分区放在不同的机器上,相当于是分布式存储
- 每个消费者并行消费
- 提高可用性,增加若干副本
2. 消息存储
每一个partition都对应了一个commit log文件,日志文件中存储了消息等信息,新到达的消息以追加的方式写入分区的末尾,然后以先入先出的顺序读取。
①、 分区中的消息会一直存储吗?
如果不停的一致向日志文件中写入消息,日志文件大小也是有上限的,所以kafka会定期的清理磁盘,有两种方式:
- 时间:kafka默认保留最近一周的消息(根据配置文中的日志保留时间设置的:log.retention.hours)
- 大小:kakfa在配置文件中配置单个消息的大小为1MB,如果生产者发送的消息超过1MB,不会接收消息
②、follower副本数据什么时候同步更新的?
- 数据传输阶段:Leader副本将消息发送给Follower副本。这个过程中,Leader副本会将消息按照一定的批次大小发送给Follower副本,Follower副本会接收并写入本地日志。一旦Follower副本成功写入消息到本地日志,就会向Leader副本发送确认消息。
- 确认阶段:Leader副本在收到来自所有Follower副本的确认消息后,就会认为消息已经成功复制到所有的副本中。然后向生产者发送成功响应,表示消息已被成功接收和复制。
注意的是,Follower副本的数据同步是异步进行的,即Follower副本不需要等待数据同步完成才返回成功响应。这样可以提高消息的处理速度和吞吐量。但也意味着,在数据同步过程中,Follower副本可能会滞后于Leader副本一段时间,这个时间间隔称为追赶(lag)。Kafka提供了配置参数来控制同步和追赶的速度,以平衡数据的一致性和性能的需求。
三、消费者消费消息流程
- 配置消费者客户端参数
- 创建消费者实例并指定订阅的主题
- 拉取消息并消费
- 提交消费offset
参考代码:
package com.example;
import org.apache.kafka.clients.consumer.*;
import org.apache.kafka.common.TopicPartition;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Map;
import java.util.Properties;
public class Consumer {
public static void main(String[] args) {
Properties properties = new Properties();
//要连接的kafka服务器
properties.put("bootstrap.servers", "localhost:9092");
//标识当前消费者所属的小组
properties.put("group.id", "test");
//---------位移提交(自动提交)----------
//为true,自动定期地向服务器提交偏移量(offset)
properties.put("enable.auto.commit", "true");
//自动提交offset的间隔,默认是5000ms(5s)
properties.put("auto.commit.interval.ms", "1000");
//每隔固定实践消费者就会把poll获取到的最大偏移量进行自动提交
//出现的问题:如果刚提交了offset,还没到5s,2s的时候就发生了均衡,导致分区会重新划分,此时offset是不准确的
//key和value反序列化
properties.put("key.serializer", "org.apache.kafka.common,serialization.StringSerializer");
properties.put("value.serializer", "org.apache.kafka.common,serialization.StringSerializer");
KafkaConsumer<Object, Object> consumer = new KafkaConsumer<>(properties);
//指定consumer消费的主题(订阅多个)
consumer.subscribe(Arrays.asList("my-topic", "bar"));
//轮询向服务器定时请求数据
while (true) {
//拉取数据
ConsumerRecords<Object, Object> records = consumer.poll(100);
for (ConsumerRecord<Object, Object> record : records) {
//同步提交:提交当前轮询的最大offset
consumer.commitSync();
//如果失败还会进行重试
//优点:提交成功准确率上升;缺点:降低程序吞吐量
System.out.printf("offset=%d,key=%s,value=%s%n", record.offset(), record.key(), record.value());
//异步提交并定义回调
//优点:提高程序吞吐量(不需要等待请求响应,程序可以继续往下执行)
//缺点:当提交失败的时候不会自动重试;
consumer.commitAsync(new OffsetCommitCallback() {
@Override
public void onComplete(Map<TopicPartition, OffsetAndMetadata> offsets,
Exception exception) {
if (exception != null) {
System.out.println("错误处理");
offsets.forEach((x, y) -> System.out.printf(
"topic = %s,partition = %d, offset = %s \n", x.topic(), x.partition(), y.offset()));
}
}
});
}
}
}
}
第一步、配置消费者客户端参数
配置要消费消息的kafka服务器、消费者所在的消费组、offset是自动提交还是手动提交
enable.auto.commit和auto.commit.interval.ms参数为是否自动提交参数
- enable.auto.commit=true:自动定期地向服务器提交偏移量(offset)
- auto.commit.interval.ms:动提交offset的间隔,默认是5000ms(5s)
逻辑:每隔固定实践消费者就会把poll获取到的最大偏移量进行自动提交
出现的问题:如果刚提交了offset,还没到5s,2s的时候就发生了均衡,导致分区会重新划分,此时offset是不准确的,所以我们也可以配置手动提交的方式,具体的手动提交方式在下面第四步会讲到
第二步、创建消费者实例并指定订阅的主题
调用subscribe()方法可以订阅多个主题
既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,涵盖了95%以上大数据知识点,真正体系化!
由于文件比较多,这里只是将部分目录截图出来,全套包含大厂面经、学习笔记、源码讲义、实战项目、大纲路线、讲解视频,并且后续会持续更新
82f92fd0559c67f0.png)
[外链图片转存中…(img-tjuD7yqa-1714782901910)]
[外链图片转存中…(img-kJ2RPLNL-1714782901911)]
[外链图片转存中…(img-0u5lqoLr-1714782901911)]
既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,涵盖了95%以上大数据知识点,真正体系化!
由于文件比较多,这里只是将部分目录截图出来,全套包含大厂面经、学习笔记、源码讲义、实战项目、大纲路线、讲解视频,并且后续会持续更新