• 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接口

  • 相关阅读:
    asp.net程序集冲突解决笔记(未能加载文件或程序集"XXXXXXXXX")
    让Asp.net mvc WebAPI 支持OData协议进行分页查询操作
    jQuery Select 自动选择默认值
    nuget在jenkins上不能自动还原项目依赖包---笔记
    Ubuntu 14.04 server ssh 远程服务遇到的一点事儿
    Unbunt vi 编辑器键盘按键不正确的一次经历与解决方案
    Ubuntu root 密码 sudo passwd
    Visual Studio 2015 下 编译 libpng
    .NET使用Com组件的一点点教训笔记~
    Linux透明大页(Transparent Huge Pages)对ES性能对影响
  • 原文地址:https://www.cnblogs.com/microcat/p/11432760.html
Copyright © 2020-2023  润新知