kafka-producer端核心参数解析

332 阅读6分钟

1.producer架构

本文主要讨论kafka-Producer端的消息发送机制;

1.kafka-信息数据结构

kafka是基于tcp进行通信的;发送的报文格式!

Kafka将一条待发送的消息抽象为ProducerRecord对象,其数据结构是:

public class ProducerRecord<K, V> {
    private final String topic; //目标topic
    private final Integer partition; //目标partition
    private final Headers headers;//消息头信息
    private final K key;   //消息key
    private final V value; //消息体
    private final Long timestamp; //消息时间戳
    //省略构造方法与成员方法
}

其中headers属性是Kafka 0.11.x 版本引入的,可以用它存储一些应用或业务相关的信息。
我们在使用api时,可以关注下headers的使用!
最终发送的报文格式

Kafka 协议格式-参考文章

我们只需要大致了解,kafka发送报文格式即可,无需过多关注详细处理步骤!
注意,我们需要清楚,将我们传递的信息会通过序列化器转换成此报文格式;
在http1.1中,我们会使用json(序列化器),将数据转换成请求体!
2.序列化器
Producer发送消息要通过序列化器(Serializer)将消息对象转换成字节数组,才能通过网络传输到服务端,消费端则需要通过反序列化器(Deserializer)从服务端拉取字节数组转成消息对象。
Kafka默认提供了多个序列化器

ByteArraySerializer // 序列化Byte数组,本质上什么都不用做。
ByteBufferSerializer // 序列化ByteBuffer。
BytesSerializer // 序列化Kafka自定义的Bytes类。
StringSerializer // 序列化String类型。
LongSerializer   // 序列化Long类型。
IntegerSerializer // 序列化Integer类型。
ShortSerializer  // 序列化Short类型。
DoubleSerializer // 序列化Double类型。
FloatSerializer  // 序列化Float类型。

在实际项目使用中,我们更倾向于指定消息数据格式;

在实战使用中,应尝试自定义序列化器;

3.分区器的介绍

在分布式集群中,很重要的一点就是负载均衡;

分区器在kafka中,顾名思义,决定当前消息,应当进入哪一个broker的分区!

分区策略

DefaultPartitioner 默认分区策略

  • 如果消息中指定了分区,则使用它
  • 如果未指定分区但存在key,则根据序列化key使用murmur2哈希算法对分区数取模。哈希策略
  • 如果不存在分区或key,则会使用粘性分区策略,关于粘性分区请参阅 KIP-480。轮询策略的优化版本

KIP-480: Sticky Partitioner

在 Kafka 2.4 版本之前,在没有指定分区和键的情况下,生产者默认 partitioner 以循环方式对数据进行分区(具体参见 《Key为null时Kafka如何选择分区(Partition)》)。这导致将一个大的 batches 拆成许多小的 batches,导致更多的请求以及排队过程中产生更长的延时。

KIP-480 实现了一个新的 partitioner,在没有指定分区或键而导致批处理满时选择 Sticky Partitioner。使用 Sticky Partitioner 有助于改进消息批处理,减少延迟,并减少 broker 的负载。在 KIP 上讨论的一些基准测试显示,延迟降低了50%,CPU 利用率降低了5-15%。

	public ListenableFuture<SendResult<K, V>> send(String topic, Integer partition, K key, @Nullable V data) {
		ProducerRecord<K, V> producerRecord = new ProducerRecord<>(topic, partition, key, data);
		return doSend(producerRecord);
	}

你可以指定需要发送的信息分区(partition);这能帮助实现顺序消费;

粘性分区策略

可以看到,使用粘性分区之后,优先填满一个Batch,然后再去填充另一个Batch。不至于轮询策略,虽然平均分配了,但是导致一个Batch都没有放满,不能立即发送。导致信息延迟(只能通过linger.ms时间到了才发送)

此图应当结合消息缓存池,一同理解!

producer端消息投递模式

消息队列的生产和消费的两种模式,即点对点和发布订阅模式。而 Kafka 同时支持这两种模式

  • 点对点模式基于队列,类似于同一个消费者组中的数据,由生产者发送数据到分区,然后消费者拉取分区的消息进行消费,此时消息只能被同一个消费者组的消费者消费一次。

  • 发布订阅模式模式就是 kafka 中的分区消息可以被不同消费者组的消费者消费。这就是一对多的广播模式应用。

消息缓存池

生产端ProducerRecord经过序列化器、分区器处理后,并不是直接发往broker端,而是发送到客户端的消息缓冲池(Accumulator) 中,最后交由Sender线程发往broker端。

缓冲池最大大小由参数buffer.memory控制,默认是32M,当生产消息的速度过快导致buffer满了的时候,将阻塞max.block.ms时间,超时抛异常,所以buffer的大小可以根据实际的业务情况进行适当调整。

批量发送

