Kafka——Kafka生产者(向Kafka写入数据)

在这一章,我们将从Kafra 生产者的设计和组件讲起,学习如何使用Kafka 生产者。我们将情示如何创建KafkaProducer 和ProducerRecords 对象、如何将记录发送给Kafka ,以及如何处理从Kafka 返回的错误,然后介绍用干控制生产者行为的重要配置选项,最后深入探讨如何使用不同的分区方棒和序列化器,以及如何自定义序列化器和分区器。

生产者概览

一个应用程序在很多情况下需要往Kafka 写入消息: 记录用户的活动(用于审计和分析)、记录度量指标、保存日志消息、记录智能家电的信息、与其他应用程序进行异步通信、缓冲即将写入到数据库的数据,等等。
多样的使用场景意味着多样的需求:是否每个消息都很重要?是否允许丢失一小部分消息?偶尔出现重复消息是否可以接受?是否有严格的延迟和吞吐量要求?

在之前提到的信用卡事务处理系统里,消息丢失或消息重复是不允许的,可以接受的延迟最大为500ms ,对吞吐量要求较高我们希望每秒钟可以处理一百万个消息。
保存网站的点击信息是另一种使用场景。在这个场景里,允许丢失少量的消息或出现少量的消息重复,延迟可以高一些,只要不影响用户体验就行。换句话说,只要用户点击链接后可以马上加载页面,那么我们并不介意消息要在几秒钟之后才能到达Kafka 服务器。吞吐量则取决于网站用户使用网站的频度。
不同的使用场景对生产者API 的使用和配置会有直接的影响。

尽管生产者API 使用起来很简单, 但消息的发送过程还是有点复杂的。下图展示了向Kafka 发送消息的主要步骤。
在这里插入图片描述
我们从创建一个ProducerRecord 对象开始, Producer Record 对象需要包含目标主题和要发送的内容。我们还可以指定键或分区
在发送ProducerRecord 对象时,生产者要先把键和值对象序列化成字节数组,这样它们才能够在网络上传输
接下来,数据被传给分区器。如果之前在ProducerRecord 对象里指定了分区,那么分区器就不会再做任何事情,直接把指定的分区返回。如果没有指定分区,那么分区器会根据ProducerRecord 对象的键来选择一个分区。选好分区以后,生产者就知道该往哪个主题和
分区发送这条记录了。紧接着,这条记录被添加到一个记录批次里,这个批次里的所有消息会被发送到相同的主题和分区上。有一个独立的线程负责把这些记录批次发送到相应的broker 上。
服务器在收到这些消息时会返回一个响应。如果消息成功写入 Kafka ,就返回一个RecordMetaData 对象,它包含了主题和分区信息,以及记录在分区里的偏移量。如果写入失败, 则会返回一个错误。生产者在收到错误之后会尝试重新发送消息,几次之后如果还
是失败,就返回错误信息

创建Kafka生产者

要往Kafka 写入消息,首先要创建一个生产者对象,井设置一些属性。Kafka 生产者有3个必选的属性。

  • bootstrap.servers
    该属性指定broker 的地址清单,地址的格式为host:port。清单里不需要包含所有broker,生产者会从给定的broker里找到其他的broker,但是,建议至少两个,其中一个宕机,使得生产者依然能够连接到集群上。
  • key.serializer
    broker 希望接收到的消息的键和值都是字节数组。生产者接口允许使用参数化类型,因此可以把Java 对象作为键和值发送给broker。

这样的代码具有良好的可读性,不过生产者需要知道如何把这些Java 对象转换成字节数组。key. serializer必须被设置为一个实现了org.apache.kafka.common.serialization.Serializer接口的类,生产者会使用这个类把键对象序列化成字节数组。
Kafka 客户端默认提供了ByteArraySerializer(这个只做很少的事情)、StringSerializer和IntegerSerializer,因此,如果你只使用常见的几种Java 对象类型,那么就没必要实现自己的序列化器。

  • value.serializer
    与前一个差不多。

