Spring-Kafka并发属性

时间:2019-04-11 07:56:33

标签: spring apache-kafka spring-kafka

我正在使用Spring-Kafka编写我的第一个Kafka Consumer。考察了框架提供的不同选项,并且对此几乎没有疑问。有人可以在下面进行说明吗?

问题-1 :根据Spring-Kafka文档,有两种方法可以实现Kafka-Consumer; “您可以通过配置MessageListenerContainer并提供消息侦听器或使用@KafkaListener批注来接收消息”。有人可以告诉我何时应该选择一个选项而不是另一个吗?

问题-2 :我选择了KafkaListener方法来编写我的应用程序。为此,我需要初始化一个容器工厂实例,并且在容器工厂内部有控制并发的选项。只是想仔细检查一下我对并发的理解是否正确。

假设我有一个主题名称MyTopic,其中有4个分区。为了使用来自MyTopic的消息,我启动了我的应用程序的2个实例,并通过将并发设置为2来启动这些实例。因此,理想情况下,按照kafka分配策略,应将2个分区分配给consumer1,将另外2个分区分配给Consumer2 。由于并发设置为2,是否每个使用者都将启动2个线程,并并行使用主题中的数据?如果我们并行消费,我们还应该考虑什么。

问题3 -我选择了手动确认模式,而不是从外部管理偏移量(不将偏移量持久保存到任何数据库/文件系统中)。因此,我是否需要编写自定义代码来处理重新平衡,否则框架将自动对其进行管理?我认为没有,因为我只有在处理完所有记录后才确认。

问题-4 :此外,在“手动ACK”模式下,哪个Listener可以提高性能? BATCH消息侦听器或常规消息侦听器。我想如果我使用Normal Message侦听器,则在处理每条消息后将提交偏移量。

粘贴下面的代码以供参考。

批次确认使用者

    public void onMessage(List<ConsumerRecord<String, String>> records, Acknowledgment acknowledgment,
          Consumer<?, ?> consumer) {
      for (ConsumerRecord<String, String> record : records) {
          System.out.println("Record : " + record.value());
          // Process the message here..
          listener.addOffset(record.topic(), record.partition(), record.offset());
       }
       acknowledgment.acknowledge();
    }

初始化容器工厂:

@Bean
public ConsumerFactory<String, String> consumerFactory() {
    return new DefaultKafkaConsumerFactory<String, String>(consumerConfigs());
}

@Bean
public Map<String, Object> consumerConfigs() {
    Map<String, Object> configs = new HashMap<String, Object>();
    configs.put(ConsumerConfig.BOOTSTRAP_SERVERS_CONFIG, bootStrapServer);
    configs.put(ConsumerConfig.GROUP_ID_CONFIG, groupId);
    configs.put(ConsumerConfig.ENABLE_AUTO_COMMIT_CONFIG, enablAutoCommit);
    configs.put(ConsumerConfig.MAX_POLL_INTERVAL_MS_CONFIG, maxPolInterval);
    configs.put(ConsumerConfig.AUTO_OFFSET_RESET_CONFIG, autoOffsetReset);
    configs.put(ConsumerConfig.CLIENT_ID_CONFIG, clientId);
    configs.put(ConsumerConfig.KEY_DESERIALIZER_CLASS_CONFIG, StringDeserializer.class);
    configs.put(ConsumerConfig.VALUE_DESERIALIZER_CLASS_CONFIG, StringDeserializer.class);
    return configs;
}

@Bean
public ConcurrentKafkaListenerContainerFactory<String, String> kafkaListenerContainerFactory() {
    ConcurrentKafkaListenerContainerFactory<String, String> factory = new ConcurrentKafkaListenerContainerFactory<String, String>();
    // Not sure about the impact of this property, so going with 1
    factory.setConcurrency(2);
    factory.setBatchListener(true);
    factory.getContainerProperties().setAckMode(AckMode.MANUAL);
    factory.getContainerProperties().setConsumerRebalanceListener(RebalanceListener.getInstance());
    factory.setConsumerFactory(consumerFactory());
    factory.getContainerProperties().setMessageListener(new BatchAckConsumer());
    return factory;
}

2 个答案:

答案 0 :(得分:1)

  1. @KafkaListener是消息驱动的“ POJO”,它添加了诸如有效负载转换,参数匹配等内容。如果实现MessageListener,则只能获得原始的ConsumerRecord来自卡夫卡。参见@KafkaListener Annotation

  2. 是的,并发表示线程数;每个线程创建一个Consumer;它们并行运行;在您的示例中,每个分区都有2个分区。

  

如果我们并行消费,我们还应该考虑什么。

您的侦听器必须是线程安全的(没有共享状态或任何此类状态都需要用锁保护。

  1. 您不清楚“处理重新平衡事件”的含义。当发生重新平衡时,框架将提交所有未决的偏移量。

  2. 没有什么区别;消息监听器批处理侦听器只是一个首选项。即使使用MANUAL ackmode的消息侦听器,在处理了所有轮询结果后,也会提交偏移量。在MANUAL_IMMEDIATE模式下,偏移量是一对一提交的。

答案 1 :(得分:1)

第一季度:

从文档

  

@KafkaListener注释用于将bean方法指定为   侦听器容器的侦听器。豆被包裹在一个   MessagingMessageListenerAdapter配置了各种功能,例如   作为转换器来转换数据(如果需要)以匹配方法   参数。

     

您可以通过使用SpEL在注释上配置大多数属性   “#{…。}或属性占位符($ {…。})。有关更多信息,请参见Javadoc。”

此方法对于简单的POJO侦听器可能很有用,并且您无需实现任何接口。您还可以使用注释以声明的方式收听任何主题和分区。您还可能返回返回的值,而对于MessageListener,则受接口签名的约束。

第二季度:

理想上是。如果您有多个主题可以使用,那么它将变得更加复杂。默认情况下,Kafka使用RangeAssignor,它具有自己的行为(您可以更改此行为-查看更多详细信息under)。

第三季度:

如果您的消费者死亡,将进行再平衡。如果您手动确认并且消费者在提交偏移量之前就去世了,则您无需执行任何操作,Kafka会处理。但是您最终可能会收到一些重复的消息(至少一次)

第四季度:

这取决于您所说的“性能”。如果您的意思是等待时间,那么尽快使用每条记录将是您的理想之选。如果要实现高吞吐量,则批次消耗会更有效。

我已经使用Spring kafka和各种监听器编写了一些示例-请查看this repo