简体   繁体   English

如何从与不同代理关联的多个 Kafka 主题中消费?

[英]How can I consume from multiple Kafka topics that are associated with different brokers?

How can I consume from multiple Kafka topics that are associated with different brokers?如何从与不同代理关联的多个 Kafka 主题中消费?

I have a Spring Boot application that needs to consume from 2 topics, but the topics are associated with different Brokers.我有一个 Spring 启动应用程序需要从 2 个主题中消费,但这些主题与不同的经纪人相关联。

I am using Spring Kafka with the @Listener annotation, and I see there are ways to consume from 2 topics that are associated with the same broker, not different brokers.我正在使用带有 @Listener 注释的 Spring Kafka,我看到有一些方法可以从与同一代理相关联的 2 个主题中消费,而不是不同的代理。 Unfortunately, I don't see anything helpful in the Spring Boot or Spring Kafka docs on how to do this.不幸的是,我在 Spring Boot 或 Spring Kafka 文档中没有看到任何有关如何执行此操作的有用信息。

There are a few ways to do this, and unfortunately Spring-Boot and Spring-Kafka do not make it clear the best practice to implement this.有几种方法可以做到这一点,不幸的是 Spring-Boot 和 Spring-Kafka 并没有明确实现这一点的最佳实践。 There are also lots of answers across SO that address consuming from multiple topics with the same Broker, and it isn't always as simple as that. SO中还有很多答案可以解决使用同一个Broker从多个主题中消费的问题,但这并不总是那么简单。

Method 1方法一

The easiest way to solve this is to add a properties parameter in your Kafka Listener annotations :解决此问题的最简单方法是在Kafka 侦听器注释中添加属性参数:

@KafkaListener(topics = ["\${topic-1-name}"], properties = ["bootstrap.servers=\${bootstrap-server-1}"])
fun topic1Listener(@Payload messages: List<String>, ack: Acknowledgment){
    // Do work
}

@KafkaListener(topics = ["\${topic-2-name}"], properties = ["bootstrap.servers=\${bootstrap-server-2}"])
fun topic2Listener(@Payload messages: List<String>, ack: Acknowledgment){
    // Do work
}

Whatever key/value pair we specify in the properties param value will override the default key/value in the DefaultKafkaConsumerFactory.我们在属性参数值中指定的任何键/值对都将覆盖 DefaultKafkaConsumerFactory 中的默认键/值。 In this case, we override the bootstrap.servers property to our own specific bootstrap server address for each topic.在这种情况下,我们将 bootstrap.servers 属性覆盖为我们自己的每个主题的特定引导服务器地址。

However, we can still use the Spring-Boot features that are “nice to have”, such as auto-topic creation and allowing Spring-Boot to setup a group-id for our application.但是,我们仍然可以使用 Spring-Boot 的“不错的”功能,例如自动主题创建和允许 Spring-Boot 为我们的应用程序设置组 ID。 We just need to leave our group-id parameter in our application.properties or application.yml file.我们只需要将 group-id 参数保留在 application.properties 或 application.yml 文件中。

spring:
  kafka:
    consumer:
      group-id: group-id-of-your-choice
  • Note that we can use the same group-id for both of our consumers, even though they may span across multiple brokers.请注意,我们可以为我们的两个消费者使用相同的 group-id,即使它们可能跨越多个代理。 It is actually a good practice to have 1 group-id for your entire application, that way monitoring consumer lag, among other metrics, becomes simple.实际上,为整个应用程序设置 1 个组 ID 是一种很好的做法,这样监控消费者延迟以及其他指标就变得简单了。
  • Also note we do not store our topic names in the Spring configuration section anymore, we need to do this elsewhere, as we do not want Spring-Boot configuring our topics with the incorrect broker addresses.另请注意,我们不再将主题名称存储在 Spring 配置部分中,我们需要在其他地方执行此操作,因为我们不希望 Spring-Boot 使用不正确的代理地址配置我们的主题。 We let our Listeners handle that part when we override the properties, as shown above.当我们覆盖属性时,我们让监听器处理该部分,如上所示。

There are lots of other ways to accomplish this, however this is the simplest way I have found and tested to work.还有很多其他方法可以实现这一点,但这是我发现并测试过的最简单的方法。

Method 2方法二

Other methods include creating your own custom ConsumerFactory and KafkaListenerContainerFactory objects, then configuring the properties in each Factory to use the bootstrap servers of your choice.其他方法包括创建您自己的自定义 ConsumerFactory 和 KafkaListenerContainerFactory 对象,然后配置每个 Factory 中的属性以使用您选择的引导服务器。 However, the 1st method is much cleaner and simpler, using the default Container Factory.但是,第一种方法更简洁,使用默认的 Container Factory。 Below is how to create custom Factories with your own properties.以下是如何使用您自己的属性创建自定义工厂。

@Bean
fun ConsumerFactory1(): DefaultKafkaConsumerFactory<String, String> {
        val props = mutableMapOf<String, Any>()
        props[ConsumerConfig.BOOTSTRAP_SERVERS_CONFIG] = bootStrapServers1!!
        props[ConsumerConfig.GROUP_ID_CONFIG] = groupId!!
        props[ConsumerConfig.KEY_DESERIALIZER_CLASS_CONFIG] = StringDeserializer::class.java
        props[ConsumerConfig.VALUE_DESERIALIZER_CLASS_CONFIG] = StringDeserializer::class.java
        return DefaultKafkaConsumerFactory(props)
}