下面的代码片段慎示了如何创建一个新的生产者,这里只指定了必要的属性,其他使用默认设置。

	private KafkaProducer producer;
    private Properties kafkaProps=new Properties();
    
    kafkaProps.put("bootstrap.servers","broker1:9092,broker2:9092");
    kafkaProps.put("key.serializer",
                "org.apache.kafka.common.serialization.StringSerializer");
    kafkaProps.put("value.serializer",
                "org.apache.kafka.common.serialization.StringSerializer");
    producer=new KafkaProducer<String ,String>(kafkaProps);

这个接口很简单,通过配置生产者的不同属性就可以很大程度地控制值它的行为。另外也可以通过Properties进行设置哦。

实例化生产者对象后,接下来就可以开始发送消息了。发送消息主要有以下3 种方式。

  • 发送并忘记( fire- and-forget )
    我们把消息发送给服务器,但井不关心它是否正常到达。大多数情况下,消息会正常到达,因为Kafka 是高可用的,而且生产者会自动尝试重发。不过,使用这种方式有时候也会丢失一些消息。
  • 同步发送
    我们使用send()方怯发送消息, 它会返回一个Future对象,调用get() 方法进行等待,就可以知道消息是否发送成功。
  • 异步发送
    我们调用send()方怯,并指定一个回调函数, 服务器在返回响应时调用该函数。

本章的所有例子都使用单线程,但其实生产者是可以使用多线程来发送消息的。刚开始的时候可以使用单个消费者和单个线程。如果需要更高的吞吐量,可以在生产者数量不变的前提下增加线程数量。如果这样做还不够, 可以增加生产者数量。

发送消息到kafka

最简单的消息发送如下所示

package com.promusician.kafka;

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

import java.util.Properties;

public class KafkaMessageProducer {
    private final KafkaProducer<String,String> producer;
    private Properties kafkaProps=new Properties();
    public KafkaMessageProducer(){
        kafkaProps.put("bootstrap.servers","broker1:9092,broker2:9092");
        kafkaProps.put("key.serializer",
                "org.apache.kafka.common.serialization.StringSerializer");
        kafkaProps.put("value.serializer",
                "org.apache.kafka.common.serialization.StringSerializer");
        producer=new KafkaProducer<>(kafkaProps);
    }

    public void send() throws Exception{
        ProducerRecord<String,String> record= new ProducerRecord<String, String>
                ("CustomerCountry","Precision Prodcts",
                        "France");

        producer.send(record);
    }
}

生产者的send()方法将ProducerRecord对象作为参数,所以我们要先创建一个ProducerRecord对象。上面三个参数分别是主题topic,键,值。send方法可以在生产者架构图里看出,消息被放入缓冲区,然后以单独的线程发送到服务器端。send会返回一个包含RecordMetadata的Future对象,不过因为我们忽略返回值,所以无法得知消息是否成功。如果不要求发送结果,可以使用这种方式。
我们可以忽略传递消息时的错误或者服务器的错误,但是在发送消息的时候,生产者还是有可能发生其他异常,比如序列化失败SerializaitonException、缓冲区已满BufferExhaustedException或者TimeoutException、又或者是线程中断InterruptException。

同步发送消息

 public void send()throws Exception{
        ProducerRecord<String,String> record= new ProducerRecord<String, String>
                ("CustomerCountry","Precision Prodcts",
                        "France");

        producer.send(record).get();
    }

在这里producer.send()方法先返回一个Future对象,然后调用Future对象的get()方法等待Kafka的相应,如果服务器返回错误,get方法会抛出异常,否则会得到一个RecordMetadata对象,可以用它来获取消息的偏移量。
当然消息发送也可能发生错误,比如broker反悔了一个不允许重发消息的异常或者已经超过了重发次数的异常,我们这里简单使用throws Exception抛出,没做其他处理。

KafkaProducer一般会发生两类错误。其中一类是可重试错误,这类错误可以通过重发消息来解决。比如对于连接错误,可以通过再次建立连接来解决,“无主( no leader )” 错误则可以通过重新为分区选举首领来解决。KafkaProducer可以被配置成自动重试,如果在多次重试后仍无能解决问题,应用程序会收到一个重试异常。另一类错误无出通过重试解决,比如“消息太大”异常。对于这类错误, KafkaProducer不会进行任何重试,直接抛出异常。

