生产者原理

java kafka 生产者配置 kafka生产者参数配置_客户端

一个生产者客户端由两个线程协调运行,这两个线程分别为主线程Sender线程。

  • 主线程:中由 kafkaProducer 创建消息,然后通过可能的拦截器、序列化器和分区器的作用之后缓存到消息累加器(RecordAccumulator, 也称为消息收集器)中。

RecordAccumulator:主要用来缓存消息以便 Sender 线程可以批量发送,进而减少网络传输的资源消耗以提升性能。缓存的大小可以通过生产者客户端参数 buffer.memory 配置,默认值为 33554432B (32M)。如果生产者发送消息的速度超过发送到服务器的速度,则会导致生产者空间不足,这个时候 KafkaProducer.send()方法调用要么被阻塞,要么抛出异常,这个取决于参数max.block.ms 的配置,此参数的默认值为 60000ms。

RecordAccumulator 内部为每个分区都维护了一个双端队列,即 Deque<ProducerBatch>。消息写入缓存时,追加到双端队列的尾部;Sender 读取消息时,从双端队列的头部读取。

ProducerBatch :一个消息批次,Sender读取时,会将较小的 ProducerBatch 凑成一个较大 ProducerBatch ,也可以减少网络请求的次数以提升整体的吞吐量。一个ProducerBatch 最大的容量是由batch.size配置。

当一条消息( ProducerRecord ) 流入RecordAccumulator 时,会先寻找与消息分区所对应的双端队列(如果没有则新建),再从这个双端队列的尾部获取一个 ProducerBatch (如果没有则新建),查看 ProducerBatch 中是否还可以写入这个 ProducerRecord, 如果可以写入, 如果不可以则需要创建一个新的 Producer Batch 。在新建ProducerBatch 时评估这条消息的大小是否超过 batch.size 参数大小,如果不超过,那么就以 batch.size 参数的大小来创建 ProducerBatch。

 

  • Sender线程:从 RecordAccumulator 获取消息并将其发送到 Kafka  中。

对于 KafkaProducer 的应用逻辑而言,主线程只关注向哪个分区中发送哪些消息;而对于网络连接来说,生产者客户端是与具体 broker 节点建立的连接,而并不关心消息属于哪一个分区,所以在这里需要做一个应用逻辑层面到网络 I/O 层面的转换。

Sender 从 RecordAccumulator 获取缓存的消息之后,会进一步将<分区,Deque<Producer Batch>>的形式转变成<Node,List< ProducerBatch>的形式,其中 Node 表示 Kafka 集群 broker 节点。转换之后,Sender 会进一步封装成<Node,Request> 的形式, 这样就可以将 Request 请求发往各个 Node 了,这里的 Request 是 Kafka 各种协议请求。

InFlightRequests :请求在从 sender 线程发往 Kafka 之前还会保存到 InFlightRequests 中,InFlightRequests 保存对象的具体形式为 Map<Nodeld, Deque<request>>,它的主要作用是缓存了已经发出去但还没有收到服务端响应的请求(Nodeld  是一个 String  类型,表示节点的 id  编号)。与此同时,InFlightRequests  还提供了许多管理类的方法,并且通过配置参数还可以限制每个连接(也就是客户端与 Node 之间的连接) 最多缓存的请求数。这个配置参数为 max.in.flight.request.per. connection ,默认值为 5,即每个连接最多只能缓存 5 个未响应的请求,超过该数值之后就不能再向这个连接发送更多的请求了,除非有缓存的请求收到了响应( Response )。通过比较 Deque<Request> 的 size 与这个参数的大小来判断对应的 Node 中是否己经堆积了很多未响应的消息,如果真是如此,那么说明这个 Node 节点负载较大或网络连接有问题,再继其发送请求会增大请求超时的可能。

 

重要参数

acks
0:Producer 往集群发送数据不需要等到集群的返回,不确保消息发送成功。安全性最低但是效率最高。
1:Producer 往集群发送数据只要 Leader 成功写入消息就可以发送下一条,只确保 Leader 接收成功。
-1/all:roducer 往集群发送数据需要所有的 ISR Follower 都完成从 Leader 的同步才会发送下一条,确保Leader 发送成功和所有的副本都成功接收。安全性最高,但是效率最低。

