1.Producer发送数据流程
一个生产者客户端由两个线程协调完成,即主线程和Sender线程。
在主线程中由KafkaProducer创建消息,然后通过可能的拦截器,序列化器,分区器的作用后缓存到消息累加器(RecordAccumulator,也称消息收集器)中。Sender线程负责从RecordAccumulator中获取消息并将其发送到kafka。
RecordAccumulator主要用来缓存消息以便Sender线程可以批量发送,进而减少网络传输的资源消耗以提升性能。RecordAccumulator缓存的大小可以通过生产者客户端参数buffer.memory配置,默认值为33554432B,即32M。如果生产者发送的消息的速度超过发送到服务器端的速度,则会导致生产者空间不足,这个时候KafkaProducer.send()方法要么被阻塞要么抛出异常,这个参数取决于max.block.ms的配置,此参数的默认值,60000即60秒。
主线程中发送过来的消息都会被迫加入到RecordAccumulator的某个双端队列(Deque)中,RecordAccumulator内部为每一个分区维护了一个双端队列,即Deque<ProducerBatch>。消息写入缓存时,追加到双端队列的尾部。Sender读取消息时,从双端队列的头部读取。注意:ProducerBatch是指一个消息批次;与此同时会将较小的ProducerBatch凑成一个较大的ProducerBatch,也可以减少网络请求的次数以提升整体吞吐量。
ProducerBatch大小和batch.size参数也有者密切关系。当一条消息(ProducerRecord)流入RecordAccumulator时,会先寻找与消息分区所对应的双端队列(如果没有则新建),再从这个这个队列的尾部获取一个ProducerBatch(如果没有则新建),查看ProducerBatch中是否可以写入这个ProducerRecord,如果可以则写入,如果不可以则重新创建一个新的ProducerBatch。在新建ProducerBatch时评估这条消息的大小是否超过batch.size参数大小。如果不超过,那么就以batch.size参数大小来创建ProducerBatch。
如果生产者客户端要向很多分区发送消息,则可以将buffer.memory参数适当调大以增加整体吞吐量。
Sender从RecordAccumulator获取缓存的消息后,会将消息做转换(从 <分区,Dqeue<ProducerBatch>>转换成 <Node, List<ProducerBatch>>的形式),其中Node表示Kafka集群broker节点。对于网络连接来说,生产者客户端是与具体的broker建立的连接,也就是向具体的broker发送消息,而不关心消息属于哪个分区;而对于以KafkaProducer应用逻辑而言,我们只关心向哪个分区发送了哪些消息,所以在这里需要做一个应用逻辑层面到网络I/O层面的转换。在转换成<Node, List<ProducerBatch>>的形式之后,Sender会进一步封装成<Node, Request>的形式,这样就可以将Request请求发往各个Node了,这里的Request就是kafka的各种协议请求。
请求在从Sender线程发往Kafka之前还会保存到IntFlightRequests中,IntFlightRequests保存对象的具体形式为 Map<NodeId, Dqeue<Request>>, 它的主要作用是缓存了已经发送出去但是还没有收到Kafka服务器端响应的请求(NodeId是个String类型,代表节点的Id编号)。与此同时,IntFlightRequests还提供了许多管理类的方法,并且可以通过配置参数还可以限制每个连接(客户端与Node的连接)最多缓存的请求数。这个配置参数为max.in.flight.request.per.connection,默认值为5,即每个连接最多缓存5个未响应的请求,超过该数值后就不再向这个连接发送更多的请求了,除非有缓存的请求收到了响应(Response)。通过比较Dqeue<Request>的size与这个参数的大小来判断对应的Node是否堆积很多未响应的消息,如果果真如此,那么说明这个Node的负载很大,或者网络连接有问题,再继续发送消息会增大消息请求超时的可能性。
2.Producer重要参数解析
2.1 acks
0
Producer往集群发消息不需要等待集群的返回,不确保消息的发送成功。安全性最低,但是效率最高。
1
Producer往集群发送消息只要Leader成功写入消息,就可以发送下一条消息。只能确保Leader接受成功。
-1 或 all
Producer往集群发送消息需要所有的ISR Follower都完成从Leader的同步,才发送下一条消息。确保Leader发送成功和所有副本同步成功。安全性最高,但效率最低。
2.2 max.request.size
这个参数用来限制生产者客户端能发送数据的最大值,默认值为1048576B,即1M。一般情况下这个默认值就可以满足绝大多数应用场景了。
这个参数还涉及到其他的参数联动,如broker端的message.max.bytes参数,如果配置错误可能会引起一些不必要的异常,
如将broker端的message.max.bytes设置为10B,将Producer客户端的max.request.size设置为20B,当发送一条15B大小的数据时,Producer客户端就会抛出异常。
2.3 compression.type
这个参数用来指定消息的压缩方式,默认值为“none”.即默认情况下,消息不户被压缩。其他可配参数为“gzip”,“snappy”,“lz4”。
对消息进行压缩可以极大的减少网络传输,降低网络I/O,从而提高整体性能。
消息压缩是一种时间还空间的优化方式,如果对延迟有一定的要求,则不建议进行消息压缩。
2.4 retries 和 retry.backoff.ms
retries参数用来配置生产者客户端重试次数,默认值为0,即在发生异常的时候不做任何重试。消息在从生产者客户端发出到成功写入服务器之前可能发生一些临时性的异常,
比如网络抖动,发生leader副本的选举等,这种异常往往是可以自动恢复的,生产者客户端通过设置retries的值,一次通过内部重试来恢复而不是一味的将异常抛给生产者的应用程序。
如果重试次数达到设置的次数,那么生产者客户端将不再重试并抛出异常。
重试还可另外一个参数有关,retry.backoff.ms有关,这个参数默认值为100,它用来设置两次重试之间的时间间隔,避免无效的频繁重试。
Kafka可以保证同一个分区中的消息是有序的。如果生产者按照一定的顺序发送消息,那么这些消息也会顺序的写入分区,进而消费者也可以按照同样的顺序消费它们。
对于某些应用顺序性非常重要,比如Mysql的binlog的传输,如果顺序错误就会发生非常严重的事故。
如果将acks参数配置成非零值。并且将max.in.flight.requests.per.connection参数配置大于1的值。那么就会出现错序现象。一般而言,
在需要保证顺序性的场景建议把参数max.in.fight.requests.per.connection配置为1,而不是把acks配置为0,不过这样配置会影响整体的吞吐量。
2.5 batch.size
每个Batch要存放batch.size大小的数据后,才可以发送出去,比如说batch.size默认值是16KB,那么里面凑够16KB的数据才会发送。
理论上来说,提升batch.size的大小,可以允许缓存更多的数据,那么一个Request发送的数据量就更大了,这样吞吐量会有所提升。
但是batch.size不能设置过大,要是Batch里面缓存的数据间隔很久才发送出去,那么发送消息的延迟就会很高。
一般可以把这个参数调大一些,然后利用生产环境发送消息负载测试下。
2.6 linger.ms
这个参数用来指定生产者发送ProducerBatch之前等待等待更多消息(ProducerRecord)的加入ProducerBatch的时间。
生产者客户端会在ProducerBacth填满或者或者等待时间超过linger.ms时发送出去。
增大这个参数的值会增大消息的延时,同同会提升一定的吞吐量。
2.7 enable.idempotence
是否开启密等性功能
2.8 partitioner.class
用来指定分区器,默认的分区器:org.apache.kafka.clients.producer.internals.DefaultPartitioner。自定义的分区器需要实现接口:org.apache.kafka.clients.producer.Partitioner。
以上参数是比较常见的参数,全部Producer的配置参数可以查看官网:https://kafka.apache.org/27/documentation.html#producerconfigs