KafkaProducer 创建一个 KafkaThread 来运行 Sender.run 方法。

1. 发送消息的入口在 KafkaProducer#doSend 中,但其实是把消息加入到 batches 中:

kafka 生产者是按 batch 发送消息,RecordAccumulator 类有个变量 ConcurrentMap<TopicPartition, Deque<ProducerBatch>> batches,
KafkaProducer#doSend 方法会把当前的这条消息放入到 ProducerBatch 中。然后调用 Sender#wakeup 方法,尝试唤醒阻塞的 io 线程。

2. 从 batches 取出数据发送,入口在 Sender.run,主要的逻辑抽象为 3 步:

2.1 RecordAccumulator#drain 取出数据

// 每个分区只取一个 ProducerBatch
public Map<Integer, List<ProducerBatch>> drain(Cluster cluster,
Set<Node> nodes,
int maxSize,
long now) {
if (nodes.isEmpty())
return Collections.emptyMap(); Map<Integer, List<ProducerBatch>> batches = new HashMap<>();
for (Node node : nodes) {
int size = 0;
// 取出该节点负责的分区
List<PartitionInfo> parts = cluster.partitionsForNode(node.id());
List<ProducerBatch> ready = new ArrayList<>();
/* to make starvation less likely this loop doesn't start at 0 */
int start = drainIndex = drainIndex % parts.size();
// 遍历每个分区
do {
PartitionInfo part = parts.get(drainIndex);
TopicPartition tp = new TopicPartition(part.topic(), part.partition());
// Only proceed if the partition has no in-flight batches.
if (!muted.contains(tp)) {
Deque<ProducerBatch> deque = getDeque(tp);
if (deque != null) {
synchronized (deque) {
ProducerBatch first = deque.peekFirst();
if (first != null) {
boolean backoff = first.attempts() > 0 && first.waitedTimeMs(now) < retryBackoffMs;
// Only drain the batch if it is not during backoff period.
if (!backoff) {
if (size + first.estimatedSizeInBytes() > maxSize && !ready.isEmpty()) {
// there is a rare case that a single batch size is larger than the request size due
// to compression; in this case we will still eventually send this batch in a single
// request
break;
} else {
ProducerIdAndEpoch producerIdAndEpoch = null;
boolean isTransactional = false;
if (transactionManager != null) {
if (!transactionManager.isSendToPartitionAllowed(tp))
break; producerIdAndEpoch = transactionManager.producerIdAndEpoch();
if (!producerIdAndEpoch.isValid())
// we cannot send the batch until we have refreshed the producer id
break; isTransactional = transactionManager.isTransactional(); if (!first.hasSequence() && transactionManager.hasUnresolvedSequence(first.topicPartition))
// Don't drain any new batches while the state of previous sequence numbers
// is unknown. The previous batches would be unknown if they were aborted
// on the client after being sent to the broker at least once.
break; int firstInFlightSequence = transactionManager.firstInFlightSequence(first.topicPartition);
if (firstInFlightSequence != RecordBatch.NO_SEQUENCE && first.hasSequence()
&& first.baseSequence() != firstInFlightSequence)
// If the queued batch already has an assigned sequence, then it is being
// retried. In this case, we wait until the next immediate batch is ready
// and drain that. We only move on when the next in line batch is complete (either successfully
// or due to a fatal broker error). This effectively reduces our
// in flight request count to 1.
break;
} ProducerBatch batch = deque.pollFirst();
if (producerIdAndEpoch != null && !batch.hasSequence()) {
// If the batch already has an assigned sequence, then we should not change the producer id and
// sequence number, since this may introduce duplicates. In particular,
// the previous attempt may actually have been accepted, and if we change
// the producer id and sequence here, this attempt will also be accepted,
// causing a duplicate.
//
// Additionally, we update the next sequence number bound for the partition,
// and also have the transaction manager track the batch so as to ensure
// that sequence ordering is maintained even if we receive out of order
// responses.
batch.setProducerState(producerIdAndEpoch, transactionManager.sequenceNumber(batch.topicPartition), isTransactional);
transactionManager.incrementSequenceNumber(batch.topicPartition, batch.recordCount);
log.debug("Assigned producerId {} and producerEpoch {} to batch with base sequence " +
"{} being sent to partition {}", producerIdAndEpoch.producerId,
producerIdAndEpoch.epoch, batch.baseSequence(), tp); transactionManager.addInFlightBatch(batch);
}
batch.close();
size += batch.records().sizeInBytes();
ready.add(batch);
batch.drained(now);
}
}
}
}
}
}
this.drainIndex = (this.drainIndex + 1) % parts.size();
} while (start != drainIndex);
batches.put(node.id(), ready);
}
return batches;
}