max.request.size
这个参数用来限制生产者客户端能发送的消息的最大值,默认值为 1048576B ,即 1MB
一般情况下,这个默认值就可以满足大多数的应用场景了。这个参数还涉及一些其它参数的联动,比如 broker 端的 message.max.bytes 参数,如果配置错误可能会引起一些不必要的异常; 比如将 broker 端的 message.max.bytes 参数配置为 10 , 而max.request.size 参数配置为 20,那么当发送一条大小为 15B 的消息时,生产者客户端就会报出异常

compression.type
这个参数用来指定消息的压缩方式,默认值为“none ",即默认情况下,消息不会被压缩。该参数还可以配置为 "gzip","snappy" 和 "lz4"。
对消息进行压缩可以极大地减少网络传输、降低网络 I/O,从而提高整体的性能 。消息压缩是一种以时间换空间的优化方式,如果对时延有一定的要求,则不推荐对消息进行压缩

buffer.memory
RecordAccumulator缓存的大小默认值为 33554432B (32M)

max.block.ms
如果记录发送到RecordAccumulator的速度比sender发送到服务器的速度快, Producer 就会阻塞,如果阻塞的时间超过 max.block.ms 配置的时长,则会抛出一个异常,默认值为 60000ms

batch.size
每个 ProducerBatch 要存放 batch.size 大小的数据后,才可以发送出去。比如说 batch.size 默认值是 16KB,那么里面凑够 16KB 的数据才会发送。
理论上来说,提升 batch.size 的大小,可以允许更多的数据缓冲在里面,那么一次 Request 发送出去的数据量就更多了,这样吞吐量可能会有所提升。但是 batch.size 也不能过大,要是数据老是缓冲在 Batch 里迟迟不发送出去,那么发送消息的延迟就会很高。一般可以尝试把这个参数调节大些,利用生产环境发消息负载测试一下。

linger.ms
这个参数用来指定生产者发送 ProducerBatch 之前等待更多消息( ProducerRecord )加入ProducerBatch 时间,默认值为 0。
生产者客户端会在 ProducerBatch 填满或等待时间超过 linger.ms 值时发送出去。增大这个参数的值会增加消息的延迟,但是同时能提升一定的吞吐量。

retries 和 retry.backoff.ms
retries 参数用来配置生产者重试的次数,默认值为 0,即在发生异常的时候不进行任何重试动作。消息在从生产者发出到成功写入服务器之前可能发生一些临时性的异常,比如网络抖动、leader 副本的选举等,这种异常往往是可以自行恢复的,生产者可以通过配置 retries 大于 0 的值,以此通过内部重试来恢复而不是一味地将异常抛给生产者的应用程序。如果重试达到设定的次数,那么生产者   就会放弃重试并返回异常。
重试还和另一个参数 retry.backoff.ms 有关,这个参数的默认值为 100,它用来设定两次重试之间的时间间隔,避免无效的频繁重试。

max.flight.requests.per.connection
该参数指定了sender在收到服务器响应之前可以发送多少个消息。它的值越高,就会占用越多的内存,不过也会提升吞吐量。把它设为 1 可以保证消息是按照发送的顺序写入服务器的,即使发生了重试。
如果将 acks 参数配置为非零值,并且 max.flight.requests.per.connection 参数配置为大于1的值,那可能会出现错序的现象:如果第一批次消息写入失败,而第二批次消息写入成功,那么生产者会重试发送第一批次的消息,此时如果第一次的消息写入成功,那么这两个批次的消息就出现了错序。
一般而言,在需要保证消息顺序的场合建议把参数 max.in.flight.requests.per.connection 配置为 1 ,而不是把 acks 配置为 0,不过这样也会影响整体的吞吐。

enable.idempotence
是否开启幂等性功能,生产者生产的一条消息,如果多次重复发送,在服务器中的结果还是只有一条
在开启幂等性功能时,如下几个参数必须正确配置
retries > 0
max.in.flight.requests.per.connection<=5
acks = -1
如有违反,则会抛出 ConfigException 异常。

partitioner.classes
用来指定分区器,默认:org.apache.kafka.internals.DefaultPartitioner
自定义 partitioner 需要实现 org.apache.kafka.clients.producer.Partitioner 接口