Kafka Producer

						为了备战四级,这段时间就没有写博客,今天考完,废话不多说,来一篇

在这里插入图片描述
下图为Kafka发送消息的主要步骤:在这里插入图片描述

步骤1:

我们首先创建一个ProducerRecord对象,从上图可以看出,里面包含着发送的目标主题,分区,键,值,
Partition和key可以不指定,但是Topic和Value必须指定。在发送ProducerRecord对象时,生产者先把键和值对象序列化成字节数组,便于之后的网络传输的进行。

步骤2:

接下来通过序列化器,把键和值序列化成字节数组,数据传给分区器,但是如果之前在ProducerRecord对象中指定了Partition,那么分区器就不会做任何事情。如果没有指定分区,那么分区器会根据Key的对分区哈希映射来选择一个分区。之后,生产者就可以把数据发送到目标的主题和分区,这些数据都会被添加到一个批次里,这个批次的所有消息都会被发到相同的主题和分区。有一个独立的线程负责把这些记录批次发送到相应的broker上。

服务器在收到这些消息时会返回一个响应。如果消息成功写入Kafka,就返回一个RecordMetaData对象,它包含了主题和分区信息,以及记录在分区里的便宜量。如果写入失败,则会返回一个错误。生产者在收到错误之后会尝试重新发送消息,几次之后如果还是失败,就返回错误信息。

下面我们来创建Kafka生产者:
首先我们在package org.apache.kafka.clients.producer包下找到KafkaProducer有一个构造方法

/**
     * A producer is instantiated by providing a set of key-value pairs as configuration. Valid configuration strings
     * are documented <a href="http://kafka.apache.org/documentation.html#producerconfigs">here</a>.
     * @param properties   The producer configs
     */
    public KafkaProducer(Properties properties) {
        this(new ProducerConfig(properties), null, null);
    }

我们在写自己的逻辑代码的时候给KafkaProducer构造器传入一个Properties,所以我们来创建,properties配置Kafka生产者的一些属性,看如下代码:

		//创建Kafka生产者的配置信息
        Properties properties = new Properties();

        //指定连接的Kafka集群
        properties.put(ProducerConfig.BOOTSTRAP_SERVERS_CONFIG, "hadoop108:9092");

        //ACK应答级别
        properties.put(ProducerConfig.ACKS_CONFIG, "all");

        //重试次数
        properties.put("retries", 1);

        //批次大小 单位:字节
        properties.put("batch.size", 16384);

        //等待时间
        properties.put("linger.ms", 1);

        //RecordAccumulator缓冲区大小
        properties.put("buffer.memory", 33554432); //32M

        //Key Value的序列化类
        properties.put("key.serializer", "org.apache.kafka.common.serialization.StringSerializer");

        properties.put("value.serializer", "org.apache.kafka.common.serialization.StringSerializer");

我在可以在KafkaProducerConfig.java文件里找到一些属性的键,值我们自己来指定。
但是要往kafka里面写入消息,首先要创建一个生产者对象,并设置一些属性。Kafka生产者有3个必选的属性。

bootstrap.servers
该属性指定broker的地址清单,地址的格式为host:port。清单里不需要包含所有的broker地址,生产者会从给定的broker里查找到其他broker的信息。不过建议至少要提供两个broker的信息,一旦其中一个宕机,生产者仍然能够连接到集群上。
key.serializer
broker希望接收到的消息的键和值都是字节数组。生产者接口允许使用参数化类型,因此可以把Java对象作为键和值发送给broker。这样的代码具有良好的可读性,不过生产者需要知道如何把这些Java对象转换成字节数组。key.serializer必须被设置为一个实现了org.apache.kafka.common.serialization.Serializer 接口的类,生产者会使用这个类把键对象序列化成字节数组。Kafka 客户端默认提供了ByteArraySerializer(这个只做很少的事情)、StringSerializer和IntegerSerializer,因此,如果你只使用常见的几种Java对象类型,那么就没必要实现自己的序列化器。要注意,key.serializer是必须设置的,就算你打算只发送值内容。
value.serializer
与key.serializer一样,value.serializer指定的类会将值序列化。如果键和值都是字符串,可以使用与key.serializer一样的序列化器。如果键是整数类型而值是字符串,那么需要使用不同的序列化器。

下面我们来看看完整的生产者代码:

import org.apache.kafka.clients.producer.*;

import java.util.Properties;
import java.util.concurrent.ExecutionException;

public class MyProducer {

