Kafka生产者与分区策略
Kafka生产者与分区策略
生产者发送消息流程
首先,我们创建了一个ProducerRecord对象,它由要发送的消息key-value、要发送的主题名、可选的分区号构成,在发送 ProducerRecord时,需要将key-value通过序列化器序列化为字节数组,这样才能在网络上传输,然后消息到达分区器。
如果发送过程中指定了有效的分区号,则直接发送到该分区,如果发送时未指定,则默认使用key的hash值指定一个分区,如果发送时未指定消息key,则采用轮询的方式选择一个分区,这就是Kafka默认的分区策略,后面会详细讲解
ProducerRecord 还有关联的时间戳,如果用户没有提供时间戳,那么生产者将会在记录中使用当前的时间作为时间戳。Kafka 最终使用的时间戳取决于 topic 主题配置的时间戳类型。
- 如果将主题配置为使用
CreateTime
,则生产者记录中的时间戳将由 broker 使用。 - 如果将主题配置为使用
LogAppendTime
,则生产者记录中的时间戳在将消息添加到其日志中时,将由 broker 重写。
然后,这条消息被存放在一个记录批次里,这个批次里的所有消息会被发送到相同的主题和分区上。由一个独立的线程负责把它们发到 Kafka Broker 上。
Kafka Broker 在收到消息时会返回一个响应,如果写入成功,会返回一个 RecordMetaData 对象,它包含了主题和分区信息,以及记录在分区里的偏移量,上面两种的时间戳类型也会返回给用户。如果写入失败,会返回一个错误。生产者在收到错误之后会尝试重新发送消息,几次之后如果还是失败的话,就返回错误消息。
分区策略
上面介绍了Kafka的默认分区策略,如果需要自定义分区策略,需要实现Partitioner接口中的partition方法,并在配置中指定。
Partitioner 类有三个方法,分别来解释一下
- partition(): 这个类有几个参数:
topic
,表示需要传递的主题;key
表示消息中的键值;keyBytes
表示分区中序列化过后的key,byte数组的形式传递;value
表示消息的 value 值;valueBytes
表示分区中序列化后的值数组;cluster
表示当前集群的原数据。Kafka 给你这么多信息,就是希望让你能够充分地利用这些信息对消息进行分区,计算出它要被发送到哪个分区中。 - close() : 继承了
Closeable
接口能够实现 close() 方法,在分区关闭时调用。 - onNewBatch(): 表示通知分区程序用来创建新的批次
其中与分区策略息息相关的就是 partition() 方法了,分区策略有下面这几种
顺序轮询
顺序分配,消息是均匀的分配给每个 partition,即每个分区存储一次消息,轮询策略是 Kafka Producer 提供的默认策略,如果你不使用指定的轮询策略的话,Kafka 默认会使用顺序轮训策略的方式。
随机分配
实现随机分配的代码只需要两行,如下
List<PartitionInfo> partitions = cluster.partitionsForTopic(topic);
return ThreadLocalRandom.current().nextInt(partitions.size());
生产者配置参数说明
key.serializer
用于 key 键的序列化,它实现了 org.apache.kafka.common.serialization.Serializer
接口
value.serializer
用于 value 值的序列化,实现了 org.apache.kafka.common.serialization.Serializer
接口
acks
acks 参数指定了要有多少个分区副本接收消息,生产者才认为消息是写入成功的。此参数对消息丢失的影响较大
- 如果 acks = 0,就表示生产者也不知道自己产生的消息是否被服务器接收了,它才知道它写成功了。如果发送的途中产生了错误,生产者也不知道,它也比较懵逼,因为没有返回任何消息。这就类似于 UDP 的运输层协议,只管发,服务器接受不接受它也不关心。
- 如果 acks = 1,只要集群的 Leader 接收到消息,就会给生产者返回一条消息,告诉它写入成功。如果发送途中造成了网络异常或者 Leader 还没选举出来等其他情况导致消息写入失败,生产者会受到错误消息,这时候生产者往往会再次重发数据。因为消息的发送也分为
同步
和异步
,Kafka 为了保证消息的高效传输会决定是同步发送还是异步发送。如果让客户端等待服务器的响应(通过调用Future
中的get()
方法),显然会增加延迟,如果客户端使用回调,就会解决这个问题。 - 如果 acks = all,这种情况下是只有当所有参与复制的节点都收到消息时,生产者才会接收到一个来自服务器的消息。不过,它的延迟比 acks =1 时更高,因为我们要等待不只一个服务器节点接收消息。
buffer.memory
此参数用来设置生产者内存缓冲区的大小,生产者用它缓冲要发送到服务器的消息。如果应用程序发送消息的速度超过发送到服务器的速度,会导致生产者空间不足。这个时候,send() 方法调用要么被阻塞,要么抛出异常,具体取决于 block.on.buffer.null
参数的设置。
compression.type
此参数来表示生产者启用何种压缩算法,默认情况下,消息发送时不会被压缩。该参数可以设置为 snappy、gzip 和 lz4,它指定了消息发送给 broker 之前使用哪一种压缩算法进行压缩。
retries
生产者从服务器收到的错误有可能是临时性的错误(比如分区找不到首领),在这种情况下,reteis
参数的值决定了生产者可以重发的消息次数,如果达到这个次数,生产者会放弃重试并返回错误。默认情况下,生产者在每次重试之间等待 100ms,这个等待参数可以通过 retry.backoff.ms
进行修改。
batch.size
当有多个消息需要被发送到同一个分区时,生产者会把它们放在同一个批次里。该参数指定了一个批次可以使用的内存大小,按照字节数计算。当批次被填满,批次里的所有消息会被发送出去。不过生产者井不一定都会等到批次被填满才发送,任意条数的消息都可能被发送。
client.id
此参数可以是任意的字符串,服务器会用它来识别消息的来源,一般配置在日志里
max.in.flight.requests.per.connection
此参数指定了生产者在收到服务器响应之前可以发送多少消息,它的值越高,就会占用越多的内存,不过也会提高吞吐量。把它设为1 可以保证消息是按照发送的顺序写入服务器。
timeout.ms、request.timeout.ms 和 metadata.fetch.timeout.ms
request.timeout.ms 指定了生产者在发送数据时等待服务器返回的响应时间,metadata.fetch.timeout.ms 指定了生产者在获取元数据(比如目标分区的首领是谁)时等待服务器返回响应的时间。如果等待时间超时,生产者要么重试发送数据,要么返回一个错误。timeout.ms 指定了 broker 等待同步副本返回消息确认的时间,与 asks 的配置相匹配----如果在指定时间内没有收到同步副本的确认,那么 broker 就会返回一个错误。
max.block.ms
此参数指定了在调用 send() 方法或使用 partitionFor() 方法获取元数据时生产者的阻塞时间当生产者的发送缓冲区已捕,或者没有可用的元数据时,这些方法就会阻塞。在阻塞时间达到 max.block.ms 时,生产者会抛出超时异常。
max.request.size
该参数用于控制生产者发送的请求大小。它可以指能发送的单个消息的最大值,也可以指单个请求里所有消息的总大小。
receive.buffer.bytes 和 send.buffer.bytes
Kafka 是基于 TCP 实现的,为了保证可靠的消息传输,这两个参数分别指定了 TCP Socket 接收和发送数据包的缓冲区的大小。如果它们被设置为 -1,就使用操作系统的默认值。如果生产者或消费者与 broker 处于不同的数据中心,那么可以适当增大这些值。