Kafka 发送消息流程

客户端的几个组件:

  • KafkaProducer      KafkaProducer 是一个生产者客户端的进程,通过该对象启动生产者来发送消息
  • RecordAccumulator   记录收集器,收集发送的消息缓存到客户端
  • Sender                            读取缓存,进行一些必要的转换    批量传到selector
  • Selector                       传送消息到broker

 一条消息首先需要确定要被存储到那个 partition 对应的双端队列上;其次,存储消息的双端队列是以批的维度存储的,即 N 条消息组成一批,一批消息最多存储 N 条,超过后则新建一个组来存储新消息;其次,新来的消息总是从左侧写入,即越靠左侧的消息产生的时间越晚;最后,只有当一批消息凑够 N 条后才会发送给 Broker,否则不会发送到 Broker 上

 

确定消息的 partition 位置

消息可分为两种,一种是指定了 key 的消息,一种是没有指定 key 的消息。

对于指定了 key 的消息,partition 位置的计算方式为:Utils.murmur2(key) % numPartitions,即先对 key 进行哈希计算,然后在于 partition 个数求余,从而得到该条消息应该被存储在哪个 partition 上。

对于没有指定 key 的消息,partition 位置的计算方式为:采用 round-robin 方式确定 partition 位置,即采用轮询的方式,平均的将消息分布到不同的 partition 上,从而避免某些 partition 数据量过大影响 Broker 和消费端性能

 

Sender 的主要工作

第一步:扫描记录收集器中满足条件的批数据,然后将 partition -> 批数据映射转换成 BrokerId -> N 批数据的映射。第二步:Sender 线程会为每个 BrokerId 创建一个客户端请求,然后将请求交给 NetWorkClient,由 NetWrokClient 去真正发送网络请求到 Broker。

NetWorkClient 的工作内容

Sender 线程准备好要发送的数据后,交由 NetWorkClient 来进行网络相关操作。主要包括客户端与服务端的建连、发送客户端请求、接受服务端响应。完成如上一系列的工作主要由如下方法完成。

  1. reday()方法。从记录收集器获取准备完毕的节点,并连接所有准备好的节点。

  2. send()方法。为每个节点创建一个客户端请求,然后将请求暂时存到节点对应的 Channel(通道)中。

  3. poll()方法。该方法会真正轮询网络请求,发送请求给服务端节点和接受服务端的响应

 

Kafka消息发送涉及两个线程协同工作,包括主线程和Sender线程。 主线程方面,首先会将业务数据封装成ProducerRecord对象,之后调用`send()`方法把消息放入RecordAccumulator(消息收集器)中暂存。消息收集器为每个分区都维护了一个`Deque<ProducerBatch>`类型的双端队列,ProducerBatch是ProducerRecord的集合,批量发送有利于提升吞吐量和降低网络影响。生产者客户端使用`java.io.ByteBuffer`在发送消息之前进行消息保存,并且维护了一个BufferPool实现ByteBuffer的复用,不过该缓存池只针对特定大小(`batch.size`指定)的ByteBuffer进行管理,对于过大的消息缓存无法重复利用。每次追加一条ProducerRecord消息时,会寻找或新建对应的双端队列,从其尾部获取一个ProducerBatch,判断当前消息的大小是否可以写入该批次中。若可以写入则写入;若不可以写入,则新建一个ProducerBatch,判断该消息大小是否超过客户端参数配置`batch.size`的值,不超过则以`batch.size`建立新的ProducerBatch,方便进行缓存重复利用;若超过,则以计算的消息大小建立对应的ProducerBatch,缺点是该内存不能被复用 [^1][^3]。 Sender线程负责将消息信息构成请求,并最终执行网络I/O。该线程从RecordAccumulator中取出消息,将其处理为`<Node, List<ProducerBatch>>`的形式,其中Node表示集群的broker节点。接着进一步将`<Node, List<ProducerBatch>>`转化为`<Node, Request>`形式,此时才可以向服务端发送数据。在发送之前,Sender线程将消息以`Map<NodeId, Deque<Request>>`的形式保存到InFlightRequests中进行缓存,还可以通过其获取`leastLoadedNode`,即当前Node中负载压力最小的一个,以实现消息的尽快发出 [^1][^3]。 需要注意的是,KafkaProducer是线程安全的,多个线程间可以共享使用同一个KafkaProducer对象 [^1]。 ```python # 这里用伪代码简单示意流程 # 主线程部分 def main_thread(data): producer_record = encapsulate_to_producer_record(data) send_to_record_accumulator(producer_record) # 消息收集器部分 def record_accumulator(producer_record): partition_deque = get_partition_deque(producer_record.partition) producer_batch = get_or_create_producer_batch(partition_deque, producer_record) if can_write(producer_batch, producer_record): write_to_batch(producer_batch, producer_record) else: new_batch = create_new_producer_batch(producer_record) write_to_batch(new_batch, producer_record) # Sender线程部分 def sender_thread(): node_batches = process_from_record_accumulator() node_requests = convert_to_requests(node_batches) cache_in_inflight_requests(node_requests) least_loaded_node = get_least_loaded_node() send_requests(least_loaded_node, node_requests) ```
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值