@Bean
fun ContainerFactory1(): ConcurrentKafkaListenerContainerFactory<String, String>? {
        val factory: ConcurrentKafkaListenerContainerFactory<String, String> = ConcurrentKafkaListenerContainerFactory()
        factory.consumerFactory = ConsumerFactory1()
        factory.containerProperties.ackMode = ContainerProperties.AckMode.MANUAL
        factory.isBatchListener = true
        return factory
}

@Bean
fun ConsumerFactory2(): DefaultKafkaConsumerFactory<Any?, Any?> {
        val props = mutableMapOf<String, Any>()
        props[ConsumerConfig.BOOTSTRAP_SERVERS_CONFIG] = bootStrapServers2!!
        props[ConsumerConfig.GROUP_ID_CONFIG] = groupId!!
        props[ConsumerConfig.KEY_DESERIALIZER_CLASS_CONFIG] = StringDeserializer::class.java
        props[ConsumerConfig.VALUE_DESERIALIZER_CLASS_CONFIG] = StringDeserializer::class.java

}

@Bean
fun ContainerFactory2(): ConcurrentKafkaListenerContainerFactory<String, String> {
        val factory: ConcurrentKafkaListenerContainerFactory<String, String> = ConcurrentKafkaListenerContainerFactory()
        factory.consumerFactory = ConsumerFactory2()
        factory.containerProperties.ackMode = ContainerProperties.AckMode.MANUAL
        factory.isBatchListener = true
        return factory
}

A few things to unpack here.这里有一些东西要解压。

  • The container factories are essentially the same, we just use their respective Consumer Factories with the relevant properties for each.容器工厂本质上是相同的,我们只是使用它们各自的消费者工厂和每个相关的属性。
  • I used the built in StringDeserializer as my application consumes messages as strings, and then uses Jackson to serialize the Json string into an object.我使用内置的 StringDeserializer,因为我的应用程序将消息作为字符串使用,然后使用 Jackson 将 Json 字符串序列化为 object。 Your application may need a different deserializer, or even a custom deserializer depending on how data is serialized on the topic.您的应用程序可能需要不同的反序列化器,甚至是自定义反序列化器,具体取决于主题上的数据序列化方式。
  • Setting the AckMode to MANUAL allows us to take control over when we acknowledge we have consumed a message from the topic.将 AckMode 设置为 MANUAL 允许我们在确认已使用来自主题的消息时进行控制。
  • Setting the batch listener to true allows our listener to listen for messages in batches rather than 1 at a time.将批处理侦听器设置为 true 允许我们的侦听器分批侦听消息,而不是一次侦听 1 个消息。
  • With this implementation, we are completely ripping Spring-Boot out of our app, in terms of using Kafka.通过这个实现,就使用 Kafka 而言,我们完全从我们的应用程序中删除了 Spring-Boot。 So our @Listener annotations is going to look a bit different:所以我们的@Listener 注解看起来会有点不同:
@KafkaListener(topics = ["\${kafka-topic-1}"], containerFactory = "ContainerFactory1", groupId = "\${kafka.group-id}")
  • We no longer let Spring-Boot configure our Group-Id for us so we need to specify that in the Listener now.我们不再让 Spring-Boot 为我们配置我们的 Group-Id,所以我们现在需要在 Listener 中指定它。 This means there is no Spring.Kafka.Consumer properties defined in your application.properties files anymore, we need to do this programmatically.这意味着您的 application.properties 文件中不再定义 Spring.Kafka.Consumer 属性,我们需要以编程方式执行此操作。 We will now need to manually configure some other things, such as auto-configuring topics upon startup, if you need that functionality you will need to manually setup a KafkaAdmin bean .我们现在需要手动配置一些其他的东西,比如启动时自动配置主题,如果你需要这个功能,你需要手动设置一个KafkaAdmin bean

Conclusion结论

There are even more ways to accomplish implementing this, and I know others have come up with good solutions too, sometimes it all depends on what you need for your application.还有更多方法可以实现这一点,我知道其他人也提出了很好的解决方案,有时这完全取决于您的应用程序需要什么。 This is just 2 of the solutions I have found to be successful with, and Method 1 is easy to understand, implement and test without getting too involved with the depth of Spring-Boot and Spring-Kafka.这只是我发现成功的解决方案中的 2 个,方法 1 易于理解、实施和测试,无需过多涉及 Spring-Boot 和 Spring-Kafka 的深度。 These methods will work with more than just 2 brokers if that is functionality you need.如果这是您需要的功能,这些方法将与超过 2 个代理一起使用。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

相关问题 如何在Java中编写Kafka Consumer Client以使用来自多个代理的消息? - How to write Kafka Consumer Client in java to consume the messages from multiple brokers? 如何创建一个Kafka消费者库来消费多个主题 - How to create a Kafka consumer library to consume multiple topics Kafka SpringBoot StreamListener - 如何按顺序使用多个主题? - Kafka SpringBoot StreamListener - how to consume multiple topics in order? 如何使用来自多个主题的消息? - How to consume messages from multiple topics? 作为 kafka 消费者同时消费多个主题 - Concurrently Consume Multiple topics as a kafka consumer 可以让具有相同group.id的kafka消费者分别使用不同的主题 - can kafka consumers with same group.id consume different topics separately 我的KafkaSpout不会在HDP中使用来自Kafka Brokers的消息 - My KafkaSpout doesn't consume messages from Kafka Brokers in HDP 从多个 Kafka 主题中消费 - Consuming from multiple Kafka topics 我可以根据负载在运行时在 kafka 中发送不同主题的消息吗? - Can I send a message different topics in kafka at runtime depending on the load? 我可以使用不同的 kafka 代理集来存储 Kafka 流应用程序的 state 吗? - Can I use different set of kafka brokers for storing the state of Kafka streams application?
 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM