Java 由许多使用者触发的Kafka broker内存泄漏

Java 由许多使用者触发的Kafka broker内存泄漏,java,memory-leaks,apache-kafka,Java,Memory Leaks,Apache Kafka,我正在构建一个Java8应用程序,该应用程序在卡夫卡主题中查询一条消息。每个请求都会创建一个新的消费者对象(独立于任何现有的消费者对象),该对象轮询我的卡夫卡主题,获取一条记录,然后关闭消费者。这种情况每天发生约20万次,每个请求都独立于所有其他请求,因此我认为我无法重用消费者。基本上,用户请求来自主题的消息,并为其创建消费者,然后关闭。这种情况平均每秒发生约2次,但是任意的,因此可能发生10次/秒或1次/小时,无法知道 过了一会儿,Kafka服务器(不是运行代码的服务器,而是运行Kafka的实

我正在构建一个Java8应用程序,该应用程序在卡夫卡主题中查询一条消息。每个请求都会创建一个新的
消费者
对象(独立于任何现有的
消费者
对象),该对象轮询我的卡夫卡主题,获取一条记录,然后关闭
消费者
。这种情况每天发生约20万次,每个请求都独立于所有其他请求,因此我认为我无法重用消费者。基本上,用户请求来自主题的消息,并为其创建消费者,然后关闭。这种情况平均每秒发生约2次,但是任意的,因此可能发生10次/秒或1次/小时,无法知道

过了一会儿,Kafka服务器(不是运行代码的服务器,而是运行Kafka的实际服务器)上的堆大小变得很大,垃圾收集无法清除它。最终,GC占用的CPU时间比其他任何东西都多,在我重新启动Kafka之前,一切都会崩溃

以下是导致问题的代码的近似版本,其中
while(true)
近似真实行为(在生产中,消费者不是在while循环中创建的,而是在用户请求主题消息时按需创建的):

Properties=newproperties();
props.put(“bootstrap.servers”,“SERVER_IP:9092”);
props.put(“session.timeout.ms”,30000);
props.put(“enable.auto.commit”、“true”);
props.put(“auto.commit.interval.ms”,1000);
while(true){
消费者=新卡夫卡消费者(道具);
TopicPartition tp=新的TopicPartition(“主题”,0);
consumer.assign(Arrays.asList(tp));
consumer.seekToEnd(Arrays.asList(tp));
//我已经把内存泄漏的范围缩小到这一行
消费者记录cr=消费者投票(1000);
//如果删除此行^,则不会发生内存泄漏
/*获取一条记录的代码*/
消费者。取消订阅();
consumer.close();
}
在20个JVM上运行此代码会在大约20分钟内导致内存泄漏。Kafka服务器上的堆(蓝色)和GC暂停时间(绿色)如下所示:

我是否做错了什么(或者有更好的方法来解决这个问题),或者当大量消费者被创建和关闭时,这是卡夫卡的一个缺陷


我在客户端运行Kafka 0.10.2.1,在服务器上运行Kafka 0.10.2.0。

您每天轮询Kafka约20万次,即每小时轮询约8千次,每分钟轮询约140次,每秒轮询两次-为什么每次都要创建(并关闭)消费者的新实例?只需按照您所需的时间间隔安排触发
KafkaConsumer
(您可以使用JDK
ScheduledExecutorService
),并重用相同的使用者实例

,无论您收到的请求数量和频率如何,您仍然可以重用KafkaConsumer实例。您只能在请求到达时进行轮询,但不需要每次都创建和关闭使用者

话虽如此,如果内存使用量增加且GCs未回收,您对消费者的使用可能会暴露出代理上的内存管理问题。我看到过这样的问题:当生产者被频繁地循环使用时,代理耗尽了直接内存。因此,很可能还有改进的余地。可能最好在issues.apache.org上提交一个问题,以便查看。

Kafka 2.4.0(可能是以前的版本)存在资源泄漏,其中一些MBean未在Consumer.close()上注销

也许你最初问这个问题时就是这样,当然,托尼在上面对你的问题的评论中建议这是原因


创建消费者的成本相对较高。您正在一次又一次地进行此操作。@ftr请注意,堆消耗在服务器上,仅创建消耗不会造成泄漏。轮询部分似乎造成了泄漏。代理还为每个新的消费者做家务。@ftr如果我删除进行轮询的行,内存泄漏就会消失,所以你是对的,但是如果没有轮询,代理会处理家务(并以创建消费者为代价)。所以这不是一个理想的解决方案,但我仍然认为卡夫卡在我现在使用的方式中存在一个缺陷。我认为每个消费者的JMX指标永远不会从消费者地图中删除/清除。这会随着时间的推移造成泄漏。使用JMX:echo-e“open$(pgrep-f kafkasserver)\nbeans\n“| java-jar~/Downloads/jmxterm-1.0-alpha-4-uber.jar | grep client id=consumer | wc-l``检查消费者条目数的命令我编辑了这个问题,以便更好地解释:在生产中,我无法控制请求发生的时间(2秒、100秒、1小时)。while循环只是复制行为和内存泄漏。我可能会尝试另一种方法,但我仍然认为卡夫卡在我现在使用的方式中存在缺陷。老实说,我无法评论“卡夫卡缺陷”部分。我确实意识到while循环是为了模拟行为,以及您无法控制生产设置的事实。但我仍然不明白的是‘每个请求都独立于所有其他请求,因此我认为我不能重用消费者’——这是问题的根源。
Properties props = new Properties();
props.put("bootstrap.servers", "SERVER_IP:9092");
props.put("session.timeout.ms", 30000);
props.put("enable.auto.commit", "true");
props.put("auto.commit.interval.ms", 1000);

while(true){
    Consumer<String, String> consumer = new KafkaConsumer<>(props);
    TopicPartition tp = new TopicPartition("TOPIC", 0);
    consumer.assign(Arrays.asList(tp));
    consumer.seekToEnd(Arrays.asList(tp));

    // I've narrowed down the memory leak to this line
    ConsumerRecords<String, String> cr = consumer.poll(1000); 
    // If I remove this line ^, the memory leak does not happen

    /* CODE TO GET ONE RECORD */

    consumer.unsubscribe();
    consumer.close();
}