向kafka发送消息的send过程

主要步骤

创建ProducerRecord对象

在我们通过send方法发送消息时,在send方法内部首先创建一个ProducerRecord对象。
ProducerRecord对象中主要包含两个必选参数目标主题(topic)和消息内容(value),以及可选参数分区(partition)、键(key)、时间戳(timestamp)等。
每条消息都有一个时间戳。如果我们在发送消息时没有指定时间戳,则生产者将在消息记录上标记其当前时间。Kafka最终使用的时间戳取决于为主题配置的时间戳类型:
如果主题配置为使用org.apache.kafka.common.record.TimestampType.CREATE_TIME,则broker将使用生产者记录中的时间戳。
如果将主题配置为使用org.apache.kafka.common.record.TimestampType.LOG_APPEND_TIME,则当broker将消息追加到日志文件时,将使用broker本地时间覆盖生产者记录中的时间戳。不管上述哪种情况,实际使用的时间戳将在RecordMetadata中返回给客户端。

拦截器

kafka0.10版本引入的,主要是为了在client端进行定制化逻辑控制。
可能会运行在多个线程中,需要开发人员在具体实现时自己保证线程安全。

序列化key和value

在发送ProducerRecord对象时,生产者通过序列化器(例如Apache Avro)把键(key)和值(value)序列化成字节数组,这样它们才能在网络中传输。

分区器根据键(key)选择一个分区

如果我们在发送消息时指定了分区,那么分区器不会再做任何事情,直接返回指定的分区。
如果我们在发送消息时没有指定分区,那么分区器会根据ProducerRecord对象的键(key)的hash值来选择一个分区。
如果发送消息时没有指定分区,也没有配置key,则使用轮询的方式分配分区。

向kafka broker发送消息

根据上一步选择的分区,生产者就知道该往哪个主题和分区发送这条消息了。这条消息会被添加到一个消息批次,缓存到缓冲区中,同一个批次中的所有消息都会被发送到相同的主题和分区上。一个独立的sender线程会把这些批次发送到响应的kafka broker上。

返回响应

kafka broker收到消息时会返回一个响应。如果消息成功写入kafka,就返回一个RecordMetaData对象,该对象包含了主题和分区信息,以及记录在分区里的偏移量、时间戳等。如果写入失败,则会返回一个错误信息。生产者在收到错误之后会尝试重试发送消息,一定次数之后如果还是失败,就返回错误信息。

基本线程

主线程

负责消息创建、拦截器、序列化器、分区器等操作,并将消息追加到消息收集器中。

  1. 消息收集器RecoderAccumulator为每个分区都维护了一个Deque 类型的双端队列。
  2. ProducerBatch可以理解为是 ProducerRecord 的集合,批量发送有利于提升吞吐量,降低网络影响;
  3. 由于生产者客户端使用 java.io.ByteBuffer 在发送消息之前进行消息保存,并维护了一个 BufferPool 实现 ByteBuffer 的复用;该缓存池只针对特定大小(batch.size指定)的 ByteBuffer进行管理,对于消息过大的缓存,不能做到重复利用。
  4. 每次追加一条ProducerRecord消息,会寻找/新建对应的双端队列,从其尾部获取一个ProducerBatch,判断当前消息的大小是否可以写入该批次中。若可以写入则写入;若不可以写入,则新建一个ProducerBatch,判断该消息大小是否超过客户端参数配置 batch.size 的值,不超过,则以 batch.size建立新的ProducerBatch,这样方便进行缓存重复利用;若超过,则以计算的消息大小建立对应的 ProducerBatch,缺点就是该内存不能被复用了。

Sender线程

  1. 该线程从消息收集器获取缓存的消息,将其处理为<Node, List> 的形式,Node 表示集群的broker节点。
  2. 进一步将<Node,List>转化为<Node, Request>形式,此时才可以向服务端发送数据。
  3. 在发送之前,Sender线程将消息以Map<Nodeld,Deque> 的形式保存到InFlightRequests 中进行缓存,可以通过其获取 leastLoadedNode,即当前Node中负载压力最小的一个,以实现消息的尽快发出。

同步发送

使用send()方法发送消息,它会返回一个Future对象或者CompletableFuture对象,调用get()方法进行等待,就可以知道消息是否发送成功。
如果业务上要求消息必须是按顺序发送的,那么可以使用同步发送的方式,并且只能发送到一个分区上,配合参retries的,可以在发送失败时进行重试发送。
设置max_in_flight_requests_per_connection=1,可以控制生产者在收到服务器晌应之前只能发送1个消息,在消息发送成功后立刻flush,从而控制消息顺序发送。
KafkaProducer有两种类型的异常,一种是可以重试的异常,该类异常可以通过重新发送消息解决。例如,连接异常后重新连接、“no leader”异常后重新选取新的leader等。KafkaProducer可以配置为遇到该类异常后自动重新发送消息直到超过重试次数。
另一种是不可重试的异常,例如“message size too large”(消息太大),该类异常会马上返回错误。

异步发送

调用send()方法,并指定一个回调函数,服务器在返回响应时调用该回调函数。
如果业务上需要知道消息发送是否成功,并且对消息的顺序不关心,那么可以用异步+回调的方式来发送消息,配合参数retries=0,并将发送失败的消息记录到日志文件中。
要使用callback函数,先要实现org.apache.kafka.clients.producer.Callback接口,该接口只有一个onCompletion方法。
如果发送异常,onCompletion的参数Exception e会为非空。
异步发送消息时,kafka会先把消息存储在缓冲池中,当到达以下设定条件后,会触发缓冲池消息发送操作:

  1. 消息缓存达到batch.size指定的大小。
  2. 距离上一次消息发送时间间隔达到了linger.ms指定的值。
  3. 调用flush()方法,会立刻触发发送,并阻塞到当前缓冲区发送完毕。
  4. 调用close(),触发发送,发送完毕后关闭。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值