1.问题

batch.size和linger.ms是对kafka producer性能影响比较大的两个参数。batch.size是producer批量发送的基本单位,默认是16384Bytes,即16kB;lingger.ms是sender线程在检查batch是否ready时候,判断有没有过期的参数,默认大小是0ms。

那么producer是按照batch.size大小批量发送消息呢,还是按照linger.ms的时间间隔批量发送消息呢?这里先说结论:其实满足batch.size和ling.ms之一,producer便开始发送消息。

2.源码分析

首先sender线程主要代码如下,我们主要关心sender线程阻塞的情况:

void run(long now) {
Cluster cluster = metadata.fetch(); // result.nextReadyCheckDelayMs表示下次检查是否ready的时间,也是//selecotr会阻塞的时间
RecordAccumulator.ReadyCheckResult result = this.accumulator.ready(cluster, now); if (result.unknownLeadersExist)
this.metadata.requestUpdate(); Iterator<Node> iter = result.readyNodes.iterator(); long notReadyTimeout = Long.MAX_VALUE;
while (iter.hasNext()) {
Node node = iter.next();
if (!this.client.ready(node, now)) {
iter.remove();
notReadyTimeout = Math.min(notReadyTimeout, this.client.connectionDelay(node, now));
}
} Map<Integer, List<RecordBatch>> batches = this.accumulator.drain(cluster,
result.readyNodes,
this.maxRequestSize,
now);
if (guaranteeMessageOrder) {
for (List<RecordBatch> batchList : batches.values()) {
for (RecordBatch batch : batchList)
this.accumulator.mutePartition(batch.topicPartition);
}
} List<RecordBatch> expiredBatches = this.accumulator.abortExpiredBatches(this.requestTimeout, now); for (RecordBatch expiredBatch : expiredBatches)
this.sensors.recordErrors(expiredBatch.topicPartition.topic(), expiredBatch.recordCount); sensors.updateProduceRequestMetrics(batches);
List<ClientRequest> requests = createProduceRequests(batches, now); // 暂且只关心result.nextReadyCheckDelayMs
long pollTimeout = Math.min(result.nextReadyCheckDelayMs, notReadyTimeout);
if (result.readyNodes.size() > 0) {
log.trace("Nodes with data ready to send: {}", result.readyNodes);
log.trace("Created {} produce requests: {}", requests.size(), requests);
pollTimeout = 0;
}
for (ClientRequest request : requests)
client.send(request, now); // poll最终会调用selector,pollTimeout也就是selector阻塞的时间
this.client.poll(pollTimeout, now);
}

selector

private int select(long ms) throws IOException {
if (ms < 0L)
throw new IllegalArgumentException("timeout should be >= 0"); if (ms == 0L)
return this.nioSelector.selectNow();
else
return this.nioSelector.select(ms);
}

我们可以从实例化一个新的KafkaProducer开始分析(还没有调用send方法),在sender线程调用accumulator#ready(..)时候,会返回result,其中包含selector可能要阻塞的时间。由于还没有调用send方法,所以Deque<RecordBatch>为空,所以result中包含的nextReadyCheckDelayMs也是最大值,这个时候selector会一直阻塞。

public ReadyCheckResult ready(Cluster cluster, long nowMs) {
Set<Node> readyNodes = new HashSet<Node>();
// 初始化为最大值
long nextReadyCheckDelayMs = Long.MAX_VALUE;
boolean unknownLeadersExist = false; boolean exhausted = this.free.queued() > 0;
for (Map.Entry<TopicPartition, Deque<RecordBatch>> entry : this.batches.entrySet()) {
TopicPartition part = entry.getKey();
Deque<RecordBatch> deque = entry.getValue(); Node leader = cluster.leaderFor(part);
if (leader == null) {
unknownLeadersExist = true;
} else if (!readyNodes.contains(leader) && !muted.contains(part)) {
synchronized (deque) {
RecordBatch batch = deque.peekFirst();
if (batch != null) {
boolean backingOff = batch.attempts > 0 && batch.lastAttemptMs + retryBackoffMs > nowMs;
long waitedTimeMs = nowMs - batch.lastAttemptMs;
long timeToWaitMs = backingOff ? retryBackoffMs : lingerMs; // 和linger.ms有关
long timeLeftMs = Math.max(timeToWaitMs - waitedTimeMs, 0);
boolean full = deque.size() > 1 || batch.records.isFull();
boolean expired = waitedTimeMs >= timeToWaitMs;
boolean sendable = full || expired || exhausted || closed || flushInProgress();
if (sendable && !backingOff) {
readyNodes.add(leader);
} else {
nextReadyCheckDelayMs = Math.min(timeLeftMs, nextReadyCheckDelayMs);
}
}
}
}
} return new ReadyCheckResult(readyNodes, nextReadyCheckDelayMs, unknownLeadersExist);
}

然后我们调用send方法往内存中放入了一条数据,由于是新建的一个RecordBatch,所以会唤醒sender线程

KafkaProducer#doSend(...)

if (result.batchIsFull || result.newBatchCreated) {
log.trace("Waking up the sender since topic {} partition {} is either full or getting a new batch", record.topic(), partition);
this.sender.wakeup();
}

这个时候会唤醒阻塞在selector#select(..)的sender线程,sender线程又运行到accumulator#ready(..),由于Deque<RecordBatch>有值,所以返回的result包含的nextReadyCheckDelayMs不再是最大值,而是和linger.ms有关的值。也就是时候selector会z最多阻塞lingger.ms后就返回,然后再次轮询。

