最近发现一个Kafka producer异步发送在某些情况会阻塞主线程,后来在排查解决问题过程中发现这可以算是Kafka的一个说明不恰当的地方。

问题说明

在很多场景下我们会使用异步方式来发送Kafka的消息,会使用KafkaProducer中的以下方法:

public Future<RecordMetadata> send(ProducerRecord<K, V> record, Callback callback) {}

根据文档的说明它是一个异步的发送方法,按道理不管如何它都不应该阻塞主线程,但实际中某些情况下会出现阻塞线程,比如broker未正确运行,topic未创建等情况,有些时候我们不需要对发送的结果做保证,但是如果出现阻塞的话,会影响其他业务逻辑。

问题出现点

从KafkaProducer send这个方法声明上看并没有什么问题,那么我们来看一下她的具体实现:

public Future<RecordMetadata> send(ProducerRecord<K, V> record, Callback callback) {
// intercept the record, which can be potentially modified; this method does not throw exceptions
ProducerRecord<K, V> interceptedRecord = this.interceptors.onSend(record);
return doSend(interceptedRecord, callback);
} /**
* Implementation of asynchronously send a record to a topic.
*/
private Future<RecordMetadata> doSend(ProducerRecord<K, V> record, Callback callback) {
TopicPartition tp = null;
try {
throwIfProducerClosed();
// first make sure the metadata for the topic is available
ClusterAndWaitTime clusterAndWaitTime;
try {
clusterAndWaitTime = waitOnMetadata(record.topic(), record.partition(), maxBlockTimeMs); //出现问题的地方
} catch (KafkaException e) {
if (metadata.isClosed())
throw new KafkaException("Producer closed while send in progress", e);
throw e;
}
...
} catch (ApiException e) {
...
}
} private ClusterAndWaitTime waitOnMetadata(String topic, Integer partition, long maxWaitMs) throws InterruptedException {
// add topic to metadata topic list if it is not there already and reset expiry
Cluster cluster = metadata.fetch(); if (cluster.invalidTopics().contains(topic))
throw new InvalidTopicException(topic); metadata.add(topic); Integer partitionsCount = cluster.partitionCountForTopic(topic);
// Return cached metadata if we have it, and if the record's partition is either undefined
// or within the known partition range
if (partitionsCount != null && (partition == null || partition < partitionsCount))
return new ClusterAndWaitTime(cluster, 0); long begin = time.milliseconds();
long remainingWaitMs = maxWaitMs;
long elapsed; //一直获取topic的元数据信息,直到获取成功,若获取时间超过maxWaitMs,则抛出异常
do {
if (partition != null) {
log.trace("Requesting metadata update for partition {} of topic {}.", partition, topic);
} else {
log.trace("Requesting metadata update for topic {}.", topic);
}
metadata.add(topic);
int version = metadata.requestUpdate();
sender.wakeup();
try {
metadata.awaitUpdate(version, remainingWaitMs);
} catch (TimeoutException ex) {
// Rethrow with original maxWaitMs to prevent logging exception with remainingWaitMs
throw new TimeoutException(
String.format("Topic %s not present in metadata after %d ms.",
topic, maxWaitMs));
}
cluster = metadata.fetch();
elapsed = time.milliseconds() - begin;
if (elapsed >= maxWaitMs) { //判断执行时间是否大于maxWaitMs
throw new TimeoutException(partitionsCount == null ?
String.format("Topic %s not present in metadata after %d ms.",
topic, maxWaitMs) :
String.format("Partition %d of topic %s with partition count %d is not present in metadata after %d ms.",
partition, topic, partitionsCount, maxWaitMs));
}
metadata.maybeThrowException();
remainingWaitMs = maxWaitMs - elapsed;
partitionsCount = cluster.partitionCountForTopic(topic);
} while (partitionsCount == null || (partition != null && partition >= partitionsCount)); return new ClusterAndWaitTime(cluster, elapsed);
}

从它的实现我们可以看出,会导致线程阻塞的原因在于以下这个逻辑:

private ClusterAndWaitTime waitOnMetadata(String topic, Integer partition, long maxWaitMs) throws InterruptedException

通过KafkaProducer 执行send的过程中需要先获取Metadata,而这是一个不断循环的操作,直到获取成功,或者抛出异常。

其实Kafka本意这么实现并没有问题,因为你要发送消息的前提就是能获取到border和topic的信息,问题在于这个send对外暴露的是Future的方法,但是内部实现却是有阻塞的,那么在有些时候没有考虑到这种情况,一旦出现border或者topic异常,将会阻塞系统线程,导致系统响应变慢,直到奔溃。

问题解决

其实解决这个问题很简单,就是单独创建几个线程用于消息发送,这样即使遇到意外情况,也只会阻塞几个线程,不会引起系统线程大面积阻塞,不可用,具体实现:

