深入理解Kafka系列(二)--Kafka生产者

系列文章目录

Kakfa权威指南系列文章

前言

本系列是我通读《Kafka权威指南》这本书做的笔录和思考。

正文

Kafka生产者

Kafka发送消息的主要步骤

首先放图:向Kafka发送消息的主要步骤
在这里插入图片描述
用文字描述:

  1. 创建一个ProducerRecord对象,该对象包含主题、发送的内容等属性。
  2. 指定键和分区,用于把ProducerRecord对象发送到指定的分区。并且发送对象的时候,生产者需要把键和值序列化成字节数组(需要我们设定序列化器),这样,他们才能够在网络上传输。
  3. 数据发送给分区器,决定消息发送到哪个主题和分区上。紧接着,这条记录被添加到一个记录批次里面(RecordAccumulator,消息累加器)。
  4. 消息到达一定程度后,会有一个独立的线程(Sender)线程将同一个批次的消息全部发送到对应的broker上。
  5. 服务器收到消息后,返回一个响应

1.若成功:
返回一个RecordMetaData对象,包含了主题、分区、偏移量。
2.若失败:
返回一个错误,生产者收到错误后,会尝试重新发送消息,在一定次数后若还是失败,返回错误信息。


创建Kafka生产者(API)

这里记得提前把kafka开起来,安装步骤在上一篇博客中:
深入理解Kafka系列(一)–初识kafka

pom包依赖:

<dependency>
    <groupId>org.apache.kafka</groupId>
    <artifactId>kafka-clients</artifactId>
    <version>0.11.0.0</version>
</dependency>
<dependency>
    <groupId>org.apache.kafka</groupId>
    <artifactId>kafka_2.12</artifactId>
    <version>0.11.0.0</version>
</dependency>

简单的demo:

import org.apache.kafka.clients.producer.KafkaProducer;
import org.apache.kafka.clients.producer.ProducerRecord;

import java.util.Properties;

public class Test {
    
    
    public static void main(String[] args) {
    
    
        Properties properties = new Properties();
        properties.put("bootstrap.servers", "192.168.237.130:9092");
        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 = 0; i < 3; i++) {
    
    
            producer.send(new ProducerRecord<String, String>("test", Integer.toString(i), "message" + i));
        }
        producer.close();
    }
}

执行后会发现终端上:
在这里插入图片描述
验证:
拷贝一个会话,输入命令:

./bin/kafka-console-consumer.sh --zookeeper 192.168.237.130:2181 --from-beginning --topic test

可见消息成功写入。
在这里插入图片描述

扫描二维码关注公众号,回复: 12238548 查看本文章

接下来就是对生产者API的细节介绍了:

Kafka生产者参数详解

以上的demo是最基本的一个kafka生产者。大家可以观察到,我只设置了3个属性,而这3个属性是必须要设置的。

  1. bootstrap.servers

该属性指定broker的地址清单,地址的格式为host:port。
如果kafka是集群,不必把所有的broker都写上去,因为生产者会从给定的broker里查找到其他broker的信息,但是注意:建议至少提供2个broker节点,以防宕机引发其他问题。

  1. key.serializer

broker希望接收到的消息的键值对都是字节数组,而key.serializer
必须被设置为一个实现了org.apache.kafka.common.serialization.StringSerializer接口的类,而生产者会使用这个类把键对象序列化成字节数组,以用于网络传输。

  1. value.serializer

和key.serializer一样,必须设置。

  1. acks

acks参数指定了必须要有多少个分区副本收到消息,生产者才会认为消息写入是成功的。(这个参数非常重要,对消息丢失的可能性影响很大)
(一)acks=0:
1.生产者在成功写入消息之前不会等待任何来自服务器的响应。
2.即:若发送消息出了问题,生产者是不会知道的,消息也就丢失了。
3.也因为第二条原因,生产者不需要等待服务器的响应,因此这种模式可以支持最大速度去发送消息,吞吐量高。
(二)acks=1
1.只要集群的主节点收到消息,生产者就会收到一个响应。
2.若消息没有发送到主节点或者主节点宕机了,一个没收到消息的从节点成为了主节点。都会发生消息的丢失。
(三)acks=all
1.当所有参与复制的节点全部收到消息的时候,生产者才会收到来自服务器的成功响应。这种模式是最安全的,但是他的吞吐量最低,延迟也最高。

  1. buffer.memory

