Java apachekafka系统错误处理

Java apachekafka系统错误处理,java,apache-kafka,ibm-cloud,kafka-consumer-api,message-hub,Java,Apache Kafka,Ibm Cloud,Kafka Consumer Api,Message Hub,我们正试图实现Kafka作为我们的MessageBroker解决方案。我们正在IBMBlueMix中部署SpringBoot微服务,其内部MessageBroker实现是Kafka版本0.10。由于我在JMS、ActiveMQ端的经验较多,我想知道在java消费者中处理系统级错误的理想方法应该是什么 下面是我们目前是如何实现它的 消费者财产 enable.auto.commit=false auto.offset.reset=latest 我们使用的是默认属性 max.partition.fe

我们正试图实现Kafka作为我们的MessageBroker解决方案。我们正在IBMBlueMix中部署SpringBoot微服务,其内部MessageBroker实现是Kafka版本0.10。由于我在JMS、ActiveMQ端的经验较多,我想知道在java消费者中处理系统级错误的理想方法应该是什么

下面是我们目前是如何实现它的

消费者财产

enable.auto.commit=false
auto.offset.reset=latest
我们使用的是默认属性

max.partition.fetch.bytes
session.timeout.ms
卡夫卡消费者

我们正在为每个主题旋转3个线程,所有线程都具有相同的groupId,即每个线程一个KafkaConsumer实例。到目前为止,我们只有一个分区。消费者代码在thread类的构造函数中如下所示

kafkaConsumer = new KafkaConsumer<String, String>(properties);

    final List<String> topicList = new ArrayList<String>();
    topicList.add(properties.getTopic());

    kafkaConsumer.subscribe(topicList, new ConsumerRebalanceListener() {

        @Override
        public void onPartitionsRevoked(final Collection<TopicPartition> partitions) {
        }

        @Override
        public void onPartitionsAssigned(final Collection<TopicPartition> partitions) {
            try {
                logger.info("Partitions assigned, consumer seeking to end.");

                for (final TopicPartition partition : partitions) {
                    final long position = kafkaConsumer.position(partition);
                    logger.info("current Position: " + position);

                    logger.info("Seeking to end...");
                    kafkaConsumer.seekToEnd(Arrays.asList(partition));
                    logger.info("Seek from the current position: " + kafkaConsumer.position(partition));
                    kafkaConsumer.seek(partition, position);
                }
                logger.info("Consumer can now begin consuming messages.");
            } catch (final Exception e) {
                logger.error("Consumer can now begin consuming messages.");
            }

        }
    });  
kafkaConsumer=新的kafkaConsumer(属性);
最终列表topicList=新的ArrayList();
添加(properties.getTopic());
订阅(topicList,new ConsumerBalanceListener()){
@凌驾
已取消分区上的公共void(最终集合分区){
}
@凌驾
公共void onPartitionsAssigned(最终集合分区){
试一试{
info(“已分配分区,消费者寻求结束”);
for(最终主题分区:分区){
最终多头仓位=卡夫卡消费仓位(分区);
logger.info(“当前位置:+位置”);
logger.info(“正在寻求结束…”);
kafkaConsumer.seekToEnd(Arrays.asList(partition));
logger.info(“从当前位置搜索:+kafkaConsumer.position(分区));
kafkaConsumer.seek(分区、位置);
}
info(“消费者现在可以开始消费消息了。”);
}捕获(最终异常e){
错误(“消费者现在可以开始消费消息了。”);
}
}
});  
实际读取发生在线程的run方法中

try {
            // Poll on the Kafka consumer every second.
            final ConsumerRecords<String, String> records = kafkaConsumer.poll(1000);


            // Iterate through all the messages received and print their
            // content.
            for (final TopicPartition partition : records.partitions()) {

                final List<ConsumerRecord<String, String>> partitionRecords = records.records(partition);
                logger.info("consumer is alive and is processing   "+ partitionRecords.size() +" records");
                for (final ConsumerRecord<String, String> record : partitionRecords) {
                    logger.info("processing topic  "+ record.topic()+" for key "+record.key()+" on offset "+ record.offset());

                    final Class<? extends Event> resourceClass = eventProcessors.getResourceClass();
                    final Object obj = converter.convertToObject(record.value(), resourceClass);
                    if (obj != null) {
                        logger.info("Event: " + obj + " acquired by  " + Thread.currentThread().getName());
                        final CommsEvent event = resourceClass.cast(converter.convertToObject(record.value(), resourceClass));
                        final MessageResults results = eventProcessors.processEvent(event
                                );
                        if ("Success".equals(results.getStatus())) {
                            // commit the processed message which changes
                            // the offset
                            kafkaConsumer.commitSync();
                            logger.info("Message processed sucessfully");
                        } else {
                            kafkaConsumer.seek(new TopicPartition(record.topic(), record.partition()), record.offset());
                            logger.error("Error processing message : {} with error : {},resetting offset to {} ", obj,results.getError().getMessage(),record.offset());
                            break;
                        }

                    }
                }

            }
            // TODO add return

        } catch (final Exception e) {
            logger.error("Consumer has failed with exception: " + e, e);
            shutdown();
        }  
试试看{
//每秒钟对卡夫卡消费者进行一次民意调查。
最终消费者记录=kafkaConsumer.poll(1000);
//遍历接收到的所有消息并打印它们的
//内容。
for(最终主题分区:records.partitions()){
最终列表分区记录=记录。记录(分区);
info(“消费者处于活动状态,正在处理”+partitionRecords.size()+“记录”);
用于(最终用户记录:分区记录){
logger.info(“处理主题”+record.topic()+”用于键“+record.key()+”位于偏移量“+record.offset());
期末班
如果问题只在于该事件,而不是其他人处理这一条记录,我们将无法处理任何其他记录

这是正确的,您建议使用错误主题似乎是可行的

我还注意到,在处理
onPartitionsAssigned
时,您基本上不使用消费者承诺的偏移量,因为您似乎总是在寻找到底

如果要从上次成功提交的偏移量重新启动,则不应执行
seek

最后,我想指出,尽管看起来您知道,在同一个组中有3个消费者订阅了单个分区,这意味着三分之二的消费者将处于空闲状态


Edo

你是在使用Message Hub还是你自己在Bluemix中实现了Kafka代理?@ValerieLampkin我们正在使用Message Hub感谢您的关注。是的,在调试过程中,我意识到我们正在寻求结束,因此当应用程序关闭时,如果在此期间发送了新事件,则没有消费者处理过。我们有remOVE结束了搜索。正如您所提到的,对于同一用户组,有多个线程指向一个分区不起作用,我们已将其更改为只有一个线程。