Java 当主题中没有更多记录时,如何在Kafka Consumer中刷新数据批处理

Java 当主题中没有更多记录时,如何在Kafka Consumer中刷新数据批处理,java,apache-kafka,kafka-consumer-api,Java,Apache Kafka,Kafka Consumer Api,考虑这个Kafka使用者,它从topic接收数据,将其缓冲到PreparedStatement中,当对100K条记录进行批处理时,它向DB发出INSERT查询 直到数据仍在输入之前,这种方法都能很好地工作。但是,例如,当缓冲了20K条记录并且没有更多的记录传入时,它仍然会等待更多的80K条记录,直到in刷新语句。但是如果在一段时间后停止,我想冲洗那些20K。我该怎么做?我看不出有什么办法能抓住它 例如,在使用基于librdkafka的PHP rdkafka扩展的PHP中,当到达分区末尾时,我会得

考虑这个Kafka使用者,它从topic接收数据,将其缓冲到PreparedStatement中,当对100K条记录进行批处理时,它向DB发出INSERT查询

直到数据仍在输入之前,这种方法都能很好地工作。但是,例如,当缓冲了20K条记录并且没有更多的记录传入时,它仍然会等待更多的80K条记录,直到in刷新语句。但是如果在一段时间后停止,我想冲洗那些20K。我该怎么做?我看不出有什么办法能抓住它

例如,在使用基于librdkafka的PHP rdkafka扩展的PHP中,当到达分区末尾时,我会得到
RD_KAFKA_RESP_ERR_PARTITION_EOF
,所以在这种情况下很容易钩住缓冲区刷新

我试图简化代码,以便只剩下重要的部分

公共类TestConsumer{
专用终端连接;
专用最终倒计时闩锁关闭闩锁;
私人最终卡夫卡消费者;
private int processedCount=0;
公共测试消费者(连接){
这个连接=连接;
this.consumer=new-KafkaConsumer(getConfig()、new-StringDeserializer()、new-ProtoDeserializer(Message.parser());
this.shutdownLatch=新的倒计时闩锁(1);
}
public void execute(){
编制报表;
试一试{
语句=getPreparedStatement();
}捕获(SQLE异常){
抛出新的运行时异常(e);
}
Runtime.getRuntime().addShutdownHook(新线程(()->{
提交(声明);
consumer.wakeup();
}));
consumer.subscribe(Collections.singletonList(“source.topic”);
试一试{
while(true){
ConsumerRecords记录=consumer.poll(持续时间(Long.MAX_值));
记录。forEach(记录->{
Message=record.value();
试一试{
fillBatch(语句、消息);
语句addBatch();
}捕获(SQLE异常){
抛出新的运行时异常(e);
}
});
processedCount+=记录数。计数();
如果(已处理计数>100000){
提交(声明);
}
}
}捕获(唤醒异常e){
//别理我,我们要关门了
}最后{
consumer.close();
shutdownLatch.countDown();
}
}
私有无效提交(PreparedStatement语句){
试一试{
语句。executeBatch();
consumer.commitSync();
processedCount=0;
}捕获(SQLE异常){
抛出新的运行时异常(e);
}
}
受保护的void fillBatch(PreparedStatement语句,Message消息)引发SQLException{
试一试{
语句.setTimestamp(1,新的时间戳(message.getTime()*1000L));
}捕获(未知后异常e){
抛出新的运行时异常(e);
}
}

我理解您的问题如下:

  • 您想使用来自卡夫卡的消息

  • 在内存中最多可存储10万条记录

  • 批量提交到数据库

  • 但您只想等待t秒(假设为10秒)

这可以通过使用Kafka内置的消费者批处理以一种非常高效和可靠的方式实现。前提是您可以以某种方式预测消息的平均大小(以字节为单位)

在Kafka使用者配置上,您可以设置以下内容:

fetch.min.bytes
=>这应该是100k x消息的平均大小

fetch.max.wait.ms
=>这是您的超时时间,单位为毫秒(例如5000秒等待)

max.partition.fetch.bytes
=>每个分区的最大数据量。这有助于优化总提取大小

max.poll.records
=>单个轮询返回的最大记录数..可以设置为100K

fetch.max.bytes
=>如果要设置单个请求的上限

这样,如果符合定义的字节大小,您可以获得多达100K条记录,但需要等待可配置的毫秒数


一旦轮询返回记录,您可以一次性保存并重复。

为什么不通过记住开始时间来添加手动超时,然后在每次迭代后检查,即
if(Duration.between(startTime,LocalDateTime.now()).toMillis()>timeoutMs){commit(statement);break;}
正如@daniu所提到的,您可以添加一个超时,这样每当达到计数或超时时,您就可以执行该语句。这是您可以在许多集成框架中找到的,例如CamelThank for comments!因此正确的方法是手动计算持续时间并调整
poll()
的持续时间,因此它不会永远阻塞。哇!尽管我知道这些配置属性,但我根本没有想到这个解决方案。但它确实是一个干净的解决方案。我会尝试并接受它,如果它能达到我认为会达到的效果。谢谢!我正在成功地使用它,有时用于优化消费者和br之间的网络呼叫okers,有时出于吞吐量的原因。在您的情况下,100K的轮询记录大小听起来很高。请注意kafka consumer端的网络负载和超时刚刚尝试过,结果非常令人满意,谢谢!现在唯一的挑战是找到消息字节大小:)