KafkaProducer发送流程源码分析

目录

一、前言

二、ProducerRecord分析

三、Sender发送线程

3.1 Sender

3.2 sendProducerData

3.3 client.poll


一、前言

本节主要是对kafka发送消息流程的源码分析,如果想了解kafka的基本原理,可以参考另外一篇文章:kafka从入门到不放弃_fish_tao-CSDN博客

下图是kafka发送消息的简单程序

//生产者客户端的基本配置
Properties props = new Properties();
props.put(ProducerConfig.BOOTSTRAP_SERVERS_CONFIG, "localhost:9092");
props.put(ProducerConfig.KEY_SERIALIZER_CLASS_CONFIG, StringSerializer.class.getName());
props.put(ProducerConfig.VALUE_SERIALIZER_CLASS_CONFIG, StringSerializer.class.getName());
props.put(ProducerConfig.MAX_BLOCK_MS_CONFIG, 1000 * 60 * 60);
props.put(ProducerConfig.ACKS_CONFIG, "1");

KafkaProducer<String, String> producer = new KafkaProducer<>(props);
String data = "hello, i am kafka_producer";
ProducerRecord<String, String> record = new ProducerRecord<>(KafkaContants.KAFKA_TOPIC, data);
//发送消息
producer.send(record);
//关闭生产者
producer.close();

ProducerRecord作为send()方法的参数,该类包含以下几个参数,其中topic和value是必传的,其他字段可以自行选择。

private final String topic;         //主题
private final Integer partition;    //分区数
private final Headers headers;      //头部信息
private final K key;                //消息的key值
private final V value;              //具体消息内容
private final Long timestamp;       //发送时间戳

kafka发送过程中,主要有两个线程负责,一个是应用程序的主线程通过调用send()方法,将消息写入到RecordAccumulator中,内部是一个ConcurrentMap,然后由sender线程轮询RecordAccumulator中的消息,如果达到要求,就会将该消息发送给kakfa集群。

在对KafkaProducer实例化的时候会初始化一些参数,比如实例化自定义的拦截器、解析序列化方式、实例化分区器、RecordAccumulator对象,创建Sender线程并且启动。

二、ProducerRecord分析

​1、send()的入口,返回值为异步Future,我们可以通过get()方法进行阻塞,直到发送成功后。还有个参数为Callback,如果选择get()阻塞会对我们的应用产生影响,所以我们可以通过自定义Callback,在回调中对发送失败的进行额外处理。

​2、发送消息首先执行拦截器,kafka默认没有实现拦截器,需要我们根据业务场景自己定义。

3、doSend()方法包括了将消息发送到 RecordAccumulator中的整个流程(图中的红色序号和下面的序号一一对应):

1)首先判断Sender线程是否在并且运行中,如果非直接抛异常;

2)获取kafka集群元数据,如果本地已经有集群数据,直接从缓存中取,反之,通知Sender线程获取元数据,并且对应用传入的partition进行校验;

3)对传入的key值进行序列化

4)对发送消息的value值进行序列化

5)如果发送消息时指定partition,不会执行分区器,没有指定会通过分区器选择出消息将要发送到哪个partition,kafka2.4版本后采用StickyPartitioning Strategy(粘性分区策略),如果往相同topic写数据,是同一批消息会指定相同的partition。

6)估算发送消息的大小,如果超过maxRequestSize或者totalMemorySize会提示错误,这两个参数可以通过max.request.size和buffer.memory进行配置。

7)实例化回调函数和拦截器

 然后执行accumulator.append()方法,将消息写入到内存中,下面具体分析append()方法

1)RecordAccumulator中保存的是一个ConcurrentMap,key为TopicPartition,即topic+partition,value是一个双端队列Deque<ProducerBatch>,首先判断map中是否有值,如果有直接返回value,如果没有初始化一个ArrayDeque(),写入到map中,然后返回;

2)进入tryAppend()方法中,获取链表最后一个node,如果没有则返回null;

3)执行append()方法时,传入的参数abortOnNewBatch为true,所以会执行第三步,new一个RecordAppendResult对象直接返回,且设置参数abortForNewBatch为true。

前面讲到在doSend()方法,执行append()方法时,返回的result.abortForNewBatch为true,此时说明还未将消息写入到内存中,进入第二步if循环体中,再次执行append()方法,判断map中是否有值,前面已经实例化一个ArrayDeque,但是该双端列表为null,此次传入的abortOnNewBatch为false,所以会执行下图的(1);

1)为ProducerBatch分配内存空间;

2) 再次调用tryAppend方法写入到内存中,如果写成功了直接返回,如果写失败了会执行(3)创建ProducerBatch然后调用ProducerBatch.tryAppend()方法;

注意:正常流程执行(1)的时候说明前面返回的PrudecerBatch为bull,为啥还要再重复执行(2)呢?因为(1)分配内存不是线程同步,所以有可能有其他的线程跟当前线程相同的TopicPartition,且已经再RecordAccumulator中写入了数据,这时如果在执行(2)的时候有可能追加成功。

ProducerBatch.tryAppend()方法会校验该ProducerBatch中是否已满,如果已满则返回null,重新实例化ProducerBatch,如果没满则追加到ProducerBatch中,最后释放分配的内存成功返回。

doSend()整个流程图如下

三、Sender发送线程

3.1 Sender

Sender是一个实现Runnable的线程,在应用调用send()方法的时会启动。

1)正常情况下 ,会循环执行while里面的runOnce()方法;

2)如果Sender线程关闭,防止内存中的数据丢失,在forceClose(默认为false)为false的情况下,将内存中的数据发送给kafka。只有当客户端KafkaProducer执行close()方法时会更新forceClose为true;

3)将未发送的消息清空,数据丢失;

4)关闭KafkaClient网络通信;

 runOnce()方法中实现数据发送功能。前面都是和Kafka事务相关的工作,我们暂且省略,重点时最后sendProducerData()和client.poll()两个方法;

3.2 sendProducerData

sendProducerData()从内存中获取达到发送要求的数据,写入到kafka channel中

1)获取kafka集群元数据,主要是集群节点信息,最终发送是要确定往哪一个节点上写数据;

2)获取内存中哪些数据达到发送要求,返回对应的节点信息,比如topic-test_0,主题为topic-test,分区为0,返回的是该主题分区为0的leader所在节点信息;

3)从内存中获取需要发送的消息数据,创建发送请求体;

 1)首先将要发送的节点信息保存到InFlightRequests中,缓存已经发出但未响应的请求,然后将消息写入到KafkaChannel中,注册write监听事件;

3.3 client.poll

执行KafkaClient.poll()方法,通过Kafka封装的Selector,通过遍历pollSelectionKeys,判断是否达到可写状态,然后执行java中nio发送到kafka集群,完成发送操作。

runOnce()流程图如下所示:

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值