1.用来设置生产者内存缓冲区的大小。
2.因为kafka的消息是先放到生产者的缓冲区的,如果缓冲区满了,再启一个独立Sender线程,去把缓冲区的消息发送到服务器。

  1. compression.type

1.默认情况下,消息发送的时候是不会被压缩的。这个参数也就是设置kafka消息的压缩格式。
2.压缩格式支持:snappy,gzip,lz4.

  1. retries

该参数的值决定了生产者可以重复发送消息的次数,如果达到这个次数,生产者就会放弃重试并返回错误。

  1. batch.size

1.当多个消息需要被发送到一个分区的时候,生产者会把他们放到同一个批次里面。
2.而这个参数决定了一个批次可以使用的内存大小,按照字节数来计算。

  1. linger.ms

1.该参数指定了生产者在发送批次之前等待更多消息加入批次的时间。
2.kafkaProducer会在批次填满或者该参数达到上限时把批次发送出去。

  1. client.id

该参数可以试试任意的字符串,服务器会用他来识别消息的来源。

  1. max.in.flight.requests.per.connection

1.该参数决定了生产者在收到服务器响应前可以发送多少个消息。
2.值越高,占用的内存越高,但是吞吐量越高。

  1. max.block.ms

1.该参数指定了在调用send()方法的时候,获取元数据时,生产者的堵塞时间。
2.何时发生堵塞?当生产者的发送缓冲区已经满了,或者没有可用的元数据,这个send方法就会堵塞。
3.超过堵塞时间,抛异常。

  1. max.request.size

该参数用于控制生产者发送消息的请求大小

  1. receive.buffer.bytes和send.buffer.bytes

这两个参数分别指定了TCP socket接收和发送数据包的缓冲区大小。


Kafka生产者发送方式详解

kafka发送消息的方式主要有3种

  1. 发送并忘记(fire-and-forget)
  2. 同步发送
  3. 异步发送

1.最基础的发送方式:

KafkaProducer<String, String> producer = new KafkaProducer<>(properties);
// 构造格式: topic,key,value
ProducerRecord<String, String> record = new ProducerRecord<>("test", Integer.toString(4), "message" + 4);
try {
    
    
    producer.send(record);
} catch (Exception e) {
    
    
    e.printStackTrace();
} finally {
    
    
    producer.close();
}

2.同步发送消息:

KafkaProducer<String, String> producer = new KafkaProducer<>(properties);
ProducerRecord<String, String> record = new ProducerRecord<>("test", Integer.toString(4), "message" + 4);
try {
    
    
	// 我们调用send方法返回的是一个Future对象。然后调用get方法等待kafka响应
	// 在调用Future的get()方法,若写入成功,则返回一个正确的相应RecordMetadata。
    RecordMetadata recordMetadata = producer.send(record).get();
    System.out.println(recordMetadata.offset());
} catch (Exception e) {
    
    
    e.printStackTrace();
} finally {
    
    
    producer.close();
}

结果:
在这里插入图片描述

3.异步发送消息:
自定义一个回调类(注意实现类的包):

import org.apache.kafka.clients.producer.Callback;
import org.apache.kafka.clients.producer.RecordMetadata;

public class DemoProducerCallBack implements Callback {
    
    
    @Override
    public void onCompletion(RecordMetadata recordMetadata, Exception e) {
    
    
    	// 意思是不报错的话,我们会返回消息。
        if (e == null) {
    
    
            System.out.println("发送成功!");
        }
    }
}
KafkaProducer<String, String> producer = new KafkaProducer<>(properties);
ProducerRecord<String, String> record = new ProducerRecord<>("test", Integer.toString(5), "message" + 5);
try {
    
    
	// 直接将我们自定义的回调类放入send方法中即可
    producer.send(record, new DemoProducerCallBack());
} catch (Exception e) {
    
    
    e.printStackTrace();
} finally {
    
    
    producer.close();
}

结果:
在这里插入图片描述