发送到缓冲池中消息会分批次的发送到broker 端,批次大小由参数batch.size控制,默认16KB。这就意味着正常情况下消息会攒够16KB时才会批量发送到broker端,所以一般减小batch大小有利于降低消息延时,增加batch大小有利于提升吞吐量。

但是消息并不是必须要达到一个batch尺寸才会批量发送到服务端呢,Producer端提供了另一个重要参数linger.ms,用来控制batch最大的空闲时间,超过该时间的batch也会被发送到broker端。

buffer.memory 是发送信息缓冲池整体的大小;

batch.size 发送信息的最小单位(一个批次)

发送消息时,经常会考虑的组合配置;batch.size 和 linger.ms

当消息到达指定尺寸,或信息超过指定超时时间;目标是减少io,增加吞吐量!

2.常用producer端配置参数

kafka.apache.org/documentati…

spring:
  #重要提示:kafka配置,该配置属性将直接注入到KafkaTemplate中
  kafka:
    bootstrap-servers: ${bootstrap-servers.ips}
    producer:
      bootstrap-servers: ${bootstrap-servers.ips}
      # 可重试错误的重试次数,例如“连接错误”、“无主且未选举出新Leader”
      retries: 1 #生产者发送消息失败重试次数
      # 多条消息放同一批次,达到多达就让Sender线程发送
      batch-size: 16384 # 同一批次内存大小(默认16K)
      # 发送消息的速度超过发送到服务器的速度,会导致空间不足。send方法要么被阻塞,要么抛异常
      # 取决于如何设置max.block.ms,表示抛出异常前可以阻塞一段时间
      buffer-memory: 314572800 #生产者内存缓存区大小(300M = 300*1024*1024)
      #acks=0: 无论成功还是失败,只发送一次。无需确认
      #acks=1: 即只需要确认leader收到消息
      #acks=all或-1: Leader + ISR 都确定收到
      acks: 1
      key-serializer: org.apache.kafka.common.serialization.StringSerializer #key的编解码方法
      value-serializer: org.apache.kafka.common.serialization.StringSerializer #value的编解码方法
      #开启事务,此配置要求ack为all,否则无法保证幂等性
      #transaction-id-prefix: "COLA_TX"
      #额外的,没有直接有properties对应的参数,将存放到下面这个Map对象中,一并初始化
      properties:
        #自定义拦截器,注意,这里结尾时classes(先于分区器,快递先贴了标签再指定地址)
        interceptor.classes: cn.com.controller.TimeInterceptor
        #自定义分区器
        #partitioner.class: com.alibaba.cola.kafka.test.customer.inteceptor.MyPartitioner
        #即使达不到batch-size设定的大小,只要超过这个毫秒的时间,一样会发送消息出去
        linger.ms: 1000
        #最大请求大小,200M = 200*1024*1024,与服务器broker的message.max.bytes最好匹配一致
        max.request.size: 209715200
        #Producer.send()方法的最大阻塞时间(115秒)
        # 发送消息的速度超过发送到服务器的速度,会导致空间不足。send方法要么被阻塞,要么抛异常
        # 取决于如何设置max.block.ms,表示抛出异常前可以阻塞一段时间
        max.block.ms: 115000
        #该配置控制客户端等待服务器的响应的最长时间。
        #如果超时之前仍未收到响应,则客户端将在必要时重新发送请求,如果重试次数(retries)已用尽,则会使请求失败。 
        #此值应大于replica.lag.time.max.ms(broker配置),以减少由于不必要的生产者重试而导致消息重复的可能性。
        request.timeout.ms: 115000
        #等待send回调的最大时间。常用语重试,如果一定要发送,retries则配Integer.MAX
        #如果超过该时间:TimeoutException: Expiring 1 record(s) .. has passed since batch creation
        delivery.timeout.ms: 120000
        # 生产者在服务器响应之前能发多少个消息,若对消息顺序有严格限制,需要配置为1
        # max.in.flight.requests.per.connection: 1

3.消息无丢失配置
kafka.apache.org/10/document…
producer端无丢失的配置列表:
●retries = 3
●acks = all or -1
●min.insync.replicas = 2
●max.in.fight.requests.per.connection = 1
●使用带回调机制的send方法即send(record, callback)发送消息,并对失败消息进行处理

min.insync.replicas的解释
写入的最少同步副本数,当我们指定 acks = all or -1 时, 通常会指定半数写入;
常见的场景是创建一个三副本(即replication.factor=3)的topic,最少同步副本数设为2(即min.insync.replicas=2),acks设为all,以保证最高的消息持久性。
在borker端应保证,如下配置

●unclean.leader.election.enable = false

非ISR中的副本不能够参与选举,此时无法进行新的选举,此时整个分区处于不可用状态
只要选举成功,才能允许,消息的写入

●replication.factor = 3 (确保能有3以上的副本数)
在consumer中应保证
●enable.auto.commit = false

consumer中,应当手动提交偏移量;