全屏浏览
缩小浏览
回到页首

kafka---->kafka生产者(一)

我们将会介绍 Kafka 的消费者客户端,以及如何从 Kafka 读取消息。

kafka生产者概述

⼀个应⽤程序在很多情况下需要往 Kafka 写⼊消息:记录⽤户的活动(⽤于审计和分析)、记录度量 指标、保存⽇志消息、记录智能家电的信息、与其他应⽤程序进⾏异步通信、缓冲即将写⼊到数据库 的数据等等。

image

  • 我们从创建⼀个 ProducerRecord 对象开始,ProducerRecord 对象需要包含⽬标主题和要发送的 内容。我们还可以指定键或分区。在发送 ProducerRecord 对象时,⽣产者要先把键和值对象序列化 成字节数组,这样它们才能够在⽹络上传输。
  • 接下来,数据被传给分区器。如果之前在 ProducerRecord 对象⾥指定了分区,那么分区器就不会再 做任何事情,直接把指定的分区返回。如果没有指定分区,那么分区器会根据 ProducerRecord 对象 的键来选择⼀个分区。选好分区以后,⽣产者就知道该往哪个主题和分区发送这条记录了。紧接着, 这条记录被添加到⼀个记录批次⾥,这个批次⾥的所有消息会被发送到相同的主题和分区上。有⼀个 独⽴的线程负责把这些记录批次发送到相应的 broker 上。
  • 服务器在收到这些消息时会返回⼀个响应。如果消息成功写⼊ Kafka,就返回⼀个 RecordMetaData 对象,它包含了主题和分区信息,以及记录在分区⾥的偏移量。如果写⼊失败, 则会返回⼀个错误。⽣产者在收到错误之后会尝试重新发送消息,⼏次之后如果还是失败,就返回错 误信息。

创建Kafka⽣产者

要往 Kafka 写⼊消息,⾸先要创建⼀个⽣产者对象,并设置⼀些属性。Kafka ⽣产者有 3 个必选的 属性。

  • bootstrap.servers:该属性指定 broker 的地址清单,地址的格式为 host:port。
  • key.serializer:broker 希望接收到的消息的键和值都是字节数组。⽣产者接⼝允许使⽤参数化类型,因此可以把 Java 对象作为键和值发送给 broker。
  • value.serializer:与 key.serializer ⼀样,value.serializer 指定的类会将值序列化。
var 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");
var producer = new KafkaProducer<String, String>(kafkaProps);

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

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

发送消息到Kafka

同步发送消息

var record = new ProducerRecord<>("CustomerCountry", "Precision Products", "France");
try {
    producer.send(record).get();
} catch (Exception e) {
    e.printStackTrace();
}

KafkaProducer ⼀般会发⽣两类错误。

  • 其中⼀类是可重试错误,这类错误可以通过重发消息来解 决。⽐如对于连接错误,可以通过再次建⽴连接来解决,“⽆主(no leader)”错误则可以通过重新 为分区选举⾸领来解决。KafkaProducer 可以被配置成⾃动重试,如果在多次重试后仍⽆法解决问 题,应⽤程序会收到⼀个重试异常。
  • 另⼀类错误⽆法通过重试解决,⽐如“消息太⼤”异常。对于这类 错误,KafkaProducer 不会进⾏任何重试,直接抛出异常。

异步发送消息

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

var record = new ProducerRecord<>("CustomerCountry", "Biomedical Materials", "USA");
producer.send(record, new DemoProducerCallback());

⽣产者的配置

以下⼏个参数在内存使⽤、性能和可靠性⽅⾯对⽣产者影响⽐较⼤

acks: 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 之前使⽤哪⼀种压缩算法进⾏压缩。snappy 压缩算法由 Google 发明,它占 ⽤较少的 CPU,却能提供较好的性能和相当可观的压缩⽐,如果⽐较关注性能和⽹络带宽,可以 使⽤这种算法。gzip 压缩算法⼀般会占⽤较多的 CPU,但会提供更⾼的压缩⽐,所以如果⽹络 带宽⽐较有限,可以使⽤这种算法。使⽤压缩可以降低⽹络传输开销和存储开销,⽽这往往是向 Kafka 发送消息的瓶颈所在。

