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

问题说明

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

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

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

问题出现点

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

  1. public Future<RecordMetadata> send(ProducerRecord<K, V> record, Callback callback) {
  2. // intercept the record, which can be potentially modified; this method does not throw exceptions
  3. ProducerRecord<K, V> interceptedRecord = this.interceptors.onSend(record);
  4. return doSend(interceptedRecord, callback);
  5. }
  6.  
  7. /**
  8. * Implementation of asynchronously send a record to a topic.
  9. */
  10. private Future<RecordMetadata> doSend(ProducerRecord<K, V> record, Callback callback) {
  11. TopicPartition tp = null;
  12. try {
  13. throwIfProducerClosed();
  14. // first make sure the metadata for the topic is available
  15. ClusterAndWaitTime clusterAndWaitTime;
  16. try {
  17. clusterAndWaitTime = waitOnMetadata(record.topic(), record.partition(), maxBlockTimeMs); //出现问题的地方
  18. } catch (KafkaException e) {
  19. if (metadata.isClosed())
  20. throw new KafkaException("Producer closed while send in progress", e);
  21. throw e;
  22. }
  23. ...
  24. } catch (ApiException e) {
  25. ...
  26. }
  27. }
  28.  
  29. private ClusterAndWaitTime waitOnMetadata(String topic, Integer partition, long maxWaitMs) throws InterruptedException {
  30. // add topic to metadata topic list if it is not there already and reset expiry
  31. Cluster cluster = metadata.fetch();
  32.  
  33. if (cluster.invalidTopics().contains(topic))
  34. throw new InvalidTopicException(topic);
  35.  
  36. metadata.add(topic);
  37.  
  38. Integer partitionsCount = cluster.partitionCountForTopic(topic);
  39. // Return cached metadata if we have it, and if the record's partition is either undefined
  40. // or within the known partition range
  41. if (partitionsCount != null && (partition == null || partition < partitionsCount))
  42. return new ClusterAndWaitTime(cluster, 0);
  43.  
  44. long begin = time.milliseconds();
  45. long remainingWaitMs = maxWaitMs;
  46. long elapsed;
  47.  
  48. //一直获取topic的元数据信息,直到获取成功,若获取时间超过maxWaitMs,则抛出异常
  49. do {
  50. if (partition != null) {
  51. log.trace("Requesting metadata update for partition {} of topic {}.", partition, topic);
  52. } else {
  53. log.trace("Requesting metadata update for topic {}.", topic);
  54. }
  55. metadata.add(topic);
  56. int version = metadata.requestUpdate();
  57. sender.wakeup();
  58. try {
  59. metadata.awaitUpdate(version, remainingWaitMs);
  60. } catch (TimeoutException ex) {
  61. // Rethrow with original maxWaitMs to prevent logging exception with remainingWaitMs
  62. throw new TimeoutException(
  63. String.format("Topic %s not present in metadata after %d ms.",
  64. topic, maxWaitMs));
  65. }
  66. cluster = metadata.fetch();
  67. elapsed = time.milliseconds() - begin;
  68. if (elapsed >= maxWaitMs) { //判断执行时间是否大于maxWaitMs
  69. throw new TimeoutException(partitionsCount == null ?
  70. String.format("Topic %s not present in metadata after %d ms.",
  71. topic, maxWaitMs) :
  72. String.format("Partition %d of topic %s with partition count %d is not present in metadata after %d ms.",
  73. partition, topic, partitionsCount, maxWaitMs));
  74. }
  75. metadata.maybeThrowException();
  76. remainingWaitMs = maxWaitMs - elapsed;
  77. partitionsCount = cluster.partitionCountForTopic(topic);
  78. } while (partitionsCount == null || (partition != null && partition >= partitionsCount));
  79.  
  80. return new ClusterAndWaitTime(cluster, elapsed);
  81. }

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

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

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

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

问题解决

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

  1. import java.util.concurrent.Callable
  2. import java.util.concurrent.ExecutorService
  3. import java.util.concurrent.Executors
  4. import org.apache.kafka.clients.producer.{Callback, KafkaProducer, ProducerRecord, RecordMetadata}
  5.  
  6. class ProducerF[K,V](kafkaProducer: KafkaProducer[K,V]) {
  7.  
  8. val executor: ExecutorService = Executors.newScheduledThreadPool(1)
  9.  
  10. def sendAsync(producerRecord: ProducerRecord[K,V], callback: Callback) = {
  11. executor.submit(new Callable[RecordMetadata]() {
  12. def call = kafkaProducer.send(producerRecord, callback).get()
  13. })
  14. }
  15. }

  

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. Telegram APIs中文介绍

    Telegram APIs 我们为开发者提供了两种API,Bot API (机器人API) 允许你很轻松地用Telegram的接口创建程序,Telegram API 和DLib 允许你创建定制自己的T ...

  2. Django models 单表查询

    从数据库中查询出来的结果一般是一个集合,这个集合叫做 QuerySet 1. 查看Django QuerySet执行的SQL .query.__str__()或 .query属性打印执行的sql语句 ...

  3. RDD算子的使用

    TransformationDemo.scala import org.apache.spark.{HashPartitioner, SparkConf, SparkContext} import s ...

  4. 3.InfluxDB-InfluxQL基础语法教程--数据说明

    下面是本次演示的示例数据 表名:h2o_feet 数据示例: 数据描述 : 表h2o_feet中所存储的是6分钟时间区间内的数据. 该表有一个tag,即location,该tag有两个值,分别为coy ...

  5. Django restframework 序列化之 ModelSerializer 小记

    首先介绍一下相关概念 序列化器(Serializer) 1. 自定义型:  继承rest_framework.serializers.Serializer 2. 模型类型:  继承rest_frame ...

  6. NVIDIA-SMI参数解析

    第一栏的Fan:N/A是风扇转速,从0到100%之间变动,这个速度是计算机期望的风扇转速,实际情况下如果风扇堵转,可能打不到显示的转速.有的设备不会返回转速,因为它不依赖风扇冷却而是通过其他外设保持低 ...

  7. eth0: ERROR while getting interface flags: No such device的解决方法、Linux怎么修改IP以及ping不通的处理方法

    首先输入ifconfig命令查看当前的ip信息 发现没有eth0这个网卡设备,有ens33 接着输入命令:ifconfig ens33 192.168.2.110    --  修改临时ip地址,系统 ...

  8. SpringBoot设置支持跨域请求

    跨域:现代浏览器出全的考虑,在http/https请求时必须遵守同源策略,否则即使跨域的http/https 请求,默认情况下是被禁止的,ip(域名)不同.或者端口不同.协议不同(比如http.htt ...

  9. 理解Javascript执行过程

    Javascript是一种解释型的动态语言. 在程序中,有编译型语言和解释型语言.那么什么是编译型语言,什么是解释型语言呢? 编译型语言: 它首先将源代码编译成机器语言,再由机器运行机器码(二进制). ...

  10. 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 ...