目录
Kafka基础介绍:
![](https://i-blog.csdnimg.cn/blog_migrate/476ee6da9a0b655bd677c072f58a245c.png)
(
1
)
Producer
:
消息生产者,就是向
Kafka broker
发消息的客户端。
(
2
)
Consumer
:
消息消费者,向
Kafka broker
取消息的客户端。
(
3
)
Consumer Group
(
CG
):
消费者组,由多个
consumer
组成。
消费者组内每个消
费者负责消费不同分区的数据,一个分区只能由一个组内消费者消费;消费者组之间互不
影响。
所有的消费者都属于某个消费者组,即
消费者组是逻辑上的一个订阅者
。
(
4
)
Broker
:
一台
Kafka
服务器就是一个
broker
。一个集群由多个
broker
组成。一个
broker
可以容纳多个
topic
。
(
5
)
Topic
:
可以理解为一个队列,
生产者和消费者面向的都是一个
topic
。
(
6
)
Partition
:
为了实现扩展性,一个非常大的
topic
可以分布到多个
broker
(即服
务器)上,
一个
topic
可以分为多个
partition
,每个
partition
是一个
有序的队列
。
(
7
)
Replica
:
副本。一个
topic
的每个分区都有若干个副本,一个
Leader
和若干个
Follower
。
(
8
)
Leader
:
每个分区多个
副本的
“
主
”
,生产者发送数据的对象,以及消费者消费数
据的对象都是
Leader
。
(
9
)
Follower
:
每个分区多个
副本中的
“
从
”
,实时从
Leader
中同步数据,保持和
Leader
数据的同步。
Leader
发生故障时,某个
Follower
会成为新的
Leader
。
1. 生产者消息发送流程
1.1 发送原理
在消息发送的过程中,涉及到了
两个线程——
main
线程和
Sender
线程
。在
main
线程
中创建了
一个双端队列
RecordAccumulator
。
main
线程将消息发送给
RecordAccumulator
,
Sender
线程不断从
RecordAccumulator
中拉取消息发送到
Kafka Broker
。
![](https://i-blog.csdnimg.cn/blog_migrate/31463759e1a5715e4d1cb48a109f598f.png)
1.2 生产者重要参数列表
参数名称
|
描述
|
bootstrap.servers
|
生产者连接集群所需的
broker
地 址 清 单 。 例 如hadoop102:9092,hadoop103:9092,hadoop104:9092,可以
设置
1 个或者多个,中间用逗号隔开。注意这里并非需要所有的 broker
地址,因为生产者从给定的
broker
里查找到其他
broker
信息。
|
key.serializer
和
value.serializer
|
指定发送消息的
key
和
value
的序列化类型。一定要写 全类名。
|
buffer.memory
|
RecordAccumulator
缓冲区总大小,
默认
32m。
|
batch.size
|
缓冲区一批数据最大值,
默认
16k
。适当增加该值,可 以提高吞吐量,但是如果该值设置太大,会导致数据 传输延迟增加。
|
linger.ms
|
如果数据迟迟未达到
batch.size
,
sender
等待
linger.time 之后就会发送数据。单位 ms
,
默认值是
0ms
,表示没 有延迟。生产环境建议该值大小为 5-100ms
之间。
|
acks
|
0
:生产者发送过来的数据,不需要等数据落盘应答。
1
:生产者发送过来的数据,
Leader
收到数据后应答。
-1
(
all
):生产者发送过来的数据,
Leader+
和
isr
队列
里面的所有节点收齐数据后应答。
默认值是
-1
,
-1
和 all 是等价的。
|
max.in.flight.requests.per.connection
|
允许最多没有返回
ack
的次数,
默认为
5
,开启幂等性 要保证该值是 1-5
的数字。
|
retries
|
当消息发送出现错误的时候,系统会重发消息。
retries 表示重试次数。
默认是
int
最大值,
2147483647
。
如果设置了重试,还想保证消息的有序性,需要设置 MAX_IN_FLIGHT_REQUESTS_PER_CONNECTION=1 否则在重试此失败消息的时候,其他的消息可能发送成功了。
|
compression.type
|
生产者发送的所有数据的压缩方式。
默认是
none
,也 就是不压缩。 支持压缩类型:
none
、
gzip
、
snappy
、
lz4
和
zstd
。
|
retry.backoff.ms
|
两次重试之间的时间间隔,默认是
100ms
。
|
1.3 异步发送 API
1.3.1 普通异步发送
(1)需求:创建
Kafka
生产者,采用异步的方式发送到
Kafka Broker
(2)导入依赖
<dependencies>
<dependency>
<groupId>org.apache.kafka</groupId>
<artifactId>kafka-clients</artifactId>
<version>3.0.0</version>
</dependency>
</dependencies>
(3)代码编写
package com.zsh.kafkatest.producer;
import org.apache.kafka.clients.producer.KafkaProducer;
import org.apache.kafka.clients.producer.ProducerConfig;
import org.apache.kafka.clients.producer.ProducerRecord;
import java.util.Properties;
//普通异步发送
public class CustomProducer {
public static void main(String[] args) throws
InterruptedException {
// 1. 创建 kafka 生产者的配置对象
Properties properties = new Properties();
// 2. 给 kafka 配置对象添加配置信息:bootstrap.servers
properties.put(ProducerConfig.BOOTSTRAP_SERVERS_CONFIG,
"192.168.140.65:9092");
// key,value 序列化(必须):key.serializer,value.serializer
properties.put(ProducerConfig.KEY_SERIALIZER_CLASS_CONFIG, "org.apache.kafka.common.serialization.StringSerializer");
properties.put(ProducerConfig.VALUE_SERIALIZER_CLASS_CONFIG, "org.apache.kafka.common.serialization.StringSerializer");
// 3. 创建 kafka 生产者对象
KafkaProducer<String, String> kafkaProducer = new
KafkaProducer<String, String>(properties);
// 4. 调用 send 方法,发送消息
for (int i = 0; i < 5; i++) {
kafkaProducer.send(new
ProducerRecord<>("first","atguigu " + i));
}
// 5. 关闭资源
kafkaProducer.close();
}
}
1.3.2 带回调函数的异步发送
回调函数会在 producer
收到
ack
时调用,为异步调用,该方法有两个参数,分别是元
数据信息(
RecordMetadata
)和异常信息(
Exception
),如果
Exception
为
null
,说明消息发
送成功,如果
Exception
不为
null
,说明消息发送失败。
package com.zsh.kafkatest.producer;
import org.apache.kafka.clients.producer.*;
import org.apache.kafka.common.serialization.StringSerializer;
import java.util.Properties;
//带回调函数的异步发送,注意:消息发送失败会自动重试,不需要我们在回调函数中手动重试。
public class CustomProducerCallback {
public static void main(String[] args) throws
InterruptedException{
// test1();
test2();
// test3();
}
//1、、将数据随机发往 partition 的情况下
public static void test1()throws
InterruptedException{
// 1. 创建 kafka 生产者的配置对象
Properties properties = new Properties();
// 2. 给 kafka 配置对象添加配置信息
properties.put(ProducerConfig.BOOTSTRAP_SERVERS_CONFIG,
"192.168.140.65:9092");
// key,value 序列化(必须):key.serializer,value.serializer
properties.put(ProducerConfig.KEY_SERIALIZER_CLASS_CONFIG,
StringSerializer.class.getName());
properties.put(ProducerConfig.VALUE_SERIALIZER_CLASS_CONFIG,
StringSerializer.class.getName());
// 3. 创建 kafka 生产者对象
KafkaProducer<String, String> kafkaProducer = new
KafkaProducer<String, String>(properties);
// 4. 调用 send 方法,发送消息
for (int i = 0; i < 5; i++) {
// 添加回调
kafkaProducer.send(new ProducerRecord<>("first",
"zsh " + i), new Callback() {
// 该方法在 Producer 收到 ack 时调用,为异步调用
@Override
public void onCompletion(RecordMetadata metadata,
Exception exception) {
if (exception == null) {
// 没有异常,输出信息到控制台
System.out.println(" 主题: " +
metadata.topic() + "->" + "分区:" + metadata.partition());
} else {
// 出现异常打印
exception.printStackTrace();
}
}
});
// 延迟一会会看到数据发往不同分区
Thread.sleep(2);
}
// 5. 关闭资源
kafkaProducer.close();
}
//2、将数据发往指定 partition 的情况下,例如,将所有数据发往分区 1 中。
public static void test2()throws InterruptedException{
// 1. 创建 kafka 生产者的配置对象
Properties properties = new Properties();
// 2. 给 kafka 配置对象添加配置信息
properties.put(ProducerConfig.BOOTSTRAP_SERVERS_CONFIG,"192.168.140.65:9092");
// key,value 序列化(必须):key.serializer,value.serializer
properties.put(ProducerConfig.KEY_SERIALIZER_CLASS_CONFIG,
StringSerializer.class.getName());
properties.put(ProducerConfig.VALUE_SERIALIZER_CLASS_CONFIG,
StringSerializer.class.getName());
KafkaProducer<String, String> kafkaProducer = new
KafkaProducer<>(properties);
for (int i = 0; i < 5; i++) {
// 指定数据发送到 1 号分区,key 为空(IDEA 中 ctrl + p 查看参数)
kafkaProducer.send(new ProducerRecord<>("first",
0,"","FENQU " + i), new Callback() {
@Override
public void onCompletion(RecordMetadata metadata,
Exception e) {
if (e == null){
System.out.println(" 主题: " +
metadata.topic() + "->" + "分区:" + metadata.partition()
);
}else {
e.printStackTrace();
}
}
});
}
kafkaProducer.close();
}
//3、没有指明 partition 值但有 key 的情况下,将 key 的 hash 值与 topic 的 partition 数进行取余得到 partition 值
public static void test3()throws InterruptedException{
Properties properties = new Properties();
properties.put(ProducerConfig.BOOTSTRAP_SERVERS_CONFIG,"192.168.140.65:9092");
properties.put(ProducerConfig.KEY_SERIALIZER_CLASS_CONFIG,
StringSerializer.class.getName());
properties.put(ProducerConfig.VALUE_SERIALIZER_CLASS_CONFIG,
StringSerializer.class.getName());
KafkaProducer<String, String> kafkaProducer = new
KafkaProducer<>(properties);
for (int i = 0; i < 5; i++) {
// 依次指定 key 值为 a,b,f ,数据 key 的 hash 值与 3 个分区求余,分别发往 1、2、0
kafkaProducer.send(new ProducerRecord<>("first",
"a","atguigu " + i), new Callback() {
@Override
public void onCompletion(RecordMetadata metadata,
Exception e) {
if (e == null){
System.out.println(" 主题: " +
metadata.topic() + "->" + "分区:" + metadata.partition()
);
}else {
e.printStackTrace();
}
}
});
}
kafkaProducer.close();
}
}
1.4 同步发送 API
只需在异步发送的基础上,再调用一下
get()
方法即可。
package com.zsh.kafkatest.producer;
import org.apache.kafka.clients.producer.KafkaProducer;
import org.apache.kafka.clients.producer.ProducerConfig;
import org.apache.kafka.clients.producer.ProducerRecord;
import org.apache.kafka.common.serialization.StringSerializer;
import java.util.Properties;
import java.util.concurrent.ExecutionException;
//同步发送 API,只需在异步发送的基础上,再调用一下 get()方法即可。
public class CustomProducerSync {
public static void main(String[] args) throws
InterruptedException, ExecutionException {
// 1. 创建 kafka 生产者的配置对象
Properties properties = new Properties();
// 2. 给 kafka 配置对象添加配置信息
properties.put(ProducerConfig.BOOTSTRAP_SERVERS_CONFIG,"192.168.140.65:9092");
// key,value 序列化(必须):key.serializer,value.serializer
properties.put(ProducerConfig.KEY_SERIALIZER_CLASS_CONFIG,
StringSerializer.class.getName());
properties.put(ProducerConfig.VALUE_SERIALIZER_CLASS_CONFIG,
StringSerializer.class.getName());
// 3. 创建 kafka 生产者对象
KafkaProducer<String, String> kafkaProducer = new
KafkaProducer<String, String>(properties);
// 4. 调用 send 方法,发送消息
for (int i = 0; i < 10; i++) {
// 异步发送 默认
kafkaProducer.send(new ProducerRecord<>("first","kafka" + i));
// 同步发送
kafkaProducer.send(new
ProducerRecord<>("first","myd" + i)).get();
}
// 5. 关闭资源
kafkaProducer.close();
}
}
2. 消费者消费消息
2.1 Kafka 消费者工作流程
2.2 消费者重要参数
参数名称
|
描述
|
bootstrap.servers
|
向
Kafka
集群建立初始连接用到的
host/port
列表。
|
key.deserializer
和
value.deserializer
|
指定接收消息的
key
和
value
的反序列化类型。一定要写全 类名。
|
group.id
|
标记消费者所属的消费者组。
|
enable.auto.commit
|
默认值为
true
,消费者会自动周期性地向服务器提交偏移 量。
|
auto.commit.interval.ms
|
如果设置了
enable.auto.commit
的值为
true
, 则该值定义了 消费者偏移量向 Kafka
提交的频率,
默认
5s
。
|
auto.offset.reset
|
当
Kafka
中没有初始偏移量或当前偏移量在服务器中不存在 (如,数据被删除了),该如何处理? earliest
:自动重置偏 移量到最早的偏移量。
latest
:默认,自动重置偏移量为最 新的偏移量。
none
:如果消费组原来的(
previous
)偏移量 不存在,则向消费者抛异常。 anything
:向消费者抛异常。
|
offsets.topic.num.partitions
|
__consumer_offsets
的分区数,
默认是
50
个分区。
|
heartbeat.interval.ms
|
Kafka
消费者和
coordinator
之间的心跳时间,
默认
3s
。
该条目的值必须小于 session.timeout.ms
,也不应该高于 session.timeout.ms 的
1/3
。
|
session.timeout.ms
|
Kafka
消费者和
coordinator
之间连接超时时间,
默认
45s
。 超过该值,该消费者被移除,消费者组执行再平衡。
|
max.poll.interval.ms
|
消费者处理消息的最大时长,
默认是
5
分钟
。超过该值,该消费者被移除,消费者组执行再平衡。
|
fetch.min.bytes
|
默认
1
个字节。消费者
获取服务器端一批消息最小的字节数。
|
fetch.max.wait.ms
|
默认
500ms
。如果没有从服务器端获取到一批数据的最小字节数
。该时间到,仍然会返回数据。
|
fetch.max.bytes
|
默认
Default: 52428800
(
50 m
)。消费者
获取服务器端一批 消息最大的字节数。如果服务器端一批次的数据大于该值 (50m
)仍然可以拉取回来这批数据,因此,这不是一个绝 对最大值。一批次的大小受 message.max.bytes
(
broker config)
or max.message.bytes
(
topic config
)影响。
|
max.poll.records
|
一次
poll
拉取数据返回消息的最大条数,
默认是
500
条。
|
2.3 消费者 API
2.3.1 独立消费者案例(订阅主题)
(1)需求: 创建一个独立消费者,消费 first 主题中数据。
![](https://i-blog.csdnimg.cn/blog_migrate/340cd2ca06a9788e23df78f8bb2d2e99.png)
注意:
在消费者
API
代码中必须配置消费者组
id
。命令行启动消费者不填写消费者组
id
会被自动填写随机的消费者组
id
(2)代码
package com.zsh.kafkatest.consumer;
import org.apache.kafka.clients.consumer.ConsumerConfig;
import org.apache.kafka.clients.consumer.ConsumerRecord;
import org.apache.kafka.clients.consumer.ConsumerRecords;
import org.apache.kafka.clients.consumer.KafkaConsumer;
import org.apache.kafka.common.serialization.StringDeserializer;
import java.time.Duration;
import java.util.ArrayList;
import java.util.Properties;
//创建一个独立消费者,消费 first 主题中数据。
public class CustomConsumer {
public static void main(String[] args) {
// 1.创建消费者的配置对象
Properties properties = new Properties();
// 2.给消费者配置对象添加参数
properties.put(ConsumerConfig.BOOTSTRAP_SERVERS_CONFIG, "192.168.140.65:9092");
// 配置序列化 必须
properties.put(ConsumerConfig.KEY_DESERIALIZER_CLASS_CONFIG,
StringDeserializer.class.getName());
properties.put(ConsumerConfig.VALUE_DESERIALIZER_CLASS_CONFIG,
StringDeserializer.class.getName());
// 配置消费者组(组名任意起名) 必须
properties.put(ConsumerConfig.GROUP_ID_CONFIG, "test");
// 创建消费者对象
KafkaConsumer<String, String> kafkaConsumer = new
KafkaConsumer<String, String>(properties);
// 注册要消费的主题(可以消费多个主题)
ArrayList<String> topics = new ArrayList<>();
topics.add("first");
kafkaConsumer.subscribe(topics);
// 拉取数据打印
while (true) {
// 设置 1s 中消费一批数据
ConsumerRecords<String, String> consumerRecords =
kafkaConsumer.poll(Duration.ofSeconds(30));
// 打印消费到的数据
for (ConsumerRecord<String, String> consumerRecord :
consumerRecords) {
System.out.println(consumerRecord);
}
}
}
}
2.3.2 独立消费者案例(订阅分区)
(1)需求:创建一个独立消费者,消费 first 主题 0 号分区的数据。
(2)代码编写
package com.zsh.kafkatest.consumer;
import org.apache.kafka.clients.consumer.ConsumerConfig;
import org.apache.kafka.clients.consumer.ConsumerRecord;
import org.apache.kafka.clients.consumer.ConsumerRecords;
import org.apache.kafka.clients.consumer.KafkaConsumer;
import org.apache.kafka.common.TopicPartition;
import org.apache.kafka.common.serialization.StringDeserializer;
import java.time.Duration;
import java.util.ArrayList;
import java.util.Properties;
//创建一个独立消费者,消费 first 主题 0 号分区的数据。
public class CustomConsumerPartition {
public static void main(String[] args) {
Properties properties = new Properties();
properties.put(ConsumerConfig.BOOTSTRAP_SERVERS_CONFIG,"192.168.140.65:9092");
// 配置序列化 必须
properties.put(ConsumerConfig.KEY_DESERIALIZER_CLASS_CONFIG,
StringDeserializer.class.getName());
properties.put(ConsumerConfig.VALUE_DESERIALIZER_CLASS_CONFIG,
StringDeserializer.class.getName());
// 配置消费者组(必须),名字可以任意起
properties.put(ConsumerConfig.GROUP_ID_CONFIG,"test");
KafkaConsumer<String, String> kafkaConsumer = new
KafkaConsumer<>(properties);
// 消费某个主题的某个分区数据
ArrayList<TopicPartition> topicPartitions = new
ArrayList<>();
topicPartitions.add(new TopicPartition("first", 0));
kafkaConsumer.assign(topicPartitions);
while (true){
ConsumerRecords<String, String> consumerRecords =
kafkaConsumer.poll(Duration.ofSeconds(1));
for (ConsumerRecord<String, String> consumerRecord :
consumerRecords) {
System.out.println(consumerRecord);
}
}
}
}