异步发送消息
为了在异步发送消息的同时能够对异常情况进行处理,生产者提供了回调支持。下面是使用回调的一个例子。

 private class MessageProducerCallBack implements Callback{
        @Override
        public void onCompletion(RecordMetadata recordMetadata, Exception e) {
            if (e!=null){
                e.printStackTrace();
            }
        }
    }

我们实现一个import org.apache.kafka.clients.producer.Callback;的接口。如果有错误,那就打印。

生产者的配置

生产者还有很多可配置的参数,在Kafka 文档里都有说明,它们大部分都有合理的默认值, 所以没有必要去修改它们。(除了上述介绍的bootstrap.server以及序列化器等)

  • acks
    该参数指定了必须要有多少个分区副本收到消息,生产者才会认为消息写入是成功的。
    如果acks=0,那么生产者在成功写入消息之前不会等待任何来自服务器的响应。也就是说,无法得知消息是否丢失。不过,因为生产者不需要等待服务器的响应,所以它可以以网络能够支持的最大速度发送消息,从而达到很高的吞吐量。
    如果acks=1,那么只要集群的首领节点收到消息,生产者就会收到一个来自服务器的成功响应。如果消息无法到达首领节点(比如首领节点崩愤,新的首领还没有被选举出来),生产者会收到一个错误响应,为了避免数据丢失,生产者会重发消息。
    如果acks=all,只有当所有参与复制的节点全部收到消息时,生产者才会收到一个来自服务器的成功响应。这种模式是最安全的,它可以保证不止一个服务器收到消息,就算有服务器发生崩溃,整个集群仍然可以运行。
  • buffer.memory
    该参数用来设置生产者内存缓冲区的大小,生产者用它缓冲要发送到服务器的消息。生产者空间不足,这个时候,send ()方法调用要么被阻塞,要么抛出异常,取决于block.on.buffer.full参数(0.9.0.0版本里被替换成为max.block.ms)。
  • compression.type
    默认情况下,消息发送时不会被压缩。该参数可以设置为snappy、gzip 或lz4,它指定了消息被发送给broker 之前使用哪一种压缩算也进行压缩。
  • retries
    生产者从服务器收到的错误有可能是临时性的错误(比如分区找不到首领)。在这种情况下,retries参数的值决定了生产者可以重发消息的次数,到达指定次数后,生产者会放弃并返回错误。
  • batch.size
    当有多个消息需要被发送到同一个分区时,生产者会把它们放在同一个批次里。该参数指定了一个批次可以使用的内存大小。
  • linger.ms
    该参数指定了生产者在发送批次之前等待更多消息加入批次的时间。KafkaProducer会在批次填满或linger.ms达到上限时把批次发送出去。
  • client.id
    服务器用来识别消息的来源。
  • 其他略。

序列化器

自定义序列化器
如果发送到Kafka 的对象不是简单的字符串或整型,那么可以使用序列化框架来创建消息记录,如Avro、Thrift或者Protobuf,或者使用自定义的序列化器(但我们不建议使用自定义的序列器)。

使用Avro进行序列化等以后有需要再了解

分区

之前我们的例子使用了ProducerRecord的topic、key、value。其中key有两种用途,它可以作为消息的附加信息,也可以用来决定消息该被写到主题的哪个区域。
key可以被设为null,只要如下设置即可:

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

key为Null,并且采用默认的分区器。分区器使用轮询算法将消息分部到各个分区上。如果key不为null,并且使用了默认的分区器,那么Kafka会对key进行散列,然后根据散列值映射到特定分区。

上面是采用的默认分区器,当然也可以自定义分区器。假设你是一个B2B 供应商,你有一个大客户,它是手持设备Banana 的制造商。Banana 占据了你整体业务10 % 的份额。如果使用默认的散列分区算怯, Banana 的账号记录将和其他账号记录一起被分配给相同的分区,导致这个分区比其他分区要大一些。服务器可能因此出现存储空间不足、处理缓慢等问题。我们需要给Banana 分配单独的分区,然后使用散列分区算住处理其他账号。
在这里插入图片描述

猜你喜欢

转载自blog.csdn.net/No_Game_No_Life_/article/details/84230839