2.2 NetworkClient.send

这里的 send 不是真正的网络发送,先把 ProduceReuquest 序列化成 Send 对象,然后加入到 inFlightRequests 的头部,调用 selector 的 send,实则是 KafkaChannel.setSend()

Send send = request.toSend(nodeId, header);

this.inFlightRequests.add(inFlightRequest);

selector.send(inFlightRequest.send);

一个 NetworkSend 对象对应一个 ProduceRequest,包含一个或多个 ProducerBatch,也就是说一次网络会发送多个 batch,这也是 kafka 吞吐量大的原因之一。

2.3 NetworkClient.poll
真正的网络发送

Selector#pollSelectionKeys 处理网络读写事件,发送消息即写事件,同时把响应存放在 Selector#completedReceives 中
producer 发送消息,如果 acks = -1 和 1,即 producer 请求需要响应,
在 NetworkClient#handleCompletedSends 中,把不需要响应的请求,从 inFlightRequests 中删除
在 NetworkClient#handleCompletedReceives 处理响应
producer 设置了 ack 的值是固定的,producer 要么都需要响应,要么都不需要响应。
新的请求加在头部,收到的响应对应最旧的请求,即尾部的请求。

3. 主要的类
KafkaProducer: 直接暴露给用户的 api 类;Sender: 主要管理 ProducerBatch
NetworkClient: ProducerBatch 是对象,通过网络发送需要序列化,该类管理连接,更接近 io 层
Selector 对 java nio Selector 的封装
KafkaChannel

4. ByteBuffer

// ByteBuffer 的使用
// ByteBuffer 初始是写模式
public static void main(String[] args) throws UnsupportedEncodingException {
// capacity = 512, limit = 512, position = 0
ByteBuffer buffer = ByteBuffer.allocate(512);
buffer.put((byte)'h');
buffer.put((byte)'e');
buffer.put((byte)'l');
buffer.put((byte)'l');
buffer.put((byte)'o'); // limit = position, position = 0
buffer.flip(); // 获取字节数
int len = buffer.remaining();
byte[] dst = new byte[len];
buffer.get(dst);
System.out.println(new String(dst));
// 结论:ByteBuffer 只是对 byte[] 的封装
} //SocketChannel
//输出
//SocketChannel#write(java.nio.ByteBuffer)
//读取输入
//SocketChannel#read(java.nio.ByteBuffer)

