Kafka 生产者概述

生产者:往消息队列里推送消息的应用

发送消息的过程

Kafka 生产者发送消息的过程:

  • Kafka 会将发送消息包装为 ProducerRecord 对象, ProducerRecord 对象包含了目标主题和要发送的内容,同时还可以指定键和分区。在发送 ProducerRecord 对象前,生产者会先把键和值对象序列化成字节数组,这样它们才能够在网络上传输。
  • 接下来,数据被传给分区器。如果之前已经在 ProducerRecord 对象里指定了分区,那么分区器就不会再做任何事情。如果没有指定分区 ,那么分区器会根据 ProducerRecord 对象的键来选择一个分区,紧接着,这条记录被添加到一个记录批次里,这个批次里的所有消息会被发送到相同的主题和分区上。有一个独立的线程负责把这些记录批次发送到相应的 broker 上。
  • 服务器在收到这些消息时会返回一个响应。如果消息成功写入 Kafka,就返回一个 RecordMetaData 对象,它包含了主题和分区信息,以及记录在分区里的偏移量。如果写入失败,则会返回一个错误。生产者在收到错误之后会尝试重新发送消息,如果达到指定的重试次数后还没有成功,则直接抛出异常,不再重试。


发送方式

生产者有三种方式发送消息。实际上,发送的动作都是一致的,不由使用者决定。这三种方式区别在于对于消息是否正常到达的处理。

  • fire-and-forget:把消息发送给broker之后不关心其是否正常到达。大多数情况下,消息会正常到达,即使出错了生产者也会自动重试。但是如果出错了,对于我们的服务而言,是无感知的。这种适用于可丢失消息、对吞吐量要求大的场景,比如用户点击日志上报。
  • 同步发送:我们使用send方法发送一条消息,它会返回一个Future,调用get方法可以阻塞住当前线程,等待返回。这种适用对消息可靠性要求高的场景,比如支付,要求消息不可丢失,如果丢失了则阻断业务(或回滚)
  • 异步发送:使用send方法发送一条消息时指定回调函数,在broker返回结果时调用。这个回调函数可以进行错误日志的记录,或者重试。这种方式牺牲了一部分可靠性,但是吞吐量会比同步发送高很多。但是我们可以通过后续的补偿操作弥补业务。

序列化器

一般而言,我们都会采用默认的序列化器。但是我们也可以采用更加高效和通用的序列化框架,比如Protobuf、Avro。
如果有业务需要,可以自定义序列化器实现消息的加密

分区

在kafka中,一个topic可以设定多个分区。在ProducerRecord中,我们可以指定分区,也可以通过指定key来决定消息被分到哪个分区。

如果key为nil,那么分区器会采用轮询的方式将消息平均分配到各个分区;
如果key不为nil,那么分区器会采用内置的hash函数(不是java提供的,不会因为java的升级而改变,可以保证新旧数据可以分到同一个分区)进行分区

指定key有两个问题:

  • 必须保证分区可用。如果分区不可用,那么消息将写入失败。这种情况很少发生,也可以通过复制和同步的功能来保证分区的高可用。
  • 使用默认的分区器,只有在不改变分区数量的情况下才能保证键与分区的映射。一般来说,我们最好是在创建分区时就把分区规划好,避免在生产环境中修改分区数量。

自定义分区算法:如果需要保证key与分区的映射,同时也要兼顾后续业务增长增加分区数量,那么最好是自定义分区算法,采用一致性hash进行分区。

顺序保证
kafka可以保证同一个分区的消息时有序的,也就是说,broker会将同一个分区的消息按发送顺序(准备来说是接收顺序)写入分区,消费者也会按照同样的顺序读取。
但是为了吞吐量,一般我们都会设置每次发送消息的数据大于1(max.in.flight.requests.per.connection)。如果AB两条消息同一批次发送,正常来说应该A在B前面。但是有可能因为A写入出错了,B成功了。之后A在下个批次重试,这时两条消息在队列里的顺序就会变成BA,消费也会按这个顺序进行消费。
如果要确保消息顺序,那么在生产者的配置上,就应该将max.in.flight.requests.per.connection设置为1,每次只提交一条消息,成功了再提交第二条消息。当然,这样会严重影响吞吐量。
还有一种方法,那就是在发送AB的时候,先保证A发送成功,再发送B。B的发送可以同步发送A成功后再发送,或者在异步发送A消息的回调函数中进行。这种方法会加长单个业务的处理时长,但是发送消息的吞吐量不会受到影响。就整体而言,如果是有异步发送的方式,其吞吐量并不会下降多少。

参考

《kafka权威指南》

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章