序列化器

首先我们应该明白一点,序列化器是干啥用的?
说白了,就是为了让生产者端产生的消息,能够顺利的在网络上传输。
那如果说,我们要把一个对象作为消息,进行发送,无疑,我们一定要自定义一个序列化器,否则会报错。

自定义序列化器Demo

假设我们要把Customer对象作为消息进行传输。
Customer类:

public class Customer {
    
    
    private int customerId;
    private String customerName;

    public Customer(int customerId, String customerName) {
    
    
        this.customerId = customerId;
        this.customerName = customerName;
    }

    public int getCustomerId() {
    
    
        return customerId;
    }

    public void setCustomerId(int customerId) {
    
    
        this.customerId = customerId;
    }

    public String getCustomerName() {
    
    
        return customerName;
    }

    public void setCustomerName(String customerName) {
    
    
        this.customerName = customerName;
    }
}

自定义序列化器(注意包不要导错了):
CustomerSerializer

import org.apache.kafka.common.errors.SerializationException;
import org.apache.kafka.common.serialization.Serializer;

import java.nio.ByteBuffer;
import java.util.Map;

public class CustomerSerializer implements Serializer<Customer> {
    
    
    @Override
    public void configure(Map<String, ?> map, boolean b) {
    
    
        // 不做任何事
    }

    @Override
    public byte[] serialize(String topic, Customer data) {
    
    
        try {
    
    
            byte[] serializedName;
            int stringSize;
            if (data == null) {
    
    
                return null;
            } else {
    
    
                if (data.getCustomerName() != null) {
    
    
                    serializedName = data.getCustomerName().getBytes("UTF-8");
                    stringSize = serializedName.length;
                } else {
    
    
                    serializedName = new byte[0];
                    stringSize = 0;
                }
            }
            ByteBuffer buffer = ByteBuffer.allocate(4 + 4 + stringSize);
            buffer.putInt(data.getCustomerId());
            buffer.putInt(stringSize);
            buffer.put(serializedName);
            return buffer.array();
        } catch (Exception e) {
    
    
            throw new SerializationException("Error!!!!");
        }
    }

    @Override
    public void close() {
    
    

    }
}

测试类:(注意序列化器已经改变了)

import org.apache.kafka.clients.producer.KafkaProducer;
import org.apache.kafka.clients.producer.ProducerRecord;
import java.util.Properties;

public class Test2 {
    
    
    public static void main(String[] args) {
    
    
        Properties properties = new Properties();
        // 这里的value,填你的自定义序列化器的引用地址。
        properties.put("bootstrap.servers", "192.168.237.130:9092");
        properties.put("key.serializer", "kafka.CustomerSerializer");
        properties.put("value.serializer", "kafka.CustomerSerializer");
        KafkaProducer<String, Customer> producer = new KafkaProducer<>(properties);
        Customer customer = new Customer(1, "hello");
        ProducerRecord record = new ProducerRecord("test", customer);
        try {
    
    
            producer.send(record);
        } catch (Exception e) {
    
    
            e.printStackTrace();
        } finally {
    
    
            producer.close();
        }
    }
}

结果:
在这里插入图片描述
这里为了作对比,再创建个Customer类,名叫Customer2(复制即可)
如果代码改成:
在这里插入图片描述
看看结果是什么?
在这里插入图片描述
为什么?因为我们自定义的序列化器这里写着:
在这里插入图片描述
要一一对应!

使用自定义序列化器的缺点:

  1. 以上demo是一个简单的入门,那问题来了,如果现实项目中,如果存在多种类型的对象,那如果有100个对象都要作为消息传输,那我们是不是也要创建100个自定义的序列化器呢?
  2. 因此,不建议使用自定义序列化器,最好使用一些序列化器的框架,如:JSON,Avro,Protobuf等等。

总结

本文大概从这么几个方面进行概述:
1.Kafka生产者的大致流程。
2.Kafka生产者API的相关参数和发送消息的方式。
3.Kafka-序列化器。
下一篇文章会根据Kafka的消费者以及API层面去详细的介绍。

猜你喜欢

转载自blog.csdn.net/Zong_0915/article/details/109357082