kafka 生产者发送消息的更多相关文章

  1. Kafka生产者发送消息的三种方式

    Kafka是一种分布式的基于发布/订阅的消息系统,它的高吞吐量.灵活的offset是其它消息系统所没有的. Kafka发送消息主要有三种方式: 1.发送并忘记 2.同步发送 3.异步发送+回调函数 下 ...

  2. 深入研究RocketMQ生产者发送消息的底层原理

    前言 hello,小伙伴们,王子又来和大家研究RocketMQ的原理了,之前的文章RocketMQ生产部署架构如何设计中,我们已经简单的聊过了生产者是如何发送消息给Broker的. 我们简单回顾一下这 ...

  3. kafka producer 发送消息简介

    kafka 的 topic 由 partition 组成,producer 会根据 key,选择一个 partition 发送消息,而 partition 有多个副本,副本有 leader 和 fol ...

  4. RocketMQ3.2.2生产者发送消息自动创建Topic队列数无法超过4个

    问题现象 RocketMQ3.2.2版本,测试时尝试发送消息时自动创建Topic,设置了队列数量为8: producer.setDefaultTopicQueueNums(8); 同时设置broker ...

  5. kafka producer发送消息 Failed to update metadata after问题

    提示示例: ERROR Error when sending message to topic test with key: null, value: 2 bytes with error: Fail ...

  6. Kafka 学习之路(三)—— Kafka生产者详解

    一.生产者发送消息的过程 首先介绍一下Kafka生产者发送消息的过程: Kafka会将发送消息包装为ProducerRecord对象, ProducerRecord对象包含了目标主题和要发送的内容,同 ...

  7. Kafka 系列(三)—— Kafka 生产者详解

    一.生产者发送消息的过程 首先介绍一下 Kafka 生产者发送消息的过程: Kafka 会将发送消息包装为 ProducerRecord 对象, ProducerRecord 对象包含了目标主题和要发 ...

  8. 入门大数据---Kafka生产者详解

    一.生产者发送消息的过程 首先介绍一下 Kafka 生产者发送消息的过程: Kafka 会将发送消息包装为 ProducerRecord 对象, ProducerRecord 对象包含了目标主题和要发 ...

  9. Kafka学习笔记(6)----Kafka使用Producer发送消息

    1. Kafka的Producer 不论将kafka作为什么样的用途,都少不了的向Broker发送数据或接受数据,Producer就是用于向Kafka发送数据.如下: 2. 添加依赖 pom.xml文 ...

随机推荐

  1. 分布式之redis(转发)

    为什么写这篇文章? 博主的<分布式之消息队列复习精讲>得到了大家的好评,内心诚惶诚恐,想着再出一篇关于复习精讲的文章.但是还是要说明一下,复习精讲的文章偏面试准备,真正在开发过程中,还是脚 ...

  2. wordpress添加index页面跳转链接

    1. 制作page页面 1.1 在themes下的主题目录新建一个page页面 1.2 在wordpress后台新建页面跟在目录页面中相同名字的页面文件 1.3 复制后台页面中的古定链接 1.4 在i ...

  3. 15.Linux-CentOS系统重启网卡ping不通问题(云环境)

    问题: CentOS系统网络不通,重启网卡后能ping通,等一会就又不通. 解决: 在云环境管理平台下,KVM系统的MAC地址,使其重新生成一下.

  4. JavaScript设计模式 样例三 —— 装饰模式

    装饰模式(Decorator Pattern): 定义:在不改变原对象的情况下,动态的给对象添加一些额外的职责.就功能而言,装饰模式相比生成子类更为灵活. 目的:把类的核心职责和装饰功能区分开.可以去 ...

  5. ifconfig-push

    ifconfig-push中的每一对IP地址表示虚拟客户端和服务器的IP端点.它们必须从连续的/30子网网段中获取(这里是/30表示xxx.xxx.xxx.xxx/30,即子网掩码位数为30),以便于 ...

  6. Qt应用程序的发布

    1 Windows平台上的应用程序发布 Windows发布工具 windeployqt.exe是Qt自带的Windows平台发布工具.windeployqt.exe文件在Qt的bin目录下,Qt的每一 ...

  7. Proxy + Reflect 实现 响应的数据变化

    Proxy 对象用于定义基本操作的自定义行为(如属性查找,赋值,枚举,函数调用等) let p = new Proxy(target, handler); get(target, propKey, r ...

  8. overflow:hidden失效问题

    2018-08-03 Questions about work 这几天开发的时候遇到了个问题,如图1. 写了个demo demo 地址 由于页面并没有进行整体缩放,导致在小屏幕手机上显示会有异常.PM ...

  9. 【LOJ#6036】[雅礼集训2017Day4]编码

    传送门 题意简述 判定 n 个含 ? 字符的二进制串是否存在一种把 0/1 填入 ? 中的方案使得任意两个串不具有前缀关系. (一个串最多一个 ?) Sol 二进制串 ,并且一个串最多一个 '?' 很 ...

  10. LeetCode--128--最长连续序列(python)

    给定一个未排序的整数数组,找出最长连续序列的长度. 要求算法的时间复杂度为 O(n). 示例: 输入: [100, 4, 200, 1, 3, 2]输出: 4解释: 最长连续序列是 [1, 2, 3, ...