kafka生产者源码解析

前言

  • 描述版本 0.10.1.1 ,1.x之后版本一些类会有相应的调整优化。
  • 文章学习自《Apache Kafka源码剖析》和实际源码。

概述

  • 整体流程
    初始化-》序列化-》路由分配策略-》写入缓存-》异步发送
  • 流程图
    图一
    消息发送过程中,会涉及两个线程协同工作,主线程将业务数据封装成ProducerRecord对象,然后调用send() 方法写入缓冲区RecordAccumulator。Sender线程负责将消息从里面取出消息并批量发送。

详解

KafkaProduct初始化

Producer<String, String> producer = new KafkaProducer<>(props);
下KafkaProduct实现Producer接口:
send() 发送消息,实际将消息暂存RecordAccumulator,等待发送;
flush() 刷新,等到RecordAccumulator中所有消息发送完成,会阻塞调用线程;
partitionsFor() 负责从Metadata(元数据)中获取指定Topic的分区信息;
close()关闭。

// 构造函数里面主要操作有:反射构建partitionier类、keySerializer类,valueSerializer类。
this.partitioner = config.getConfiguredInstance(ProducerConfig.PARTITIONER_CLASS_CONFIG, Partitioner.class);
...
this.keySerializer = config.getConfiguredInstance(ProducerConfig.KEY_SERIALIZER_CLASS_CONFIG,Serializer.class);

this.valueSerializer = config.getConfiguredInstance(ProducerConfig.VALUE_SERIALIZER_CLASS_CONFIG,
...
// 创建kafka集群元数据
this.metadata = new Metadata(retryBackoffMs, config.getLong(ProducerConfig.METADATA_MAX_AGE_CONFIG), true, clusterResourceListeners);
...
// 创建RecordAccumulator
this.accumulator = new RecordAccumulator(config.getInt(ProducerConfig.BATCH_SIZE_CONFIG),
                    this.totalMemorySize,
                    this.compressionType,
                    config.getLong(ProducerConfig.LINGER_MS_CONFIG),
                    retryBackoffMs,
                    metrics,
                    time);
 ...
 // 创建NetworkClient,这个是KafkaProducter网络 I/O 的核心
 NetworkClient client = new NetworkClient(
                    new Selector(config.getLong(ProducerConfig.CONNECTIONS_MAX_IDLE_MS_CONFIG), this.metrics, time, "producer", channelBuilder),
                    this.metadata,
                    clientId,
                    config.getInt(ProducerConfig.MAX_IN_FLIGHT_REQUESTS_PER_CONNECTION),
                    config.getLong(ProducerConfig.RECONNECT_BACKOFF_MS_CONFIG),
                    config.getInt(ProducerConfig.SEND_BUFFER_CONFIG),
                    config.getInt(ProducerConfig.RECEIVE_BUFFER_CONFIG),
                    this.requestTimeoutMs, time);
 ...
 // 创建并启动Sender线程
 this.sender = new Sender(client,
                    this.metadata,
                    this.accumulator,
                    config.getInt(ProducerConfig.MAX_IN_FLIGHT_REQUESTS_PER_CONNECTION) == 1,
                    config.getInt(ProducerConfig.MAX_REQUEST_SIZE_CONFIG),
                    (short) parseAcks(config.getString(ProducerConfig.ACKS_CONFIG)),
                    config.getInt(ProducerConfig.RETRIES_CONFIG),
                    this.metrics,
                    new SystemTime(),
                    clientId,
                    this.requestTimeoutMs);
            String ioThreadName = "kafka-producer-network-thread" + (clientId.length() > 0 ? " | " + clientId : "");
            this.ioThread = new KafkaThread(ioThreadName, this.sender, true);
            this.ioThread.start();

send()

我们将上文流程图中主线程部分拆解。1至5的操作就是在KafkaProducer的send()方法里完成的。下面我们看下send()调用流程:
在这里插入图片描述

  • onsend() 通过Intercetor对消息进行拦截或修改
    ProducerRecord<K, V> interceptedRecord = this.interceptors == null ? record : this.interceptors.onSend(record);

  • waitOnMetadata() 更新kafka集群信息
    ClusterAndWaitTime clusterAndWaitTime = waitOnMetadata(record.topic(), record.partition(), maxBlockTimeMs);

  • serialize() 序列化消息的key和value
    serializedKey = keySerializer.serialize(record.topic(), record.key());
    serializedValue = valueSerializer.serialize(record.topic(), record.value());

  • partition() 选择合适分区
    int partition = partition(record, serializedKey, serializedValue, cluster);

  • accumulator.append() 将消息追加到缓冲区
    RecordAccumulator.RecordAppendResult result = accumulator.append(tp, timestamp, serializedKey, serializedValue, interceptCallback, remainingWaitMs);
    RecordAccumulator 中有许多设计操作细节,值得大家进一步学习。比如:private final ConcurrentMap<TopicPartition, Deque<RecordBatch>> batches 中Deque一个双向队列的选择。

  • wakeup() 通过Sender线程将RecordAccumulator缓冲区的消息发送出去。
    this.sender.wakeup();

Sender线程run()方法

  • 概述
    Sender线程发送消息,首先会从缓存里筛选出可以向哪些Node节点发送消息,然后更加生产者和各个节点的链接情况(NetworkClient管理),过滤节点,之后生产相应的请求调用NetworkClient发送出去。下面下看run()方法的流程图:
    Sender线程
  • fetch 从Metadata获取集群元数据
    Cluster cluster = metadata.fetch();
  • ready() 筛选哪些节点存在准备好的数据。
    RecordAccumulator.ReadyCheckResult result = this.accumulator.ready(cluster, now);
  • drain() 获取待发送的数据
    Map<Integer, List<RecordBatch>> batches = this.accumulator.drain(cluster, result.readyNodes, this.maxRequestSize, now);
  • createProduceRequests() 将待发送的数据封装成ClientRequest
    List<ClientRequest> requests = createProduceRequests(batches, now);
  • send() 将ClientRequest写入KafkaChannel的send字段,不是真的发送。
    client.send(request, now);
  • poll() 将KafkaChannel.send字段中保存的ClientRequest发送出去,同时还会处理服务端发出的响应,用户自定义的回调函数等。
    this.client.poll(pollTimeout, now);

结尾

在这里插入图片描述
最后补充张总体的UML图。上述简要描述了Kafka生产者的主要流程及源码,其中重要核心的结构及内的设计希望后续能够加以补充。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值