    public static void main(String[] args) {

        //创建Kafka生产者的配置信息
        Properties properties = new Properties();

        //指定连接的Kafka集群
        properties.put(ProducerConfig.BOOTSTRAP_SERVERS_CONFIG, "hadoop108:9092");

        //ACK应答级别
        properties.put(ProducerConfig.ACKS_CONFIG, "all");

        //重试次数
        properties.put("retries", 1);

        //批次大小 单位:字节
        properties.put("batch.size", 16384);

        //等待时间
        properties.put("linger.ms", 1);

        //RecordAccumulator缓冲区大小
        properties.put("buffer.memory", 33554432); //32M

        //Key Value的序列化类
        properties.put("key.serializer", "org.apache.kafka.common.serialization.StringSerializer");

        properties.put("value.serializer", "org.apache.kafka.common.serialization.StringSerializer");

        //创建生产者对象
        KafkaProducer<String, String> producer = new KafkaProducer<>(properties);

        //发送数据
        for(int i = 1 ; i < 5 ; i++){
            try {
                producer.send(new ProducerRecord<String,String>("first","ly",""+i));
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
        //关闭资源
        producer.close();
        }
 }      

下面我们启动hadoop108的zookeeper和kafka服务
开启消费者进程,同时执行上述的程序代码:
在这里插入图片描述
自定义分区器:

Kafka的消息是一个个键值对,ProducerRecord对象可以只包含目标主题和值,键的默认值为null,不过大多数应用程序会用到键。键有两个用途:可以作为消息的附加消息,也可以用来决定消息该被写到哪个主题的分区。拥有相同键的消息将被写到同一个分区。

比如:

ProducerRecord<String,String> record = new ProducerRecord<("CustomerCountry","Laboratory","USA");

如果我们创建键为null的消息,不指定键就可以了
ProducerRecord<String,String> record = new ProducerRecord<("CustomerCountry","USA");

下面为上面两种方式对应的源码

/**
     * Create a record to be sent to Kafka
     * 
     * @param topic The topic the record will be appended to
     * @param key The key that will be included in the record
     * @param value The record contents
     */
    public ProducerRecord(String topic, K key, V value) {
        this(topic, null, null, key, value, null);
    }
    
    /**
     * Create a record with no key
     * 
     * @param topic The topic this record should be sent to
     * @param value The record contents
     */
    public ProducerRecord(String topic, V value) {
        this(topic, null, null, null, value, null);
    }

分区源码剖析:
如果key为null,则按照一种轮询的方式来计算分区分配:第一次调用时,获得一个数,将他存入topicCounterMap中,下次调用时,取出该数,并counter.getAndIncrement(),这样每次获得的数都不一样。

如果key不为null,则使用Hash算法:murmur的hash算法:非加密型Hash函数,具备高运算性能及低碰撞率,减少重复碰撞 来计算分区分配;key的hash值取模,除以分区数量,取余数

public class DefaultPartitioner implements Partitioner {

    private final ConcurrentMap<String, AtomicInteger> topicCounterMap = new ConcurrentHashMap<>();

    public void configure(Map<String, ?> configs) {}

    /**
     * Compute the partition for the given record.
     *
     * @param topic The topic name
     * @param key The key to partition on (or null if no key)
     * @param keyBytes serialized key to partition on (or null if no key)
     * @param value The value to partition on or null
     * @param valueBytes serialized value to partition on or null
     * @param cluster The current cluster metadata
     */
    public int partition(String topic, Object key, byte[] keyBytes, Object value, byte[] valueBytes, Cluster cluster) {
        List<PartitionInfo> partitions = cluster.partitionsForTopic(topic);
        int numPartitions = partitions.size();
        if (keyBytes == null) {
            int nextValue = nextValue(topic);
            List<PartitionInfo> availablePartitions = cluster.availablePartitionsForTopic(topic);
            if (availablePartitions.size() > 0) {
                int part = Utils.toPositive(nextValue) % availablePartitions.size();
                return availablePartitions.get(part).partition();
            } else {
                // no partitions are available, give a non-available partition
                return Utils.toPositive(nextValue) % numPartitions;
            }
        } else {
            // hash the keyBytes to choose a partition
            return Utils.toPositive(Utils.murmur2(keyBytes)) % numPartitions;
        }
    }

    private int nextValue(String topic) {
        AtomicInteger counter = topicCounterMap.get(topic);
        if (null == counter) {
            counter = new AtomicInteger(ThreadLocalRandom.current().nextInt());
            AtomicInteger currentCounter = topicCounterMap.putIfAbsent(topic, counter);
            if (currentCounter != null) {
                counter = currentCounter;
            }
        }
        return counter.getAndIncrement();
    }

    public void close() {}

}

只有在不改变主题分区数量的情况下,键对分区的哈希映射保持不变。但是如果使用键来映射分区,那么最好在创建主题的时候把分区规划好,不要增加新分区
下面来实现自定义分区器:
首先我们要实现自定义分区器就要实现Partitioner接口,接口中会有三个方法,模拟默认分区器的代码,我们只需要在partition方法中编写自己的逻辑代码就行了,这里我们直接简单的返回一个分区号(不管key的值是什么,都会返回1号分区)

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) {
        return 1;
    }

    @Override
    public void close() {

    }

    @Override
    public void configure(Map<String, ?> map) {

    }
}

然后在程序中要指定分区器:

properties.put(ProducerConfig.PARTITIONER_CLASS_CONFIG,"com.ly.partitioner.MyPartitioner");
     					好了,生产者的部分知识就介绍完毕了!喜欢的点个👍!

在这里插入图片描述

  • 4
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 3
    评论
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值