<dependency>
<groupId>org.apache.kafka</groupId>
<artifactId>kafka-clients</artifactId>
<version>3.0.0</version>
</dependency>
package com.itwlj.kafka.shangguigu.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) {
//创建kafka生产者配置对象
Properties properties = new Properties();
//给kafka配置对象添加配置信息 bootstrap.servers
properties.put(ProducerConfig.BOOTSTRAP_SERVERS_CONFIG, "localhost:9092");
//key value序列化(必须)
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");
//以上等同于StringSerializer.class.getName()
//properties.put(ProducerConfig.VALUE_SERIALIZER_CLASS_CONFIG, StringSerializer.class.getName());
//创建kafka生产对象
KafkaProducer<String, String> kafkaProducer = new KafkaProducer<>(properties);
//调用send方法发送消息
for (int i = 0; i < 5; i++) {
//kafkaProducer.send(new ProducerRecord<>("主题","内容"));
kafkaProducer.send(new ProducerRecord<>("first",i+""));
}
//关闭资源
kafkaProducer.close();
}
}
回调函数会在 producer 收到 ack 时调用,为异步调用,该方法有两个参数,分别是元
数据信息(RecordMetadata)和异常信息(Exception),如果 Exception 为 null,说明消息发
送成功,如果 Exception 不为 null,说明消息发送失败
package com.itwlj.kafka.shangguigu.kafka.producer;
import org.apache.kafka.clients.producer.*;
import java.util.Properties;
public class CustomProducerCallback {
public static void main(String[] args) {
//创建kafka生产者配置对象
Properties properties = new Properties();
//给kafka配置对象添加配置信息 bootstrap.servers
properties.put(ProducerConfig.BOOTSTRAP_SERVERS_CONFIG, "localhost:9092");
//key value序列化(必须)
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");
//以上等同于StringSerializer.class.getName()
//properties.put(ProducerConfig.VALUE_SERIALIZER_CLASS_CONFIG, StringSerializer.class.getName());
//创建kafka生产对象
KafkaProducer<String, String> kafkaProducer = new KafkaProducer<>(properties);
//调用send方法发送消息
for (int i = 0; i < 5; i++) {
//kafkaProducer.send(new ProducerRecord<>("主题","内容"));
kafkaProducer.send(new ProducerRecord<>("first", i + "a"), new Callback() {
// 该方法在 Producer 收到 ack 时调用,为异步调用
@Override
public void onCompletion(RecordMetadata metadata, Exception e) {
//e为null 没有异常
if (e ==null){
System.out.println("主题的名称 :"+metadata.topic()+"分区:"+ metadata.partition());
}else {
e.printStackTrace();
}
}
});
}
//关闭资源
kafkaProducer.close();
}
}
只需在异步发送的基础上,再调用一下 get()方法即可。
package com.itwlj.kafka.shangguigu.kafka.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;
import java.util.concurrent.ExecutionException;
/**
*@创建人 wlj
*@创建时间 2023/7/13
*@描述 同步发送消息
*/
public class CustomProducerSync {
public static void main(String[] args) throws ExecutionException, InterruptedException {
//创建kafka生产者配置对象
Properties properties = new Properties();
//给kafka配置对象添加配置信息 bootstrap.servers
properties.put(ProducerConfig.BOOTSTRAP_SERVERS_CONFIG, "localhost:9092");
//key value序列化(必须)
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");
//以上等同于StringSerializer.class.getName()
//properties.put(ProducerConfig.VALUE_SERIALIZER_CLASS_CONFIG, StringSerializer.class.getName());
//创建kafka生产对象
KafkaProducer<String, String> kafkaProducer = new KafkaProducer<>(properties);
//调用send方法发送消息
for (int i = 0; i < 5; i++) {
//kafkaProducer.send(new ProducerRecord<>("主题","内容"));
kafkaProducer.send(new ProducerRecord<>("first",i+"w")).get();
}
//关闭资源
kafkaProducer.close();
}
}
默认的分区器 DefaultPartitioner
package org.apache.kafka.clients.producer.internals;
import java.util.Map;
import org.apache.kafka.clients.producer.Partitioner;
import org.apache.kafka.common.Cluster;
import org.apache.kafka.common.utils.Utils;
public class DefaultPartitioner implements Partitioner {
private final StickyPartitionCache stickyPartitionCache = new StickyPartitionCache();
public DefaultPartitioner() {
}
package com.itwlj.kafka.shangguigu.kafka.producer;
import org.apache.kafka.clients.producer.*;
import java.util.Properties;
/**
*@创建人 wlj
*@创建时间 2023/7/13
*@描述 回调函数发送消息
*/
public class CustomProducerCallbackPartitioner {
public static void main(String[] args) {
//创建kafka生产者配置对象
Properties properties = new Properties();
//给kafka配置对象添加配置信息 bootstrap.servers
properties.put(ProducerConfig.BOOTSTRAP_SERVERS_CONFIG, "localhost:9092");
//key value序列化(必须)
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");
//以上等同于StringSerializer.class.getName()
//properties.put(ProducerConfig.VALUE_SERIALIZER_CLASS_CONFIG, StringSerializer.class.getName());
//创建kafka生产对象
KafkaProducer<String, String> kafkaProducer = new KafkaProducer<>(properties);
//调用send方法发送消息
for (int i = 0; i < 5; i++) {
//kafkaProducer.send(new ProducerRecord<>("主题",分区,key,"内容"));
kafkaProducer.send(new ProducerRecord<>("first", 0,"",i + "partition"), new Callback() {
// 该方法在 Producer 收到 ack 时调用,为异步调用
@Override
public void onCompletion(RecordMetadata metadata, Exception e) {
//e为null 没有异常
if (e ==null){
System.out.println("主题的名称 :"+metadata.topic()+"分区:"+ metadata.partition());
}else {
e.printStackTrace();
}
}
});
}
//关闭资源
kafkaProducer.close();
}
}
控制台输出
当没有指定分区时,会根据key的hash值和topic的patition取余计算出patition值
package com.itwlj.kafka.shangguigu.kafka.producer;
import org.apache.kafka.clients.producer.*;
import java.util.Properties;
/**
*@创建人 wlj
*@创建时间 2023/7/13
*@描述 回调函数发送消息
*/
public class CustomProducerCallbackKey {
public static void main(String[] args) {
//创建kafka生产者配置对象
Properties properties = new Properties();
//给kafka配置对象添加配置信息 bootstrap.servers
properties.put(ProducerConfig.BOOTSTRAP_SERVERS_CONFIG, "localhost:9092");
//key value序列化(必须)
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");
//以上等同于StringSerializer.class.getName()
//properties.put(ProducerConfig.VALUE_SERIALIZER_CLASS_CONFIG, StringSerializer.class.getName());
//创建kafka生产对象
KafkaProducer<String, String> kafkaProducer = new KafkaProducer<>(properties);
//调用send方法发送消息
String [] strings ={"a","b","c","e","n",};
for (int i = 0; i < 5; i++) {
//kafkaProducer.send(new ProducerRecord<>("主题",key,"内容"));
String value=strings[i];
kafkaProducer.send(new ProducerRecord<>("first", "",value), new Callback() {
// 该方法在 Producer 收到 ack 时调用,为异步调用
@Override
public void onCompletion(RecordMetadata metadata, Exception e) {
//e为null 没有异常
if (e ==null){
System.out.println("主题的名称 :"+metadata.topic()+ value +"分区:"+ metadata.partition());
}else {
e.printStackTrace();
}
}
});
}
//关闭资源
kafkaProducer.close();
}
}
既没有partition值又没有key值的情况下,Kafka采用Sticky Partition(黏性分区器),会随机选择一个分区,并尽可能一直使用该分区,待该分区的batch已满或者已完成,Kafka再随机一个分区进行使用(和上一次的分区不同)。
例如:第一次随机选择0号分区,等0号分区当前批次满了(默认16k)或者linger.ms设置的时间到, Kafka再随机一个分区进行使用(如果还是0会继续随机)。
如果研发人员可以根据企业需求,自己重新实现分区器。
例如我们实现一个分区器实现,发送过来的数据中如果包含 atguigu,就发往 0 号分区,
不包含 atguigu,就发往 1 号分区。
(1)定义类实现 Partitioner 接口。
(2)重写 partition()方法。
package com.itwlj.kafka.shangguigu.kafka.producer;
import org.apache.kafka.clients.producer.Partitioner;
import org.apache.kafka.common.Cluster;
import java.util.Map;
/**
*@创建人 wlj
*@创建时间 2023/7/14
*@描述 自定义分区器
* 实现Partitioner接口
* 实现3个方法 partition close configure
* 编写partition方法 返回分区号
*/
public class MyPartitioner implements Partitioner {
/**
*
* @param topic 主题
* @param key key
* @param bytes 序列化之后的key
* @param value value
* @param bytes1 序列化之后的value
* @param cluster 集群元数据可以查看分区信息
* @return
*/
@Override
public int partition(String topic, Object key, byte[] bytes, Object value, byte[] bytes1, Cluster cluster) {
// 获取消息
String msgValue = value.toString();
// 创建 partition
int partition;
// 判断消息是否包含 atguigu
if (msgValue.contains("atguigu")){
partition = 0;
}else {
partition = 1;
}
// 返回分区号
return partition;
}
@Override
public void close() {
}
@Override
public void configure(Map<String, ?> map) {
}
}
在kafka生产者配置对象中添加自定义分区器
//自定义分区器
properties.put(ProducerConfig.PARTITIONER_CLASS_CONFIG,"com.itwlj.kafka.shangguigu.kafka.producer.MyPartitioner");
package com.itwlj.kafka.shangguigu.kafka.producer;
import org.apache.kafka.clients.producer.*;
import org.apache.kafka.common.serialization.StringSerializer;
import java.util.Properties;
/**
*@创建人 wlj
*@创建时间 2023/7/14
*@描述 提高生产者吞吐量
* 设置缓冲区大小 64M
* 设置batch大小 32K
* 设置等待时间 1MS
* 设置压缩格式 snappy
*/
public class CustomProducerParameters {
public static void main(String[] args) {
//创建kafka生产者配置对象
Properties properties = new Properties();
//给kafka配置对象添加配置信息 bootstrap.servers
properties.put(ProducerConfig.BOOTSTRAP_SERVERS_CONFIG, "localhost:9092");
//key value序列化(必须)
properties.put(ProducerConfig.KEY_SERIALIZER_CLASS_CONFIG, StringSerializer.class.getName());
properties.put(ProducerConfig.VALUE_SERIALIZER_CLASS_CONFIG,StringSerializer.class.getName());
//设置缓冲区大小 32M
properties.put(ProducerConfig.BUFFER_MEMORY_CONFIG,33554432);
//设置batch大小 16K
properties.put(ProducerConfig.BATCH_SIZE_CONFIG, 16384);
//设置等待时间 1MS
properties.put(ProducerConfig.LINGER_MS_CONFIG,1);
//设置压缩类型
properties.put(ProducerConfig.COMPRESSION_TYPE_CONFIG,"snappy");
//创建kafka生产对象
KafkaProducer<String, String> kafkaProducer = new KafkaProducer<>(properties);
//调用send方法发送消息
for (int i = 0; i < 5; i++) {
//kafkaProducer.send(new ProducerRecord<>("主题","内容"));
kafkaProducer.send(new ProducerRecord<>("first", "123"), new Callback() {
// 该方法在 Producer 收到 ack 时调用,为异步调用
@Override
public void onCompletion(RecordMetadata metadata, Exception e) {
//e为null 没有异常
if (e ==null){
System.out.println("主题的名称 :"+metadata.topic()+"分区:"+ metadata.partition());
}else {
e.printStackTrace();
}
}
});
}
//关闭资源
kafkaProducer.close();
}
}
kafka集群收到消息之后,会对消息进行保存。
ack=0
生产者发送过来的数据,不需要等数据保存的磁盘应答,此时Leader挂掉,还没有和其他副本进行同步,就会出现消息丢失的情况。
ack=1
生产者发送过来的数据,leader将数据保存到磁盘之后。进行应答,下面是可能会出现的问题
问题分析: 当Leader接收到消息之后,把数据保存的磁盘,也成功进行了应答。然而还未进行Follower副本备份,此时Leader挂掉,会选举出新的Leader,但是原来的Leader已经进行了应答,生产者就会认为数据成功发送,因为新的Leader是原来的follower选举的,原来的follower并没有同步数据,所以消息会丢失。
ack=1
思考:Leader收到数据,所有Follower都开始同步数据,但有一个Follower,因为某种故障,迟迟不能与Leader进行同步,那这个问题怎么解决呢?
acks=0,生产者发送过来数据就不管了,可靠性差,效率高;
acks=1,生产者发送过来数据Leader应答,可靠性中等,效率中等;
acks=-1,生产者发送过来数据Leader和ISR队列里面所有Follwer应答,可靠性高,效率低;
在生产环境中,acks=0很少使用;acks=1,一般用于传输普通日志,允许丢个别数据;acks=-1,一般用于传输和钱相关的数据,对可靠性要求比较高的场景。
当Leader,收到数据之后,所有节点都同步完成,但是应答时失败,生产者没有得到应带,就会重复发送消息,因为原来的follower已经进行过消息同步,成为新的leader之后,又会重新接收到数据,所以会导致消息的重复。Kafka提供了幂等性来保证消息不会重复发送。
package com.itwlj.kafka.shangguigu.kafka.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;
/**
*
*/
/**
*@创建人 wlj
*@创建时间 2023/7/14
*@描述 ack应答机制
*/
public class CustomProducerAcks {
public static void main(String[] args) {
//创建kafka生产者配置对象
Properties properties = new Properties();
//给kafka配置对象添加配置信息 bootstrap.servers
properties.put(ProducerConfig.BOOTSTRAP_SERVERS_CONFIG, "localhost:9092");
//key value序列化(必须)
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");
//设置ack应答机制
properties.put(ProducerConfig.ACKS_CONFIG, "all");
//设置重试次数
properties.put(ProducerConfig.RETRIES_CONFIG, 3);
//创建kafka生产对象
KafkaProducer<String, String> kafkaProducer = new KafkaProducer<>(properties);
//调用send方法发送消息
for (int i = 0; i < 5; i++) {
//kafkaProducer.send(new ProducerRecord<>("主题","内容"));
kafkaProducer.send(new ProducerRecord<>("first", i + "all"));
}
//关闭资源
kafkaProducer.close();
}
}