Kafka consumer.shutdown 关闭无效,后台线程一直在消费

Kafka停不掉shutdown关闭不了问题

原因是卡在了consumer.close()方法里面,它会提交offset信息,如果网络中断或者kafka服务器有问题导致提交不了offset,则consumer.close方法会一直卡住(不停的循环尝试提交offset,永不中断)。


参见:Kafka poll一直等待的bug:

https://issues.apache.org/jira/browse/KAFKA-4189?jql=project%20%3D%20KAFKA%20AND%20resolution%20%3D%20Unresolved%20AND%20component%20%3D%20consumer%20ORDER%20BY%20priority%20DESC


https://issues.apache.org/jira/browse/KAFKA-3172?jql=project%20%3D%20KAFKA%20AND%20resolution%20%3D%20Unresolved%20AND%20component%20%3D%20consumer%20ORDER%20BY%20priority%20DESC



解决方法:目前还没有好的办法,只能将offset的自动提交改成手动提交offset。但是,我写了一个程序可以在调用consumer.close后将线程强行杀死,作为临时解决方案。



kafka shutdown停止很慢问题


在数据量大的时候,consumer一次抓取数据的数据很多,进入到业务处理的数据可能有很多,

假设一次poll有1万条数据进入业务程序,而且业务程序是和poll绑定在一起线程同步执行的,假设平均每条数据,执行业务程序花费100ms,

那么poll一次的数据,至少要执行 1w*0.1s = 1000s = 16.67分钟。

所以,在数据量大的时候,停止一个线程(需要先等待业务程序处理完数据),可能要十几分钟。

 

shutdown问题解决方案

1、改成异步处理数据,consumer取出来的数据,放到BlockQueue中,由异步线程去处理,当异步线程处理不过来时,阻塞consumer,调用consumer.pause()方法avoid group management rebalance,代码如下(来源于Spring-Kafka):

1
2
3
4
5
6
// avoid group management rebalance due to a slow consumer
this.consumer.pause(this.assignedPartitions.toArray(new TopicPartition[this.assignedPartitions.size()]));
 
public void onPartitionsAssigned(Collection<TopicPartition> partitions) {
    this.assignedPartitions = partitions;
}


2、如果是同步执行数据处理,考虑提高业务程序 处理数据的速度。


3、同步处理数据,但是改成手动提交offset,当shutdown的时候,poll的数据不需要全部处理,只需要记录处理的位置即可。代码示例如下:

1
2
3
4
5
6
7
8
9
list data = consumer.poll();
for(record: data) { 
    if(shutdown) {  // 收到shutdown命令后立即停止,未处理的数据将丢弃
        break;
    }
   deal(record);
   saveTopicOffset(record);
}
submitDealtDataOffset();



要为每个线程创建一个独立的 KafkaConsumer 实例,可以使用线程局部变量(ThreadLocal)来实现。ThreadLocal 可以让每个线程都拥有一个独立的变量副本,从而保证每个线程都有自己的 KafkaConsumer 实例。 下面是一个示例代码片段,展示了如何使用 ThreadLocal 创建独立的 KafkaConsumer 实例: ```java public class KafkaConsumerThread implements Runnable { private static final String BOOTSTRAP_SERVERS = "your-bootstrap-server"; private static final String GROUP_ID = "your-consumer-group"; private static final String TOPIC = "your-topic"; private final ThreadLocal<KafkaConsumer<String, String>> threadLocalConsumer = new ThreadLocal<>(); @Override public void run() { // 创建 KafkaConsumer 实例 KafkaConsumer<String, String> consumer = new KafkaConsumer<>(createConsumerConfig()); threadLocalConsumer.set(consumer); try { // 订阅主题 consumer.subscribe(Collections.singletonList(TOPIC)); while (true) { ConsumerRecords<String, String> records = consumer.poll(Duration.ofMillis(100)); for (ConsumerRecord<String, String> record : records) { // 处理消费到的消息 System.out.println("Thread: " + Thread.currentThread().getId() + ", Received message: " + record.value()); } } } finally { // 关闭 KafkaConsumer 实例 consumer.close(); threadLocalConsumer.remove(); } } private Properties createConsumerConfig() { Properties props = new Properties(); props.put("bootstrap.servers", BOOTSTRAP_SERVERS); props.put("group.id", GROUP_ID); props.put("enable.auto.commit", "true"); // 根据需求设置自动提交或手动提交 // 其他配置项... return props; } } ``` 在上述示例中,我们创建了一个 `KafkaConsumerThread` 类实现了 `Runnable` 接口,用于作为线程的执行逻辑。在 `run()` 方法中,我们首先创建了一个 KafkaConsumer 实例,并将其存储在 `threadLocalConsumer` 中。然后,我们订阅了指定的主题,并在消费消息的循环中处理每条消息。最后,在 `finally` 块中关闭 KafkaConsumer 实例,并从 ThreadLocal 中移除。 要启动多个线程并创建独立的 KafkaConsumer 实例,您可以使用以下代码: ```java public class Main { public static void main(String[] args) { int numThreads = 5; // 指定线程数量 ExecutorService executor = Executors.newFixedThreadPool(numThreads); for (int i = 0; i < numThreads; i++) { executor.submit(new KafkaConsumerThread()); } executor.shutdown(); } } ``` 在上述示例中,我们使用 `ExecutorService` 创建了一个固定数量的线程池,并提交了 `KafkaConsumerThread` 实例作为任务。每个线程都将拥有自己独立的 KafkaConsumer 实例。 请注意,根据您的具体需求,您可能需要根据不同的线程KafkaConsumer 进行更复杂的配置和处理。这里只提供了一个基本的示例来说明如何为每个线程创建独立的 KafkaConsumer 实例。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值