import java.util.concurrent.Callable
import java.util.concurrent.ExecutorService
import java.util.concurrent.Executors
import org.apache.kafka.clients.producer.{Callback, KafkaProducer, ProducerRecord, RecordMetadata} class ProducerF[K,V](kafkaProducer: KafkaProducer[K,V]) { val executor: ExecutorService = Executors.newScheduledThreadPool(1) def sendAsync(producerRecord: ProducerRecord[K,V], callback: Callback) = {
executor.submit(new Callable[RecordMetadata]() {
def call = kafkaProducer.send(producerRecord, callback).get()
})
}
}

  

Kafka producer异步发送在某些情况会阻塞主线程,使用时候慎重的更多相关文章

  1. kafka producer batch 发送消息

    1. 使用 KafkaProducer 发送消息,是按 batch 发送的,producer 首先把消息放入 ProducerBatch 中: org.apache.kafka.clients.pro ...

  2. 要求两个异步任务都完成后, 才能回到主线程:dispatch_group_t

    需求:两个异步任务都完成后, 回到主线程 /** 1.下载图片1和图片2 2.将图片1和图片2合并成一张图片后显示到imageView上 思考: * 下载图片 : 子线程 * 等2张图片都下载完毕后, ...

  3. 关于高并发下kafka producer send异步发送耗时问题的分析

    最近开发网关服务的过程当中,需要用到kafka转发消息与保存日志,在进行压测的过程中由于是多线程并发操作kafka producer 进行异步send,发现send耗时有时会达到几十毫秒的阻塞,很大程 ...

  4. ActiveMQ producer同步/异步发送消息

    http://activemq.apache.org/async-sends.html producer发送消息有同步和异步两种模式,可以通过代码配置: ((ActiveMQConnection)co ...

  5. 【原创】Kafka producer原理 (Scala版同步producer)

    本文分析的Kafka代码为kafka-0.8.2.1.另外,由于Kafka目前提供了两套Producer代码,一套是Scala版的旧版本:一套是Java版的新版本.虽然Kafka社区极力推荐大家使用J ...

  6. 【原创】kafka producer源代码分析

        Kafka 0.8.2引入了一个用Java写的producer.下一个版本还会引入一个对等的Java版本的consumer.新的API旨在取代老的使用Scala编写的客户端API,但为了兼容性 ...

  7. 【转】Kafka producer原理 (Scala版同步producer)

    转载自:http://www.cnblogs.com/huxi2b/p/4583249.html     供参考 本文分析的Kafka代码为kafka-0.8.2.1.另外,由于Kafka目前提供了两 ...

  8. 详解Kafka Producer

    上一篇文章我们主要介绍了什么是 Kafka,Kafka 的基本概念是什么,Kafka 单机和集群版的搭建,以及对基本的配置文件进行了大致的介绍,还对 Kafka 的几个主要角色进行了描述,我们知道,不 ...

  9. Apache Kafka Producer For Beginners

    在我们上一篇Kafka教程中,我们讨论了Kafka Cluster.今天,我们将通过示例讨论Kafka Producer.此外,我们将看到KafkaProducer API和Producer API. ...

随机推荐

  1. Mysql中的sql是如何执行的 --- 极客时间学习笔记

    MySQL中的SQL是如何执行的 MySQL是典型的C/S架构,也就是Client/Server架构,服务器端程序使用的mysqld.整体的MySQL流程如下图所示: MySQL是有三层组成: 连接层 ...

  2. centos6.5 yum搭建安装linux+apache+mysql+php环境

    一.脚本YUM源安装: 1.yum install wget                                                     #安装下载工具wget2.wget ...

  3. Linux 找文件

    最简单的可以使用 find find . -name "libxxx.so" 还可以使用 locate libxxx.so

  4. 201871010101-陈来弟《面向对象程序设计(Java)》第八周学习总结

    实验七 接口的定义与使用 第一部分:理论知识 一.接口.lambda和内部类:  Comparator与comparable接口: 1.comparable接口的方法是compareTo,只有一个参数 ...

  5. %STSADM% -o addsolution -filename AEMediaPlayerWebpart.wsp

    SET STSADM="c:\Program Files\Common Files\Microsoft Shared\Web Server Extensions\16\bin\STSADM. ...

  6. 【PL/SQL 卡】

    DBA给解决好了,但我不在场,要问一下

  7. spring讲解

    今日先简单介绍一下Spring bean 的 5 种效果域,然后详细介绍 singleton 和 prototype 这两种最常用的效果域. JavaSpring Bean的五种效果域 效果域的种类 ...

  8. Hello 2019 D 素因子贡献法计算期望 + 概率dp + 滚动数组

    https://codeforces.com/contest/1097/problem/D 题意 给你一个n和k,问n经过k次操作之后留下的n的期望,每次操作n随机变成一个n的因数 题解 概率dp计算 ...

  9. [LeetCode] 552. Student Attendance Record II 学生出勤记录之二

    Given a positive integer n, return the number of all possible attendance records with length n, whic ...

  10. TJOI 2015 概率论(生成函数)

    题意 ​ 求一棵随机生成的有根二叉树(节点无标号,各种不同构的情况随机出现)叶子结点个数的期望. 思路 ​ 用生成函数做是个好题. ​ 我们考虑设 \(n\) 个节点,所有不同构二叉树叶子结点的总和为 ...