我正在实现一个微服务,它从Kafka队列中读取消息并将它们写入数据库。我正在使用spring-boot 1.5.6.RELEASE
和spring-kafka 1.3.0.RELEASE
。为了避免丢失数据,我需要确保在提交偏移量之前将消息保留在数据库中,因此我将enable.auto.commit
设置为 false 并将AckMode
设置为 MANUAL_IMMEDIATE 即可。这是我的Kafka配置:
@Configuration
@EnableKafka
public class KafkaConfiguration {
...
@Bean
public Map<String, Object> consumerConfigs() {
return new HashMap<String, Object>() {
{
put(ConsumerConfig.BOOTSTRAP_SERVERS_CONFIG, servers);
put(ConsumerConfig.KEY_DESERIALIZER_CLASS_CONFIG, StringDeserializer.class);
put(ConsumerConfig.VALUE_DESERIALIZER_CLASS_CONFIG, StringDeserializer.class);
put(ConsumerConfig.GROUP_ID_CONFIG, groupIdConfig);
put(ConsumerConfig.AUTO_OFFSET_RESET_CONFIG, "earliest");
put(ConsumerConfig.ENABLE_AUTO_COMMIT_CONFIG, false);
}
};
}
@Bean
public ConsumerFactory<String, String> consumerFactory() {
return new DefaultKafkaConsumerFactory<>(consumerConfigs());
}
@Bean
public ConcurrentKafkaListenerContainerFactory<String, String> kafkaListenerContainerFactory() {
ConcurrentKafkaListenerContainerFactory<String, String> factory =
new ConcurrentKafkaListenerContainerFactory<>();
factory.setConsumerFactory(consumerFactory());
factory.getContainerProperties().setAckMode(AbstractMessageListenerContainer.AckMode.MANUAL_IMMEDIATE);
return factory;
}
...
}
对于侦听器的实现,我使用的是@KafkaListener
注释。在数据库中保留消息后,我使用acknowledge()
方法提交偏移量。这是我的听众的样子:
@KafkaListener(topics = "${kafka.myTopic}")
public void receive(ConsumerRecord<String, String> payload, Acknowledgment acknowledgment) {
// persist message here
acknowledgment.acknowledge();
latch.countDown();
}
为了测试我的应用程序,我停止了数据库,以便当业务逻辑尝试持久化消息时,在acknowledge()
方法提交偏移量之前将抛出运行时异常:
1)停止了数据库。
2)发送内容 MESSAGE_1 的消息。
3)启动数据库。
4)发送另一封内容 MESSAGE_2 的消息。
最终结果是数据库仅包含 MESSAGE_2 ,因此第一条消息丢失。我在数据库中获取这两个消息的唯一方法是在我启动数据库后重新启动微服务:
1)停止了数据库。
2)发送内容 MESSAGE_1 的消息。
3)启动数据库。
4)重启微服务。
5)发送包含 MESSAGE_2 内容的其他邮件。
这次两条消息都在数据库中。我的问题是为什么在第一个场景中,虽然抛出了运行时异常并且从未调用acknowledge()
,但偏移是已提交的事件?什么是实现我的kafka监听器的正确方法,以便在处理收到的消息期间发生某些事情时我不会丢失数据?
提前谢谢!
答案 0 :(得分:2)
你必须研究Apache Kafka的工作原理。
提交偏移量恰好适用于同一组中的新消费者或同一重新启动的消费者。对于当前运行的消费者而言,它没有意义,Broker跟踪内存中的当前偏移量,因此所有这些提交都与记录提取过程无关。
您必须考虑将seek
消费者回到您感兴趣的位置:https://docs.spring.io/spring-kafka/docs/2.0.0.RELEASE/reference/html/_reference.html#seek
另见GH问题:https://github.com/spring-projects/spring-kafka/issues/470
答案 1 :(得分:0)
默认情况下,kafka将在指定的时间间隔后提交偏移量,使用手动确认时,对于处理的记录,应始终确认,对于失败的记录,应始终确认。
下面是示例代码
@KafkaListener(id = "baz", topics = "${message.topic.name}", containerFactory = "containerFactory")
public void listenPEN_RE(@Payload String message,
@Header(KafkaHeaders.RECEIVED_PARTITION_ID) int partition,
@Header(KafkaHeaders.OFFSET) int offsets,
Acknowledgment acknowledgment) {
if (value%2==0){
acknowledgment.acknowledge();
} else {
acknowledgment.nack(10); //sleep time should be less than max.poll.interval.ms
}
value++;
}
在这里,我要确认记录是否均匀,否则我要在10毫秒后重试当前记录的位置并重试。