Kafka producer异步发送在某些情况会阻塞主线程,使用时候慎重
最近发现一个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异步发送在某些情况会阻塞主线程,使用时候慎重的更多相关文章
- kafka producer batch 发送消息
1. 使用 KafkaProducer 发送消息,是按 batch 发送的,producer 首先把消息放入 ProducerBatch 中: org.apache.kafka.clients.pro ...
- 要求两个异步任务都完成后, 才能回到主线程:dispatch_group_t
需求:两个异步任务都完成后, 回到主线程 /** 1.下载图片1和图片2 2.将图片1和图片2合并成一张图片后显示到imageView上 思考: * 下载图片 : 子线程 * 等2张图片都下载完毕后, ...
- 关于高并发下kafka producer send异步发送耗时问题的分析
最近开发网关服务的过程当中,需要用到kafka转发消息与保存日志,在进行压测的过程中由于是多线程并发操作kafka producer 进行异步send,发现send耗时有时会达到几十毫秒的阻塞,很大程 ...
- ActiveMQ producer同步/异步发送消息
http://activemq.apache.org/async-sends.html producer发送消息有同步和异步两种模式,可以通过代码配置: ((ActiveMQConnection)co ...
- 【原创】Kafka producer原理 (Scala版同步producer)
本文分析的Kafka代码为kafka-0.8.2.1.另外,由于Kafka目前提供了两套Producer代码,一套是Scala版的旧版本:一套是Java版的新版本.虽然Kafka社区极力推荐大家使用J ...
- 【原创】kafka producer源代码分析
Kafka 0.8.2引入了一个用Java写的producer.下一个版本还会引入一个对等的Java版本的consumer.新的API旨在取代老的使用Scala编写的客户端API,但为了兼容性 ...
- 【转】Kafka producer原理 (Scala版同步producer)
转载自:http://www.cnblogs.com/huxi2b/p/4583249.html 供参考 本文分析的Kafka代码为kafka-0.8.2.1.另外,由于Kafka目前提供了两 ...
- 详解Kafka Producer
上一篇文章我们主要介绍了什么是 Kafka,Kafka 的基本概念是什么,Kafka 单机和集群版的搭建,以及对基本的配置文件进行了大致的介绍,还对 Kafka 的几个主要角色进行了描述,我们知道,不 ...
- Apache Kafka Producer For Beginners
在我们上一篇Kafka教程中,我们讨论了Kafka Cluster.今天,我们将通过示例讨论Kafka Producer.此外,我们将看到KafkaProducer API和Producer API. ...
随机推荐
- Telegram APIs中文介绍
Telegram APIs 我们为开发者提供了两种API,Bot API (机器人API) 允许你很轻松地用Telegram的接口创建程序,Telegram API 和DLib 允许你创建定制自己的T ...
- Django models 单表查询
从数据库中查询出来的结果一般是一个集合,这个集合叫做 QuerySet 1. 查看Django QuerySet执行的SQL .query.__str__()或 .query属性打印执行的sql语句 ...
- RDD算子的使用
TransformationDemo.scala import org.apache.spark.{HashPartitioner, SparkConf, SparkContext} import s ...
- 3.InfluxDB-InfluxQL基础语法教程--数据说明
下面是本次演示的示例数据 表名:h2o_feet 数据示例: 数据描述 : 表h2o_feet中所存储的是6分钟时间区间内的数据. 该表有一个tag,即location,该tag有两个值,分别为coy ...
- Django restframework 序列化之 ModelSerializer 小记
首先介绍一下相关概念 序列化器(Serializer) 1. 自定义型: 继承rest_framework.serializers.Serializer 2. 模型类型: 继承rest_frame ...
- NVIDIA-SMI参数解析
第一栏的Fan:N/A是风扇转速,从0到100%之间变动,这个速度是计算机期望的风扇转速,实际情况下如果风扇堵转,可能打不到显示的转速.有的设备不会返回转速,因为它不依赖风扇冷却而是通过其他外设保持低 ...
- eth0: ERROR while getting interface flags: No such device的解决方法、Linux怎么修改IP以及ping不通的处理方法
首先输入ifconfig命令查看当前的ip信息 发现没有eth0这个网卡设备,有ens33 接着输入命令:ifconfig ens33 192.168.2.110 -- 修改临时ip地址,系统 ...
- SpringBoot设置支持跨域请求
跨域:现代浏览器出全的考虑,在http/https请求时必须遵守同源策略,否则即使跨域的http/https 请求,默认情况下是被禁止的,ip(域名)不同.或者端口不同.协议不同(比如http.htt ...
- 理解Javascript执行过程
Javascript是一种解释型的动态语言. 在程序中,有编译型语言和解释型语言.那么什么是编译型语言,什么是解释型语言呢? 编译型语言: 它首先将源代码编译成机器语言,再由机器运行机器码(二进制). ...
- Redis笔记2-Redis安装、配置
下载安装包 wget http://download.redis.io/releases/redis-4.0.8.tar.gz解压 tar xzvf redis-4.0.8.tar.gz安装 cd r ...