也就是说当Deque<RecordBatch>不为空的时候,sender线程会最多阻塞linger.ms时间;Deque<RecordBatch>为空的时候,sender线程会阻塞Long.MAX_VALUE时间;一旦调用了KafkaProduer#send(..)将消息放到内存中,新建了个RecordBatch,则会将sender线wakeup。

另外从上面的代码,即KafkaProducer#doSend(...)中也可以看到,如果有一个RecordBatch满了,也会调用Sender#wakeup(..),所以综上所述:只要满足linger.ms和batch.size满了就会激活sender线程来发送消息。

kafka producer的batch.size和linger.ms的更多相关文章

  1. TIPS FOR IMPROVING PERFORMANCE OF KAFKA PRODUCER

    When we are talking about performance of Kafka Producer, we are really talking about two different t ...

  2. kafka producer 源码总结

    kafka producer可以总体上分为两个部分: producer调用send方法,将消息存放到内存中 sender线程轮询的从内存中将消息通过NIO发送到网络中 1 调用send方法 其实在调用 ...

  3. Kafka遇到30042ms has passed since batch creation plus linger time at org.apache.kafka.clients.producer.internals.FutureRecordMetadata.valueOrError(FutureRecordMetadata.java:94)

    问题描述: 运行生产者线程的时候显示如下错误信息: Expiring 1 record(s) for XXX-0: 30042 ms has passed since batch creation p ...

  4. kafka producer源码

    producer接口: /** * Licensed to the Apache Software Foundation (ASF) under one or more * contributor l ...

  5. Kafka Producer Consumer

    Producer API org.apache.kafka.clients.producer.KafkaProducer props.put("bootstrap.servers" ...

  6. kafka producer实例

    1. 定义要发送的消息User POJO package lenmom.kafkaproducer; public class User { public String name; public in ...

  7. Kafka producer介绍

    Kafka 0.9版本正式使用Java版本的producer替换了原Scala版本的producer.本文着重讨论新版本producer的设计原理以及基本的使用方法. 新版本Producer 首先明确 ...

  8. Kafka设计解析(十四)Kafka producer介绍

    转载自 huxihx,原文链接 Kafka producer介绍 Kafka 0.9版本正式使用Java版本的producer替换了原Scala版本的producer.本文着重讨论新版本produce ...

  9. kafka producer自定义partitioner和consumer多线程

    为了更好的实现负载均衡和消息的顺序性,Kafka Producer可以通过分发策略发送给指定的Partition.Kafka Java客户端有默认的Partitioner,平均的向目标topic的各个 ...

随机推荐

  1. Vue 源码解读(8)—— 编译器 之 解析(上)

    特殊说明 由于文章篇幅限制,所以将 Vue 源码解读(8)-- 编译器 之 解析 拆成了上下两篇,所以在阅读本篇文章时请同时打开 Vue 源码解读(8)-- 编译器 之 解析(下)一起阅读. 前言 V ...

  2. strtok()出现segment fault的错误

    在写一个简易的 shell 时,需要将命令行的命令通过空格分割成一个个字符串参数,这里我使用了 strtok() 函数,然后遇到了 segment fault 的错误. 出现问题的代码如下: 终于寻找 ...

  3. 程序与CPU,内核,寄存器,缓存,RAM,ROM、总线、Cache line缓存行的作用和他们之间的联系?

    目录 缓存 什么是缓存 L1.L2.L3 为什么要设置那么多缓存.缓存在cup内还是cup外 MESI协议----主流的处理缓存和主存数据不一样问题 Cache line是什么已经 对编程中数组的影响 ...

  4. Java基础--序列化和反序列化

    作用:在很多应用中,需要对某些对象进行序列化,让它们离开内存空间,入住物理硬盘,以便长期保存.比如最常见的是Web服务器中的Session对象,当有 10万用户并发访问,就有可能出现10万个Sessi ...

  5. 进程&线程(二):Thread相关方法与属性

    学习自:python进程.线程.协程 - 张岩林 - 博客园 1.threading.Thread Thread方法 方法(使用方法为Thread.xxx) 说明 start() 激活线程 getNa ...

  6. Python:HTTP请求头headers信息的查询

    GET和POST请求后会返回一个response对象,可通过key.values等访问字典中元素的方式来访问该response对象headers中的各种属性 r=requests.get(url,he ...

  7. 在 k8s 以外的分布式环境中使用 Dapr

    在Dapr 文档和实践案例中多是推荐采用k8s, 其实我目前也是在k8s 上操作的,有公有云TKE,AKS,还有私有云的Rancher ,它并没有传闻中的那么难,而且我认为它非常容易上手.不过,我还是 ...

  8. w10环境vs2017,vs2019配置Opengl快捷方法

    最近,计算机图形学老师向我们布置了任务,配置自己的opengl.百度之后我发现很多教程和方法尝试之后,我发现一种简单的方法来分享给大家. 首先我的软件配置是w10专业版系统+Visual Stdio ...

  9. ffmpeg修改视频文件的分辨率

    在本文中,我们将展示如何调整任何视频文件的大小. 这种方法是在Linux系统(几乎任何发行版)中调整视频文件大小的最佳方法之一,也是Windows和Mac用户的绝佳替代方案. 更改视频文件的分辨率将是 ...

  10. python实现解析markdown文档中的图片,并且保存到本地~

    背景 前阵子简书好像说是凉了,搞得我有点小慌,毕竟我的大部分博客都是放在简书上面的,虽然简书提供了打包导出功能,但是只能导出文字,图片的话还是存在简书服务器上面,再加上我一直想要重新做一个个人博客,于 ...