Kafka--生产者

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

多样的使用场景意味着多样的需求:是否每个消息都很重要?是否允许丢失一小部分消息?偶尔出现重复消息是否可以接受?是否有严格的延迟和吞吐量要求?

不同的使用场景对生产者API的使用和配置会有直接的影响。

 

 

消息发送过程

首先创建一个ProducerRecord对象。

  ProducerRecord对象包含目标主题和要发送的内容。

  可以指定键或分区。

如果有键的话,将键序列化成字节数组,以便在网络上传输。同样地,要发送的内容,即值,也需要序列化成字节数组。

接下来,数据被传给分区器。

  如果之前在ProducerRecord对象里指定了分区,那么分区器就不会再做任何事情,直接把指定的分区返回。

  如果没有指定分区,那么分区器会根据ProducerRecord对象的键来选择一个分区。

选好分区后,生产者就知道该往哪个主题和分区发送记录了。

紧接着,这条记录被添加到了一个记录批次里。

  这个批次里的所有消息都会被发送到相同的主题和分区上。

  有一个独立的线程负责把这些记录批次发送到相应的broker上。

服务器在收到这些消息时会返回一个相应。

  如果消息成功写入Kafka,就返回一个RecordMetaData对象,它包含了主题和分区信息,以及记录在分区里的偏移量。

  如果写入失败,则会返回一个错误。

生产者在接收到错误之后会尝试重新发送消息,几次之后如果还是失败,就返回错误信息。

 

 

发送消息的方式

生产者可以使用单个消费者单个线程,也可以使用单个消费者多个线程。

或者增加消费者。

 

发送并忘记

producer.send(record)

我们把消息发送给服务器,但并不关心它是否正常到达。

大多数情况下,消息会正常到达, 因为Kafka是高可用的,而且生产者会自动尝试重发。

不过这种方式有时候也会丢失一些消息。

 

同步发送

producer.send(record).get()

如果服务器返回错误,get()方法会抛出异常。如果没有发生错误,我们会的到一个RecordMetadata对象,可以用它获取消息的偏移量。

我们使用send()发送消息,它会返回一个Future对象,调用get()方法进行等待。就可以知道消息是否发送成功。

 

异步发送

producer.send(record, ? extends org.apache.kafka.clients.producer.Callback)

Callback接口只有一个onCompletion方法。

如果Kafka返回一个错误,onCompletion方法会抛出一个非空异常。

我们调用send()方法,并指定一个回调函数,服务器在返回响应时调用该函数。

 

 

KafkaProducer异常

KafkaProducer一般会发生两类错误。

其中一类是可重试错误。这类错误通过重发消息来解决。比如连接错误,无主错误等。KafkaProducer可以被配置成自动测试,如果在多次重试后仍无法解决问题,应用程序会收到一个重试异常。

另一类错误无法通过重试解决,比如消息太大异常。对于这类错误,KafkaProducer不会进行任何重试,直接抛出异常。

 

 

生产者配置

 org.apache.kafka.clients.producer.ProducerConfig 

 

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。

该参数必须设置,即使只发送值。

 

value.serializer

用指定的类将值序列化。

该参数必须设置。

 

acks

acks参数指定了必须要有多少个分区副本接收到消息,生产者才会认为消息写入是陈宫的。

这个参数对消息丢失的可能性有重要影响。

该参数有如下选项:

  acks=0.生产者在写入消息之前不会等待任何来自服务器的响应。所以该值可以以网络能够支持的最大速度发送消息,从而达到很高的吞吐量。

  acks=1.只要集群的首领结点接收到消息,生产者就会收到一个来自服务器的成功响应。如果首领结点发生崩溃,一个没有收到消息的结点称为新首领,消息将丢失。

  acks=all.只有当所有参与复制的结点全部接收到消息时,生产者才会收到一个来自服务器的成功响应。这种模式是最安全的,但是延迟最高。

 

buffer.memory

该参数用来设置生产者内存缓冲区的大小,生产者用它缓冲要发送到服务器的消息。

如果应用程序发送消息的速度超过发送到服务器的速度,会导致生产者空间不足。这个时候,send()方法调用要么被阻塞,要么抛出异常,取决于如何设置max.block.ms参数。

 

compression.type

默认情况下,消息发送时不会被压缩。

使用压缩可以降低网络传输开销和存储开销,这也往往是Kafka发送消息的瓶颈所在。

该参数指定了消息被发送给broker之前使用哪一种压缩算法进行压缩,可以设置为:

  snappy -- 占用较少的cpu,提供较好的性能和相当可观的压缩比

  gzip -- 占用较多的cpu,提供更高的压缩比

  lz4

 

retries

生产者从服务器收到的错误有可能是临时性的错误(比如分区找不到首领)。

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

默认情况下,生产者会在每次重试之间等待100ms,不过可以通过retry.backoff.ms参数来改变这个时间间隔。

一般情况下,因为生产者会自动进行重试,所以就没必要在代码逻辑里处理那些可重试的错误。只需要处理那些不可重试的错误或重试次数超出上限的情况。

 

batch.size

该参数指定了一个批次可以使用的内存大小,按照字节计算(而不是消息个数)。

当有多个消息需要被发送到同一个分区时,生产者会把它们放到同一个批次里。

当批次被填满,批次里的所有消息会被发送出去。

不过生产者并不一定都会等到批次被填满时才发送(见下一个参数linger.ms)。

所以,就算把批次大小设置的很大,也不会造成延迟,只是会占用更多的内存而已。

