Kafka核心源码分析-生产者-KafkaProducer

KafkaProducer1.发送消息流程2.实例3.kafkaProducer分析3.1 ProducerInterceptors3.2 集群元数据3.3 Serialiser&DeseriaLizer3.4 Partitioner1.发送消息流程生产者与服务端完成一次网络通信步骤如下:生产者客户端应用程序产生消息客户端连接对象将消息包装到请求中,发送到服务端服务短...
摘要由CSDN通过智能技术生成

1.发送消息流程

生产者与服务端完成一次网络通信步骤如下:

  1. 生产者客户端应用程序产生消息

  2. 客户端连接对象将消息包装到请求中,发送到服务端

  3. 服务短连接对象负责接收请求,并将消息以文件形式存储

  4. 服务端返回响应结果给生产者客户端
    在这里插入图片描述
    以上流程对应到代码中如下:

  5. 创建ProducerRecord对象,此对象包含目标主题和要发送的内容,还可以指定键或者分区

  6. 生产者对ProducerRecord对象进行序列化

  7. 分区器接收到消息后,先来选择一个分区

  8. 这条记录被添加到一个记录批次里,这个批次会被发送到相同的主题和分区上

  9. 独立线程负责把这些记录批次,发送到相应的broker上

  10. broker就是服务器,在接收到消息时,会返回一个响应,如果成功,返回一个RecordMetaData对象,它包含了主题和分区信息,以及记录在分区里的偏移量。如果失败,就会返回一个错误

  11. 生产者如果接收到错误之后,会尝试重新发送,几次尝试后,还是失败,就返回错误信息

生产者发送的代码在clients包下,下面我们详细讲解下发送流程,附有源码和流程图,方便大家理解。

2.实例

要往Kafka里面写入消息,首先要创建一个生产者对象,并设置一些属性。
配置属性:

  1. bootstrap.servers
    该属性指定broker的地址清单,地址的格式为host:port
  2. key.serializer
    指定了序列化的类,生产者会使用这个类把键对象序列化成字节数组
  3. value.serializer
    指定了序列化的类,生产者会使用这个类把值序列化
  4. client.id
    客户端的id
    发送消息的方式:
  5. 发送并忘记
    我们把消息发送给服务器,但并不关心它是否正常到达
  6. 同步发送
    我们使用send()方法发送消息,它会返回一个Future对象,调用get()方法进行等待,就可以知道消息是否发送成功
  7. 异步发送
    我们调用send()方法,并指定一个回调 函数,服务器在返回响应时调用该函数

我们来看下源码中的例子

public class Producer extends Thread {
   
    private final KafkaProducer<Integer, String> producer;
    private final String topic;
    //定义消息的发送方式:异步发送还是同步发送
    private final Boolean isAsync;

    public Producer(String topic, Boolean isAsync) {
   
        Properties props = new Properties();
        //Kafka服务端的主机名和端口号
        props.put("bootstrap.servers", "localhost:9092");
        //客户端的id
        props.put("client.id", "DemoProducer");
        //key的序列化方法
        props.put("key.serializer", "org.apache.kafka.common.serialization.IntegerSerializer");
        //value的序列化方法
        props.put("value.serializer", "org.apache.kafka.common.serialization.StringSerializer");
        producer = new KafkaProducer<>(props);
        this.topic = topic;
        this.isAsync = isAsync;
    }

    public void run() {
   
        //消息的key
        int messageNo = 1;
        while (true) {
   
            //消息的value
            String messageStr = "Message_" + messageNo;
            long startTime = System.currentTimeMillis();
            if (isAsync) {
    // Send asynchronously 异步发送
                //第一个参数是ProducerRecord对象,封装了目标Topic、消息的key,消息的value
                //第二个参数是一个Callback对象,当生产者接收到Kafka发来的ACK确认消息的时候,
                //会调用此CallBack对象的onCompletion()方法,实现回调
                producer.send(new ProducerRecord<>(topic,
                    messageNo,
                    messageStr), new DemoCallBack(startTime, messageNo, messageStr));
            } else {
    // Send synchronously 同步发送
                try {
   
                    //send()返回的是一个Future<RecordMetadata>这里通过Future.get()方法,阻塞当前线程
                    //等待Kafka服务端的ACK响应
                    producer.send(new ProducerRecord<>(topic,
                        messageNo,
                        messageStr)).get();
                    System.out.println("Sent message: (" + messageNo + ", " + messageStr + ")");
                } catch (InterruptedException | ExecutionException e) {
   
                    e.printStackTrace();
                }
            }
            //递增消息的Key
            ++messageNo;
        }
    }
}

