【尚硅谷】2022版Kafka3.x教程(从入门到调优,深入全面)
在消息发送的过程中,涉及到了两个线程——main线程和Sender线程。在main线程中创建一个双端队列RecordAccumulator。main线程负责将消息发送给RecordAccumulator,sender线程则不断地从RecordAccumulator中拉取消息发送到Kafka Broker。
缓冲区RecordAccumulator默认的大小为32M,触发sender线程的条件有两种:一种是数据达到batch.size时,或者时间到达linger.ms时。相应地,在生产中,为了提高数据吞吐量有以下方案:1)修改缓存的大小buffer.memory为64M;2)增加batch.size的大小为32K;3)修改linger.ms为5ms~100ms左右;4)添加数据压缩方式。
max.in.flight.requests.per.connection属性设置了允许最多没有返回ACK的Request的个数,若开启了幂等性,则需要小于等于5。当Request发送出错时,retries参数设置了重试的次数(默认为INT的最大值),retry.backoff.ms设置了两次重试的时间间隔(默认为100ms)。当收到Kafka集群的应答后,NetWorkClient就清理对应的Request,RecordAccumulator就清理对应的缓存数据。Producer发送数据流程结束。
其他参数:bootstrap.servers设置了生产者连接集群所需的broker地址清单,可以有一个或多个;key.serializer和value.serializer分别指定了发送消息的key和value的序列化类型;acks设置了生产者对Kafka集群的应答方式;enable.idempotence设置了是否开启幂等性,默认是true;compression.type设置了生产者发送的所有数据的压缩方式(包括gzip,snappy,lz4和zstd),默认为none。
前提:拥有一个包含节点hadoop101,hadoop102和hadoop103的Kafka集群并创造了一个主题名为first,分区数为3,副本因子为3的topic。
导入依赖:
<dependency>
<groupId>org.apache.kafkagroupId>
<artifactId>kafka-clientsartifactId>
<version>3.0.0version>
dependency>
CustomProducerAsync.java
package com.jd.springboot_kafka.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;
public class CustomProducer {
public static void main(String[] args) {
//0 配置参数
Properties properties = new Properties();
properties.put(ProducerConfig.BOOTSTRAP_SERVERS_CONFIG,"hadoop101:9092,hadoop102:9092");
properties.put(ProducerConfig.KEY_SERIALIZER_CLASS_CONFIG, StringSerializer.class.getName());
properties.put(ProducerConfig.VALUE_SERIALIZER_CLASS_CONFIG, StringSerializer.class.getName());
//1、创建Kafka生产者对象
KafkaProducer<String, String> kafkaProducer = new KafkaProducer<String, String>(properties);
//2、发送数据
for (int i = 0; i < 5; i++) {
kafkaProducer.send(new ProducerRecord<>("first","msg_async"+i));
}
//3、关闭资源
kafkaProducer.close();
}
}
Producer完成send()方法后,就可以执行之后的任务了,broker在收到消息后异步调用生产者提供的callback()方法。
CustomProducerCallback.java
package com.jd.springboot_kafka.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 {
//配置参数
Properties properties = new Properties();
properties.put(ProducerConfig.BOOTSTRAP_SERVERS_CONFIG,"hadoop101:9092,hadoop102:9092");
properties.put(ProducerConfig.KEY_SERIALIZER_CLASS_CONFIG, StringSerializer.class.getName());
properties.put(ProducerConfig.VALUE_SERIALIZER_CLASS_CONFIG, StringSerializer.class.getName());
//1、创建Kafka生产者对象
KafkaProducer<String, String> kafkaProducer = new KafkaProducer<>(properties);
//2、发送数据
for (int i = 0; i < 5; i++) {
kafkaProducer.send(new ProducerRecord<String, String>("first", "msg" + i), new Callback() {
@Override
public void onCompletion(RecordMetadata recordMetadata, Exception e) {
if (e == null) {
System.out.println("主题:"+recordMetadata.topic()+",分区:"+recordMetadata.partition());
}
}
});
System.out.printf("之后的任务..."+i);
Thread.sleep(1);
}
//3、关闭资源
kafkaProducer.close();
}
}
CustomProducerSync.java
package com.jd.springboot_kafka.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;
public class CustomProducerSync {
public static void main(String[] args) throws ExecutionException, InterruptedException {
//配置参数
Properties properties = new Properties();
properties.put(ProducerConfig.BOOTSTRAP_SERVERS_CONFIG,"hadoop101:9092,hadoop102:9092");
properties.put(ProducerConfig.KEY_SERIALIZER_CLASS_CONFIG, StringSerializer.class.getName());
properties.put(ProducerConfig.VALUE_SERIALIZER_CLASS_CONFIG, StringSerializer.class.getName());
//1、创建Kafka生产者对象
KafkaProducer<String, String> kafkaProducer = new KafkaProducer<String, String>(properties);
//2、发送数据
for (int i = 0; i < 5; i++) {
kafkaProducer.send(new ProducerRecord<>("first","msg_sync"+i)).get();
}
//3、关闭资源
kafkaProducer.close();
}
}
1)从存储上看,便于合理使用存储资源,每个Partition在一个Broker上存储,可以把海量的数据按照分区切割成一块一块数据存储在多台Broker上。合理控制分区的任务,可以实现负载均衡的效果。
2)从计算上看,提高并行度,生产者可以以分区为单位发送数据;消费者可以以分区为单位进行消费数据。
1)若指定了生产者发送的分区,就按照指定的分区执行:
//2、发送数据
for (int i = 0; i < 5; i++) {
kafkaProducer.send(new ProducerRecord<String, String>("first",1,"", "msg" + i), new Callback() {
@Override
public void onCompletion(RecordMetadata recordMetadata, Exception e) {
if (e == null) {
System.out.println("主题:"+recordMetadata.topic()+",分区:"+recordMetadata.partition());
}
}
});
Thread.sleep(20);
}
2)若没有指定分区,但发送的数据包含了key,就计算hashcode(key)%分区数,得到分区值:
//2、发送数据
for (int i = 0; i < 5; i++) {
kafkaProducer.send(new ProducerRecord<String, String>("first","", "msg" + i), new Callback() {
@Override
public void onCompletion(RecordMetadata recordMetadata, Exception e) {
if (e == null) {
System.out.println("主题:"+recordMetadata.topic()+",分区:"+recordMetadata.partition());
}
}
});
Thread.sleep(1);
}
3)若没有指定key,就随机分配分区,使用黏性方案,即消息层面的黏性,批次层面上的轮询:
//2、发送数据
for (int i = 0; i < 5; i++) {
kafkaProducer.send(new ProducerRecord<String, String>("first", "msg" + i), new Callback() {
@Override
public void onCompletion(RecordMetadata recordMetadata, Exception e) {
if (e == null) {
System.out.println("主题:"+recordMetadata.topic()+",分区:"+recordMetadata.partition());
}
}
});
Thread.sleep(1);
}
4)自定义分区:将数据中包含"msg"字段的数据分至1区,其他的分至0区。
创造Partition接口实现类MyPartition.java:
package com.jd.springboot_kafka.patitioner;
import org.apache.kafka.clients.producer.Partitioner;
import org.apache.kafka.common.Cluster;
import java.util.Map;
public class MyPartitioner implements Partitioner {
//返回分区的值
@Override
public int partition(String topic, Object key, byte[] keyBytes, Object value, byte[] valueBytes, Cluster cluster) {
String valueString = value.toString();
if (valueString.contains("msg")) {
return 1;
}
return 0;
}
@Override
public void close() {
}
@Override
public void configure(Map<String, ?> map) {
}
}
测试CustomProducerMyPartitioner.java:
package com.jd.springboot_kafka.producer;
import org.apache.kafka.clients.producer.*;
import org.apache.kafka.common.serialization.StringSerializer;
import java.util.Properties;
public class CustomProducerMyPartitioner {
public static void main(String[] args) {
//配置参数
Properties properties = new Properties();
properties.put(ProducerConfig.BOOTSTRAP_SERVERS_CONFIG,"hadoop101:9092,hadoop102:9092");
properties.put(ProducerConfig.KEY_SERIALIZER_CLASS_CONFIG, StringSerializer.class.getName());
properties.put(ProducerConfig.VALUE_SERIALIZER_CLASS_CONFIG, StringSerializer.class.getName());
// 设置自定义的分区方案
properties.put(ProducerConfig.PARTITIONER_CLASS_CONFIG,"com.jd.springboot_kafka.patitioner.MyPartitioner");
//1、创建Kafka生产者对象
KafkaProducer<String, String> kafkaProducer = new KafkaProducer<>(properties);
//2、发送数据
for (int i = 0; i < 5; i++) {
kafkaProducer.send(new ProducerRecord<String, String>("first", "message" + i), new Callback() {
@Override
public void onCompletion(RecordMetadata recordMetadata, Exception e) {
if (e == null) {
System.out.println("主题:"+recordMetadata.topic()+",分区:"+recordMetadata.partition());
}
}
});
}
//3、关闭资源
kafkaProducer.close();
}
}
在生产中,为了提高数据吞吐量有以下方案:1)修改缓存的大小buffer.memory为64M;2)增加batch.size的大小为32K;3)修改linger.ms为5ms~100ms左右;4)添加数据压缩方式。
CustomProducerParameter.java
package com.jd.springboot_kafka.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;
public class CustomProducerParameter {
public static void main(String[] args) {
// 配置参数
Properties properties = new Properties();
properties.put(ProducerConfig.BOOTSTRAP_SERVERS_CONFIG,"hadoop101:9092,hadoop102:9092");
properties.put(ProducerConfig.KEY_SERIALIZER_CLASS_CONFIG, StringSerializer.class.getName());
properties.put(ProducerConfig.VALUE_SERIALIZER_CLASS_CONFIG,StringSerializer.class.getName());
//配置缓冲区大小
properties.put(ProducerConfig.BUFFER_MEMORY_CONFIG,33554432);
//配置批次大小
properties.put(ProducerConfig.BATCH_SIZE_CONFIG,16384);
//配置等待时间
properties.put(ProducerConfig.LINGER_MS_CONFIG,1);
//配置压缩类型
properties.put(ProducerConfig.COMPRESSION_TYPE_CONFIG,"snappy");
//1 建立生产者对象
KafkaProducer<String, String> kafkaProducer = new KafkaProducer<String, String>(properties);
//2 发送数据
for (int i = 0; i < 5; i++) {
ProducerRecord<String, String> record = new ProducerRecord<String,String>("first","atguigu"+i);
kafkaProducer.send(record);
}
//3 关闭资源
kafkaProducer.close();
}
}
设置ACKS的级别为0,代表生产者发送过来的数据,不需要等数据落盘应答,容易丢数,效率高;
设置ACKS的级别为1(默认),代表生产者发送过来的数据,Leader收到数据后应答,有丢数的可能,即Leader发送完ack后,瞬间宕机,还未向Follower同步数据;
设置ACKS的级别为"ALL",代表生产者发送过来的数据,Leader和ISR队列里面的所有节点收齐数据后应答。很安全
ACKS=1适合传输普通的日志,上万条中丢失1条影响不大,而ALL适合传输销售信息,和钱相关的情况。
数据完全可靠的条件:ACK的级别为ALL,且副本数大于等于2,ISR中应答的最小副本数量大于等于2。
CustomProducerACK.java
package com.jd.springboot_kafka.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;
public class CustomProducerACK {
public static void main(String[] args) {
//配置参数
Properties properties = new Properties();
properties.put(ProducerConfig.BOOTSTRAP_SERVERS_CONFIG,"hadoop101:9092,hadoop102:9092");
properties.put(ProducerConfig.KEY_SERIALIZER_CLASS_CONFIG, StringSerializer.class.getName());
properties.put(ProducerConfig.VALUE_SERIALIZER_CLASS_CONFIG, StringSerializer.class.getName());
//设置acks
properties.put(ProducerConfig.ACKS_CONFIG,"all");
//设置重试次数,默认无限次
properties.put(ProducerConfig.RETRIES_CONFIG,3);
//1、创建Kafka生产者对象
KafkaProducer<String, String> kafkaProducer = new KafkaProducer<String, String>(properties);
//2、发送数据
for (int i = 0; i < 5; i++) {
kafkaProducer.send(new ProducerRecord<>("first","msg2"+i));
}
//3、关闭资源
kafkaProducer.close();
}
}
幂等性:指多接口的多次调用所产生的结果和只调用一次是一致的,没有幂等性就会重复发送数据。具体来说,就是Producer无论向Broker发送多少次重复的数据,Broker端都只会持久化其中一条,保证不重复。
重复数据判断的标准:具有
注意:幂等性只能保证的是在单分区单会话内不重复。
开启参数enable.idempotence即可,默认为true。
1)Kafka事务原理
2)Kafka的事务相关的API:
// 1 初始化事务
void initTransactions();
// 2 开启事务
void beginTransaction() throws ProducerFencedException;
// 3 在事务内提交已经消费的偏移量(主要用于消费者)
void sendOffsetsToTransaction(Mapoffsets,String consumerGroupId) throws ProducerFencedException;
// 4 提交事务
void commitTransaction() throws ProducerFencedException;
// 5 放弃事务(类似于回滚事务的操作)
void abortTransaction() throws ProducerFencedException;
3)使用事务,保证数据的不重复。注意:必须指定事务的id,它被事务持久化分区所需要。
package com.jd.springboot_kafka.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;
public class CustomProducerTransaction {
public static void main(String[] args) {
//配置参数
Properties properties = new Properties();
properties.setProperty(ProducerConfig.BOOTSTRAP_SERVERS_CONFIG,"hadoop101:9092,hadoop102:9092");
properties.setProperty(ProducerConfig.KEY_SERIALIZER_CLASS_CONFIG, StringSerializer.class.getName());
properties.setProperty(ProducerConfig.VALUE_SERIALIZER_CLASS_CONFIG, StringSerializer.class.getName());
// 指定事务id
properties.put(ProducerConfig.TRANSACTIONAL_ID_CONFIG,"transaction_id_01");
//1、创建Kafka生产者对象
KafkaProducer<String, String> kafkaProducer = new KafkaProducer<String, String>(properties);
// 初始化事务
kafkaProducer.initTransactions();
// 开启事务
kafkaProducer.beginTransaction();
try {
//2、发送数据
for (int i = 0; i < 5; i++) {
kafkaProducer.send(new ProducerRecord<>("first","trans_msg"+i));
}
// 事务提交
kafkaProducer.commitTransaction();
} catch (Exception e) {
// 事务回滚
kafkaProducer.abortTransaction();
} finally {
//3、关闭资源
kafkaProducer.close();
}
}
}
首先,要求数据有序是指单分区的有序,多分区的有序需要等数据拉取完成后,在服务端全部重排序。
1)kafka在1.x版本之前保证数据单分区有序,条件如下:
max.in.flight.requests.per.connection=1(不需要考虑是否开启幂等性)。
2)kafka在1.x及以后版本保证数据单分区有序,条件如下:
(1)未开启幂等性:
max.in.flight.requests.per.connection=1
(2)开启幂等性
max.in.flight.requests.per.connection需要设置小于等于5。
原因说明:因为在kafka1.x以后,启用幂等后,kafka服务端会缓存producer发来的最近5个request的元数据,故无论如何,都可以保证最近5个request的数据都是有序的。