但如果设置的太小,因为生产者需要更频繁的发送消息,会增加一些额外的开销。

 

linger.ms

该参数指定了生产者在发送批次之前等待更多消息加入批次的时间。

KafkaProducer毁在批次填满或linger.ms达到上限时把批次发送出去。

默认情况下, 只要有可用的线程,生产者就会把消息发送出去,就算批次里只有一个消息。

把linger.ms设置成比0大的书,让生产者在发送批次之前等待一会儿,使更多的消息加入到这个批次。虽然这样会增加延迟,但也会提升吞吐量。

 

https://www.itread01.com/content/1524315728.html

只要滿足linger.ms和batch.size滿了就會激活sender線程來發送消息。

 

client.id

该参数可以使任意的字符串,服务器会用它来识别消息的来源,还可以用在日志和配额指标里。

 

max.in.flight.requests.per.connection

该参数指定了生产者在接收到服务器响应之前可以发送多少个消息。

它的值越高,就会占用越多的内存,不过也会提升吞吐量。

把它设为1可以保证消息时按照发送的顺序写入服务器,即使发生了重试。

 

timeout.ms

该参数制定了broker等待同步副本返回消息确认的时间,也acks的配置相匹配。

如果在指定时间内没有收到同步副本的确认,那么broker就会返回一个错误。

 

request.timeout.ms

该参数制定了生产者在发送数据时等待服务器返回响应的时间。

如果等待响应超时,那么生产者要么重试发送数据,要么返回一个错误。

 

medata.fetch.timeout.ms

该参数制定了生产者在获取元数据时等待服务器返回相应的时间。

 

max.block.ms

该参数制定了在调用send()方法或使用partitionsFor()方法获取元数据时生产者的阻塞时间。

当生产者的发送缓冲区已满,或者没有可用的元数据时,这些方法就会阻塞。在则色时间达到max.block.ms时,生产者会抛出超时异常。

 

max.request.size

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

它可以指定能发送的单个消息的最大值,也可以指单个请求里所有消息总的大小。

另外,broke对可接收的消息最大值也有自己的限制(message.max.bytes),所以,两边的配置最好可以匹配,避免生产者发送的消息被broker拒绝。

 

receive.buffer.bytes

该参数指定了TCP socker接收数据包的缓冲区大小。

如果被设为-1,就使用操作系统的默认值。

如果生产者或消费者与broker处于不同的数据中心,那么可以适当增大这些值,因为跨数据中心的网络一般都有比较高的延迟和比较低的宽带。

 

send.buffer.bytes

该参数指定了TCP socker发送数据包的缓冲区大小。

如果被设为-1,就使用操作系统的默认值。

如果生产者或消费者与broker处于不同的数据中心,那么可以适当增大这些值,因为跨数据中心的网络一般都有比较高的延迟和比较低的宽带。

 

 

序列化器

创建一个生产者对象必须制定序列化器。

 

自定义序列化器

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

建议使用通用的序列化框架。

自定义序列化器需要实现org.apache.kafka.common.serialization.Serializer接口。

 

 

 

Avro

Apache Avro是一种与编程语言无关的序列化格式。

Avro数据通过与语言无关的schema来定义。

schema通过JSON来描述,数据被序列化成二进制文件或JSON文件,不过一般会使用二进制文件。

Avro在读写文件时需要用到schema,schema一般会被内嵌在数据文件里。

Avro有一个很有意思的特性,当负责写消息的应用程序使用的新的schema,负责读消息的应用程序可以继续处理消息而无需做任何改动,这个特性使得它特别适合用在像Kafka这样的消息系统上。

 

不过,有以下两个需要注意的地方:

  用于写入数据和读取数据的schema必须是相互兼容的。

  反序列化器需要用到用于写入数据的schema,即使它可能与用于读取数据的schema不一样。Avro数据文件里就包含了用于写入数据的schema,不过在Kafka里一般会将schema文件注册到注册表中。

 

在Kafka里使用Avro

Avro的数据文件里包含了整个schema,不过这样的开销是可接受的。

但是如果在每条Kafka记录里都嵌入schema,会让记录的大小成倍地增加。

通过schema注册表可以将schema保存在注册表中,然后在记录里引用schema的标识符。

 

 

分区

ProducerRecord对象包含了目标主题,键和值。

Kafka的消息是一个个键值对,ProducerRecord对象可以只包含目标主题和值,键可以设置为默认的null,不过大多数应用程序会用到键。

键有两个用途:

  作为消息的附加信息

  决定消息该被写到主题的哪个分区

 

如果键值为null,并且使用了默认的分区器,那么记录将被随机地发送到主体内各个可用的分区上。

分区器使用轮询算法将消息均匀地分布到各个分区上。

如果键不为空,并且使用了默认的分区器,那么Kafka会对键进行散列,然后根据散列值把消息映射到特定的分区上。

同一个键总是被映射到同一个分区上,所以在进行映射时,我们会使用主题所有的分区,而不仅仅是可用的分区。这也意味着,如果写入数据的分区时不可用的,那么就会发生错误。

只有在不改变主题分区数量的情况下, 键与分区之间的映射才能保持不变。

如果要使用键来映射分区,那么最好在创建主题的时候就把分区规划好,而且永远不要增加新分区。

 

自定义分区策略

实现org.apache.kafka.clients.producer.Partitioner接口

 

posted @ 2019-08-30 00:08  MicroCat  阅读(264)  评论(0编辑  收藏  举报