Kafka生产者API

这里写目录标题

  • 添加依赖
  • 最基础生产消息代码
    • 消费者消费消息![在这里插入图片描述](https://img-blog.csdnimg.cn/2db8539e99294b6898eaa4c3c1dbc684.png)
  • 带回调函数的异步发送
  • 同步发送
  • 生产者发送消息的分区策略
    • 分区规则
  • 指定分区发送消息
  • 不指定分区,根据key的hash值发送消息
  • 当没有分区和key值时
  • 自定义分区器
    • 需求
    • 实现步骤
    • 如何使用自定义的分区器
  • 提高生产者的吞吐量
  • 数据的可靠性
    • ack应答机制
  • 详解ack
    • 可靠性总结
    • 数据重复分析
      • 数据可靠性代码案例

添加依赖

   <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();
    }
}

消费者消费消息Kafka生产者API_第1张图片

带回调函数的异步发送

回调函数会在 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() {
    }

分区规则

Kafka生产者API_第2张图片

指定分区发送消息

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();
    }
}

控制台输出
Kafka生产者API_第3张图片
当没有指定分区时,会根据key的hash值和topic的patition取余计算出patition值

不指定分区,根据key的hash值发送消息

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();
    }
}

当没有分区和key值时

既没有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");

提高生产者的吞吐量

Kafka生产者API_第4张图片

  • RecordAccumulator:缓冲区大小,默认32M
  • sender线程会从缓冲区拉取数据,默认时batch.size达到16k或者linger.ms等待时间结束,默认是0ms。
  • compression.type:压缩
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:生产者发送过来的数据,不需要等数据保存的磁盘应答。
  • 1:生产者发送过来的数据,Leader收到数据后应答。
  • -1:生产者发送过来的数据,Leader和ISR队列(可用的副本和leader)里面
    的所有节点收齐数据后应答。

详解ack

ack=0
Kafka生产者API_第5张图片
生产者发送过来的数据,不需要等数据保存的磁盘应答,此时Leader挂掉,还没有和其他副本进行同步,就会出现消息丢失的情况。
ack=1
Kafka生产者API_第6张图片
生产者发送过来的数据,leader将数据保存到磁盘之后。进行应答,下面是可能会出现的问题
Kafka生产者API_第7张图片
问题分析: 当Leader接收到消息之后,把数据保存的磁盘,也成功进行了应答。然而还未进行Follower副本备份,此时Leader挂掉,会选举出新的Leader,但是原来的Leader已经进行了应答,生产者就会认为数据成功发送,因为新的Leader是原来的follower选举的,原来的follower并没有同步数据,所以消息会丢失。

ack=1
Kafka生产者API_第8张图片
思考:Leader收到数据,所有Follower都开始同步数据,但有一个Follower,因为某种故障,迟迟不能与Leader进行同步,那这个问题怎么解决呢?Kafka生产者API_第9张图片

可靠性总结

acks=0,生产者发送过来数据就不管了,可靠性差,效率高;
acks=1,生产者发送过来数据Leader应答,可靠性中等,效率中等;
acks=-1,生产者发送过来数据Leader和ISR队列里面所有Follwer应答,可靠性高,效率低;
在生产环境中,acks=0很少使用;acks=1,一般用于传输普通日志,允许丢个别数据;acks=-1,一般用于传输和钱相关的数据,对可靠性要求比较高的场景。

数据重复分析

Kafka生产者API_第10张图片
当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();
    }
}

你可能感兴趣的:(kafka,linq,分布式)