retries: ⽣产者从服务器收到的错误有可能是临时性的错误(⽐如分区找不到⾸领)。在这种情况下, retries 参数的值决定了⽣产者可以重发消息的次数,如果达到这个次数,⽣产者会放弃重试并 返回错误。默认情况下,⽣产者会在每次重试之间等待 100ms,不过可以通过 retry.backoff.ms 参数来改变这个时间间隔

batch.size: 当有多个消息需要被发送到同⼀个分区时,⽣产者会把它们放在同⼀个批次⾥。该参数指定了⼀ 个批次可以使⽤的内存⼤⼩,按照字节数计算(⽽不是消息个数)。当批次被填满,批次⾥的所 有消息会被发送出去。不过⽣产者并不⼀定都会等到批次被填满才发送,半满的批次,甚⾄只包 含⼀个消息的批次也有可能被发送。

linger.ms: 该参数指定了⽣产者在发送批次之前等待更多消息加⼊批次的时间。KafkaProducer 会在批次 填满或 linger.ms 达到上限时把批次发送出去。默认情况下,只要有可⽤的线程,⽣产者就会 把消息发送出去,就算批次⾥只有⼀个消息。

client.id: 该参数可以是任意的字符串,服务器会⽤它来识别消息的来源,还可以⽤在⽇志和配额指标⾥。

max.in.flight.requests.per.connection: 该参数指定了⽣产者在收到服务器响应之前可以发送多少个消息。它的值越⾼,就会占⽤越多的 内存,不过也会提升吞吐量。把它设为 1 可以保证消息是按照发送的顺序写⼊服务器的,即使发 ⽣了重试。

timeout.ms、request.timeout.ms 和 metadata.fetch.timeout.ms: request.timeout.ms 指定了⽣产者在发送数据时等待服务器返回响应的时间, metadata.fetch.timeout.ms 指定了⽣产者在获取元数据(⽐如⽬标分区的⾸领是谁)时等 待服务器返回响应的时间。如果等待响应超时,那么⽣产者要么重试发送数据,要么返回⼀个错 误(抛出异常或执⾏回调)。timeout.ms 指定了 broker 等待同步副本返回消息确认的时间, 与 asks 的配置相匹配——如果在指定时间内没有收到同步副本的确认,那么 broker 就会返回 ⼀个错误。

max.block.ms: 该参数指定了在调⽤ send() ⽅法或使⽤ partitionsFor() ⽅法获取元数据时⽣产者的阻塞 时间。当⽣产者的发送缓冲区已满,或者没有可⽤的元数据时,这些⽅法就会阻塞。在阻塞时间 达到 max.block.ms 时,⽣产者会抛出超时异常。

max.request.size: 该参数⽤于控制⽣产者发送的请求⼤⼩。它可以指能发送的单个消息的最⼤值,也可以指单个请 求⾥所有消息总的⼤⼩。例如,假设这个值为 1MB,那么可以发送的单个最⼤消息为 1MB,或 者⽣产者可以在单个请求⾥发送⼀个批次,该批次包含了 1000 个消息,每个消息⼤⼩为 1KB。 另外,broker 对可接收的消息最⼤值也有⾃⼰的限制(message.max.bytes),所以两边的 配置最好可以匹配,避免⽣产者发送的消息被 broker 拒绝。

receive.buffer.bytes 和 send.buffer.bytes: 这两个参数分别指定了 TCP socket 接收和发送数据包的缓冲区⼤⼩。如果它们被设为 -1,就使 ⽤操作系统的默认值。如果⽣产者或消费者与 broker 处于不同的数据中⼼,那么可以适当增⼤ 这些值,因为跨数据中⼼的⽹络⼀般都有⽐较⾼的延迟和⽐较低的带宽。

posted @ 2023-03-28 20:55  huhx  阅读(36)  评论(0编辑  收藏  举报