//回调对象
class DemoCallBack implements Callback {
   
    //开始发送消息的时间戳
    private final long startTime;
    //消息的Key
    private final int key;
    //消息的value
    private final String message;

    public DemoCallBack(long startTime, int key, String message) {
   
        this.startTime = startTime;
        this.key = key;
        this.message = message;
    }

    /**
     * 生产者成功发送消息,收到Kafka服务端发来的ACK确认消息后,会调用此回调函数
     *
     * @param metadata  生产者发送的消息的元数据,如果发送过程中出现异常,此参数为null
     * @param exception 发送过程中出现的异常,如果发送成功,则此参数为null
     */
    public void onCompletion(RecordMetadata metadata, Exception exception) {
   
        long elapsedTime = System.currentTimeMillis() - startTime;
        if (metadata != null) {
   
            //RecordMetadata中包含了分区信息、offset信息等
            System.out.println(
                "message(" + key + ", " + message + ") sent to partition(" + metadata.partition() +
                    "), " +
                    "offset(" + metadata.offset() + ") in " + elapsedTime + " ms");
        } else {
   
            exception.printStackTrace();
        }
    }
}

KafkaProducer只用了一个send方法,就可以完成同步和异步两种模式的消息发送。这是因为send方法返回的是一个Future。基于Future,我们可以实现同步或异步的消息发送语义。

  • 同步。调用send返回Future时,需要立即调用get,因为Future.get在没有返回结果时会一直阻塞。
  • 异步。提供一个回调,调用send后,可以继续发送消息而不用等待。当有结果返回时,会自动执行回调函数。

源码中的注释写的很清楚了,就不再赘述了,大家有兴趣也可以把源码从github上下载下来。

3.kafkaProducer分析

我们来看下发送消息时的类图
在这里插入图片描述消息发送的流程如上,主要有3个模块,主线程,负责生产消息并序列化,选择分区。然后将消息存放到RecordAccumulator中,相当于记录的缓冲区,当缓冲区满了之后,sender线程会从缓冲区中读取数据,并发送到Kafka的服务器上。

  1. ProducerInterceptors对消息进行拦截
  2. Serializer对消息的key和value进行序列化
  3. Patitioner为消息选择合适的Partition
  4. RecordAccumulator收集消息,实现批量发送
  5. Sernder线程从RecordAccumulator中收集消息,实现批量发送
  6. 构造ClientRequest
  7. 将ClientRequest交给NetworkClient,准备发送
  8. NetworkClient将请求放入KafkaChannel
  9. 执行网络IO,发送请求
  10. 收到响应,调用ClientRequest的回调函数
  11. 调用RecordBatch的回调函数,最终调用每个消息上注册的回调函数

代码调用的时序图如下:
在这里插入图片描述
解释一下上图的主要流程:

1.1:调用ProducerInterceptors的onSend()方法,对消息进行加工
1.2:实际发送doSend()方法
1.2.1:waitOnMetadata判断是否要更新元数据,以及wakeUp sender线程去更新元数据
1.2.2:序列化key
1.2.3:序列化value
1.2.4:获取分区信息
1.2.5:保证记录的size小于最大的请求size,小于totalSize
1.2.6:将记录追加到RecordAccumulator中
1.2.7:wakeup sender线程,去发送数据

现在呢,我们大体流程已经都了解了,下面我们深入去看下里面涉及到的部分逻辑

3.1 ProducerInterceptors

ProducerInterceptors里面维护了一个ProducerInterceptor的list。这个类,主要是在消息被发送到Kafka服务器前,或者接收到了kafka对消息处理信息之后,进行加工处理。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值