Springboot Kafka整合(二)( Receiving Messages)—官方原版

文章详细介绍了SpringKafka中如何配置和使用MessageListener来接收消息,包括不同类型的MessageListener接口、MessageListenerContainer的实现如KafkaMessageListenerContainer和ConcurrentMessageListenerContainer的使用,以及手动提交偏移量和@KafkaListener注解的详细用法。还提到了容器的生命周期管理和异常处理机制。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

 一、Receiving Messages

您可以通过配置MessageListenerContainer并提供消息侦听器或使用@KafkaListener注释来接收消息。

1、Message Listeners

当您使用消息侦听器容器时,必须提供一个侦听器来接收数据。目前有八个受支持的消息侦听器接口。以下列表显示了这些接口:

public interface MessageListener<K, V> { (1)

    void onMessage(ConsumerRecord<K, V> data);

}

public interface AcknowledgingMessageListener<K, V> { (2)

    void onMessage(ConsumerRecord<K, V> data, Acknowledgment acknowledgment);

}

public interface ConsumerAwareMessageListener<K, V> extends MessageListener<K, V> { (3)

    void onMessage(ConsumerRecord<K, V> data, Consumer<?, ?> consumer);

}

public interface AcknowledgingConsumerAwareMessageListener<K, V> extends MessageListener<K, V> { (4)

    void onMessage(ConsumerRecord<K, V> data, Acknowledgment acknowledgment, Consumer<?, ?> consumer);

}

public interface BatchMessageListener<K, V> { (5)

    void onMessage(List<ConsumerRecord<K, V>> data);

}

public interface BatchAcknowledgingMessageListener<K, V> { (6)

    void onMessage(List<ConsumerRecord<K, V>> data, Acknowledgment acknowledgment);

}

public interface BatchConsumerAwareMessageListener<K, V> extends BatchMessageListener<K, V> { (7)

    void onMessage(List<ConsumerRecord<K, V>> data, Consumer<?, ?> consumer);

}

public interface BatchAcknowledgingConsumerAwareMessageListener<K, V> extends BatchMessageListener<K, V> { (8)

    void onMessage(List<ConsumerRecord<K, V>> data, Acknowledgment acknowledgment, Consumer<?, ?> consumer);

}

(1)当使用自动提交或容器管理的提交方法之一时,使用此接口处理从Kafka consumer poll()操作接收到的单个ConsumerRecord实例。
(2)当使用其中一种手动提交方法时,使用此接口处理从Kafka consumer poll()操作接收的单个ConsumerRecord实例。
(3)当使用自动提交或容器管理的提交方法之一时,使用此接口处理从Kafka consumer poll()操作接收到的单个ConsumerRecord实例。提供了对Consumer对象的访问。
(4)当使用其中一种手动提交方法时,使用此接口处理从Kafka consumer poll()操作接收的单个ConsumerRecord实例。提供了对Consumer对象的访问。
(5)当使用自动提交或容器管理的提交方法时,使用此接口处理从Kafka consumer poll()操作接收到的所有ConsumerRecord实例。使用此接口时不支持AckMode.RECORD,因为侦听器已获得完整的批处理。
(6)当使用手动提交方法时,使用此接口处理从Kafka consumer poll()操作接收到的所有ConsumerRecord实例。
(7)当使用自动提交或容器管理的提交方法时,使用此接口处理从Kafka consumer poll()操作接收到的所有ConsumerRecord实例。使用此接口时不支持AckMode.RECORD,因为侦听器已获得完整的批处理。提供了对Consumer对象的访问。
(8)当使用手动提交方法时,使用此接口处理从Kafka consumer poll()操作接收到的所有ConsumerRecord实例。提供了对Consumer对象的访问。

 2、消息侦听器容器

提供了两个MessageListenerContainer实现:

  • KafkMessageListener容器
  • 并发消息侦听器容器

KafkMessageListenerContainer在一个线程上接收来自所有主题或分区的所有消息。ConcurrentMessageListenerContainer委托给一个或多个KafkMessageListenerContain实例以提供多线程消耗。
从2.2.7版本开始,您可以将RecordInterceptor添加到侦听器容器中;它将在调用监听器之前被调用,从而允许检查或修改记录。如果拦截器返回null,则不会调用监听器。从2.7版本开始,它有额外的方法,这些方法在侦听器退出后调用(通常情况下,或通过抛出异常)。此外,从2.7版本开始,现在有一个BatchInterceptor,为Batch Listener提供类似的功能。此外,ConsumerwareRecordInterceptor(和BatchInterceptor)提供对Consumer<?,?>的访问。例如,这可能用于访问拦截器中的消费者度量。

CompositeRecordInterceptor和CompositeBatchInterceptor可用于调用多个拦截器。
默认情况下,从2.8版本开始,在使用事务时,会在事务启动之前调用拦截器。您可以将侦听器容器的interceptBeforeTx属性设置为false,以便在事务启动后调用侦听器。从2.9版本开始,这将适用于任何事务管理器,而不仅仅是KafkaAwareTransactionManagers。例如,这允许拦截器参与容器启动的JDBC事务。
从2.3.8和2.4.6版本开始,当并发性大于1时,ConcurrentMessage ListenerContainer现在支持静态成员身份。group.instance.id后缀为-n,n从1开始。这与增加的session.timeout.ms一起,可以用于减少重新平衡事件,例如,当应用程序实例重新启动时。

使用KafkaMessageListenerContainer

以下构造函数可用:

public KafkaMessageListenerContainer(ConsumerFactory<K, V> consumerFactory,
                    ContainerProperties containerProperties)

它接收一个ConsumerFactory以及ContainerProperties对象中有关主题和分区以及其他配置的信息。ContainerProperties具有以下构造函数:

public ContainerProperties(TopicPartitionOffset... topicPartitions)

public ContainerProperties(String... topics)

public ContainerProperties(Pattern topicPattern)

第一个构造函数采用一个TopicPartitionOffset参数数组,明确指示容器使用哪些分区(使用consumer assign()方法),并带有可选的初始偏移量。默认情况下,正值是绝对偏移。默认情况下,负值是相对于分区内当前的最后一个偏移量。提供了TopicPartitionOffset的构造函数,该构造函数接受额外的布尔参数。如果这是真的,则初始偏移(正偏移或负偏移)相对于该消费者的当前位置。当容器启动时,将应用偏移。第二个采用主题数组,Kafka根据group.id属性分配分区 — 在组中分布分区。第三个使用正则表达式模式来选择主题。
要将MessageListener分配给容器,可以在创建容器时使用ContainerProps.setMessageListener方法。以下示例显示了如何执行此操作:

ContainerProperties containerProps = new ContainerProperties("topic1", "topic2");
containerProps.setMessageListener(new MessageListener<Integer, String>() {
    ...
});
DefaultKafkaConsumerFactory<Integer, String> cf =
                        new DefaultKafkaConsumerFactory<>(consumerProps());
KafkaMessageListenerContainer<Integer, String> container =
                        new KafkaMessageListenerContainer<>(cf, containerProps);
return container;

请注意,在创建DefaultKafkaConsumerFactory时,使用上面只接受财产的构造函数意味着键和值Deserializer类是从配置中选取的。或者,可以将反序列化器实例传递给键和/或值的DefaultKafkaConsumerFactory构造函数,在这种情况下,所有Consumers共享相同的实例。另一种选择是提供供应商<反序列化程序>(从版本2.3开始),用于为每个消费者获得单独的反序列化程序实例:

DefaultKafkaConsumerFactory<Integer, CustomValue> cf =
                        new DefaultKafkaConsumerFactory<>(consumerProps(), null, () -> new CustomValueDeserializer());
KafkaMessageListenerContainer<Integer, String> container =
                        new KafkaMessageListenerContainer<>(cf, containerProps);
return container;

有关可以设置的各种财产的更多信息,请参阅Javadoc for ContainerProperties。
从2.1.1版本开始,一个名为logContainerConfig的新属性可用。当启用true和INFO日志记录时,每个侦听器容器都会写入一条日志消息,总结其配置财产。
默认情况下,主题偏移量提交的日志记录是在DEBUG日志记录级别执行的。从2.1.2版本开始,ContainerProperties中名为commitLogLevel的属性允许您指定这些消息的日志级别。例如,要将日志级别更改为INFO,可以使用containerProperties.setCommitLogLevel(LogIfLevelEnabled.level.INFO);。
从版本2.2开始,添加了一个名为missingTopicsFtal的新容器属性(默认值:自2.3.4以来为false)。如果代理上没有任何配置的主题,则会阻止容器启动。如果容器配置为侦听主题模式(regex),则不适用。以前,容器线程在consumer.poll()方法中循环,等待主题出现,同时记录许多消息。除了日志之外,没有任何迹象表明存在问题。
从2.8版本开始,引入了一个新的容器属性authExceptionRetryInterval。这会导致容器在从KafkaConsumer获得任何AuthenticationException或AuthorizationException之后重试获取消息。例如,当配置的用户被拒绝访问某个主题或凭据不正确时,就会发生这种情况。通过定义authExceptionRetryInterval,可以在授予适当权限时恢复容器。

从2.8版开始,在创建消费者工厂时,如果您将反序列化器作为对象提供(在构造函数中或通过setter),工厂将调用configure()方法以使用配置财产配置它们。

使用ConcurrentMessageListenerContainer

单个构造函数类似于KafkaListenerContainer构造函数。以下列表显示了构造函数的签名:

public ConcurrentMessageListenerContainer(ConsumerFactory<K, V> consumerFactory,
                            ContainerProperties containerProperties)

它还有一个并发属性。例如,container.setConcurrency(3)创建了三个KafkaMessageListenerContainer实例。
对于第一个构造函数,Kafka使用其组管理功能在消费者之间分发分区。

当容器财产配置为TopicPartitionOffset s时,ConcurrentMessageListenerContainer会跨委托KafkaMessageListener container实例分发TopicPatitionOffset实例。
例如,如果提供了六个TopicPartitionOffset实例,并且并发性为3;每个容器有两个分区。对于五个TopicPartitionOffset实例,两个容器得到两个分区,第三个得到一个分区。如果并发性大于TopicPartitions的数量,则会向下调整并发性,使每个容器获得一个分区。

从1.3版本开始,MessageListenerContainer提供了对底层KafkaConsumer的度量的访问。在ConcurrentMessageListenerContainer的情况下,metrics()方法返回所有目标KafkaMessageListenerContain实例的度量。度量被分组到Map<MetricName?通过为底层KafkaConsumer提供的客户端id扩展Metric>。
从2.3版本开始,ContainerProperties提供了一个idleBetweenPolls选项,让侦听器容器中的主循环在KafkaConsumer.poll()调用之间休眠。从提供的选项和max.poll.interval.ms消费者配置与当前记录批处理时间之间的差异中选择实际睡眠间隔作为最小值。

Committing Offsets

提供了几个用于提交偏移的选项。如果enable.auto.commit consumer属性为true,则Kafka会根据其配置自动提交偏移。如果为false,则容器支持几个AckMode设置(在下一个列表中描述)。默认的AckMode为BATCH。从2.3版开始,除非在配置中明确设置,否则框架会将enable.auto.commit设置为false。以前,如果未设置属性,则使用Kafka默认值(true)。
consumer poll()方法返回一个或多个ConsumerRecords。为每条记录调用MessageListener。以下列表描述了容器对每个AckMode所采取的操作(当不使用事务时):

  • RECORD:当侦听器在处理记录后返回时,提交偏移量。
  • BATCH:当poll()返回的所有记录都已处理完毕时,提交偏移量。
  • TIME:当poll()返回的所有记录都已处理时,只要超过了自上次提交以来的ackTime,就提交偏移量。
  • COUNT:当poll()返回的所有记录都已处理时,只要自上次提交以来收到了ackCount条记录,就提交偏移量。
  • COUNT_TIME:类似于TIME和COUNT,但如果其中一个条件为true,则执行提交。
  • 手动:消息侦听器负责确认()确认。之后,将应用与BATCH相同的语义。
  • MANUAL_IMMEDIATE:当监听器调用Acknowledgement.acknowledge()方法时,立即提交偏移量。

使用事务时,偏移量被发送到事务,语义等效于RECORD或BATCH,具体取决于侦听器类型(记录或批)。

根据syncCommits容器属性,使用使用者上的commitSync()或commitAsync()方法。syncCommits默认为true;另请参阅setSyncCommitTimeout。请参阅setCommitCallback以获取异步提交的结果;默认回调是LoggingCommitCallback,它记录错误(以及调试级别的成功)。
因为侦听器容器有自己的提交偏移的机制,所以它更喜欢Kafka ConsumerConfig.ENABLE_AUTO_COMMIT_CONFIG为false。从版本2.3开始,它无条件地将其设置为false,除非在使用者工厂或容器的使用者属性重写中特别设置。
确认采用以下方法:

public interface Acknowledgment {

    void acknowledge();

}

此方法使侦听器能够控制何时提交偏移。
从2.3版本开始,Acknowledgement接口有两个额外的方法nack(长睡眠)和nack(int index,长睡眠)。第一个用于记录侦听器,第二个用于批处理侦听器。为侦听器类型调用错误的方法将引发IllegalStateException。

使用记录侦听器,当调用nack()时,将提交任何挂起的偏移,丢弃上次轮询中的剩余记录,并在其分区上执行查找,以便在下一次轮询()中重新传递失败的记录和未处理的记录。通过设置sleep参数,可以在重新交付之前暂停使用者。这与使用DefaultErrorHandler配置容器时抛出异常的功能类似。
使用批处理侦听器时,可以在发生故障的批处理中指定索引。当调用nack()时,在对失败和丢弃的记录的分区执行索引和查找之前,将为记录提交偏移量,以便在下一次poll()中重新传递这些记录。

侦听器容器自动启动

侦听器容器实现SmartLifecycle,默认情况下autoStartup为true。容器是在后期启动的(Integer.MAX-VALUE-100)。实现SmartLifecycle以处理来自侦听器的数据的其他组件应该在早期阶段启动。-100为后面的阶段留出了空间,使组件能够在容器之后自动启动。

3、手动提交Offsets

通常,当使用AckMode.MANUAL或AckMode_MANUAL_IMMEDIATE时,必须按顺序确认确认,因为Kafka不维护每个记录的状态,只维护每个组/分区的提交偏移量。从2.8版本开始,您现在可以设置容器属性asyncAcks,它允许以任何顺序确认轮询返回的记录的确认。侦听器容器将延迟无序提交,直到收到丢失的确认为止。使用者将被暂停(没有传递新记录),直到提交了上一次轮询的所有偏移量。

4、@KafkaListener注解

@KafkaListener注释用于将bean方法指定为侦听器容器的侦听器。bean封装在一个MessagingMessageListenerAdapter中,该适配器配置有各种功能,如转换器,以便在必要时转换数据以匹配方法参数。
您可以使用#{…​} 或属性占位符(${…​}). 有关更多信息,请参阅Javadoc。

Record Listeners

@KafkaListener注释为简单的POJO侦听器提供了一种机制。以下示例显示了如何使用它:

public class Listener {

    @KafkaListener(id = "foo", topics = "myTopic", clientIdPrefix = "myClientId")
    public void listen(String data) {
        ...
    }

}

此机制需要在您的一个@Configuration类上有一个@EnableKafka注释和一个侦听器容器工厂,该工厂用于配置底层ConcurrentMessageListenerContainer。默认情况下,需要一个名为kafkaListenerContainerFactory的bean。以下示例显示了如何使用ConcurrentMessage ListenerContainer:

@Configuration
@EnableKafka
public class KafkaConfig {

    @Bean
    KafkaListenerContainerFactory<ConcurrentMessageListenerContainer<Integer, String>>
                        kafkaListenerContainerFactory() {
        ConcurrentKafkaListenerContainerFactory<Integer, String> factory =
                                new ConcurrentKafkaListenerContainerFactory<>();
        factory.setConsumerFactory(consumerFactory());
        factory.setConcurrency(3);
        factory.getContainerProperties().setPollTimeout(3000);
        return factory;
    }

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

    @Bean
    public Map<String, Object> consumerConfigs() {
        Map<String, Object> props = new HashMap<>();
        props.put(ProducerConfig.BOOTSTRAP_SERVERS_CONFIG, embeddedKafka.getBrokersAsString());
        ...
        return props;
    }
}

注意,要设置容器财产,必须在工厂上使用getContainerProperties()方法。它用作注入容器的实际财产的模板。
从2.1.1版本开始,现在可以为注释创建的使用者设置client.id属性。clientIdPrefix后缀为-n,其中n是一个整数,表示使用并发时的容器编号。
从2.2版开始,现在可以通过在注释本身上使用财产来覆盖容器工厂的并发性和autoStartup财产。财产可以是简单值、属性占位符或SpEL表达式。以下示例显示了如何执行此操作:

@KafkaListener(id = "myListener", topics = "myTopic",
        autoStartup = "${listen.auto.start:true}", concurrency = "${listen.concurrency:3}")
public void listen(String data) {
    ...
}

显式分区分配

您还可以使用显式主题和分区(以及可选的初始偏移量)配置POJO侦听器。以下示例显示了如何执行此操作:

@KafkaListener(id = "thing2", topicPartitions =
        { @TopicPartition(topic = "topic1", partitions = { "0", "1" }),
          @TopicPartition(topic = "topic2", partitions = "0",
             partitionOffsets = @PartitionOffset(partition = "1", initialOffset = "100"))
        })
public void listen(ConsumerRecord<?, ?> record) {
    ...
}

您可以在partitions或partitionOffsets属性中指定每个分区,但不能同时指定两者。
与大多数注释财产一样,您可以使用SpEL表达式;有关如何生成大的分区列表的示例,请参阅手动分配所有分区。
从2.5.5版本开始,您可以对所有分配的分区应用初始偏移:

@KafkaListener(id = "thing3", topicPartitions =
        { @TopicPartition(topic = "topic1", partitions = { "0", "1" },
             partitionOffsets = @PartitionOffset(partition = "*", initialOffset = "0"))
        })
public void listen(ConsumerRecord<?, ?> record) {
    ...
}

*通配符表示分区属性中的所有分区。每个@TopicPartition中只能有一个带有通配符的@PartitionOffset。
此外,当侦听器实现ConsumerSeekAware时,onPartitionsAssigned现在被调用,即使使用手动分配也是如此。例如,这允许在那个时候进行任意的寻道操作。
从2.6.4版本开始,您可以指定一个逗号分隔的分区列表或分区范围:

@KafkaListener(id = "pp", autoStartup = "false",
        topicPartitions = @TopicPartition(topic = "topic1",
                partitions = "0-5, 7, 10-15"))
public void process(String in) {
    ...
}

范围包括在内;上面的示例将分配分区0、1、2、3、4、5、7、10、11、12、13、14、15。
指定初始偏移时可以使用相同的技术:

@KafkaListener(id = "thing3", topicPartitions =
        { @TopicPartition(topic = "topic1",
             partitionOffsets = @PartitionOffset(partition = "0-5", initialOffset = "0"))
        })
public void listen(ConsumerRecord<?, ?> record) {
    ...
}

初始偏移量将应用于所有6个分区。

手动确认

当使用手动AckMode时,您还可以向侦听器提供Acknowledgement。以下示例还显示了如何使用不同的容器工厂。

@KafkaListener(id = "cat", topics = "myTopic",
          containerFactory = "kafkaManualAckListenerContainerFactory")
public void listen(String data, Acknowledgment ack) {
    ...
    ack.acknowledge();
}

消费者记录元数据

最后,可以从消息头中获得有关记录的元数据。您可以使用以下标头名称来检索邮件的标头:

  • KafkaHeaders.OFFSET(卡夫卡标题偏移)
  • KafkaHeaders.RECEIVED_KEY(Kafka标头接收密钥)
  • KafkaHeaders.RECEIVED_主题
  • KafkaHeaders.RECEIVED_PARTITION(卡夫卡标题接收_分区)
  • KafkaHeaders.RECEIVED_TIMESTAMP(卡夫卡标题接收时间戳)
  • 卡夫卡标题.TIMESTAMP_TYPE

从版本2.5开始,如果传入记录具有空密钥,则RECEIVED_KEY不存在;之前,标头中填充了一个null值。这一变化是为了使框架与spring消息传递约定保持一致,其中不存在空值标头。
以下示例显示了如何使用标头:

@KafkaListener(id = "qux", topicPattern = "myTopic1")
public void listen(@Payload String foo,
        @Header(name = KafkaHeaders.RECEIVED_KEY, required = false) Integer key,
        @Header(KafkaHeaders.RECEIVED_PARTITION) int partition,
        @Header(KafkaHeaders.RECEIVED_TOPIC) String topic,
        @Header(KafkaHeaders.RECEIVED_TIMESTAMP) long ts
        ) {
    ...
}

从2.5版开始,您可以在ConsumerRecordMetadata参数中接收记录元数据,而不是使用离散标头。

这包含ConsumerRecord中除关键字和值之外的所有数据。

批处理侦听器

从1.1版本开始,您可以配置@KafkaListener方法来接收从消费者轮询中接收的整批消费者记录。要配置侦听器容器工厂以创建批侦听器,可以设置batchListener属性。以下示例显示了如何执行此操作:

@Bean
public KafkaListenerContainerFactory<?> batchFactory() {
    ConcurrentKafkaListenerContainerFactory<Integer, String> factory =
            new ConcurrentKafkaListenerContainerFactory<>();
    factory.setConsumerFactory(consumerFactory());
    factory.setBatchListener(true);  // <<<<<<<<<<<<<<<<<<<<<<<<<
    return factory;
}

以下示例显示了如何接收有效载荷列表:

@KafkaListener(id = "list", topics = "myTopic", containerFactory = "batchFactory")
public void listen(List<String> list) {
    ...
}

主题、分区、偏移量等在与有效负载并行的标头中可用。以下示例显示了如何使用标头:

@KafkaListener(id = "list", topics = "myTopic", containerFactory = "batchFactory")
public void listen(List<String> list,
        @Header(KafkaHeaders.RECEIVED_KEY) List<Integer> keys,
        @Header(KafkaHeaders.RECEIVED_PARTITION) List<Integer> partitions,
        @Header(KafkaHeaders.RECEIVED_TOPIC) List<String> topics,
        @Header(KafkaHeaders.OFFSET) List<Long> offsets) {
    ...
}

或者,您可以接收消息列表<?>每个消息中具有每个偏移量和其他详细信息的对象,但它必须是方法上定义的唯一参数(当使用手动提交和/或Consumer<?,?>参数时,除了可选的Acknowledgement之外)。以下示例显示了如何执行此操作:

@KafkaListener(id = "listMsg", topics = "myTopic", containerFactory = "batchFactory")
public void listen14(List<Message<?>> list) {
    ...
}

@KafkaListener(id = "listMsgAck", topics = "myTopic", containerFactory = "batchFactory")
public void listen15(List<Message<?>> list, Acknowledgment ack) {
    ...
}

@KafkaListener(id = "listMsgAckConsumer", topics = "myTopic", containerFactory = "batchFactory")
public void listen16(List<Message<?>> list, Acknowledgment ack, Consumer<?, ?> consumer) {
    ...
}

在这种情况下,不会对有效载荷进行转换。
如果BatchMessagingMessageConverter配置有RecordMessageConverter,您也可以将泛型类型添加到Message参数中,并转换有效载荷。有关更多信息,请参阅使用Batch Listeners进行有效负载转换。
您还可以收到ConsumerRecord<?,?>的列表对象,但它必须是方法上定义的唯一参数(除了可选的Acknowledgement,当使用手动提交和Consumer<?,?>参数时)。以下示例显示了如何执行此操作:

@KafkaListener(id = "listCRs", topics = "myTopic", containerFactory = "batchFactory")
public void listen(List<ConsumerRecord<Integer, String>> list) {
    ...
}

@KafkaListener(id = "listCRsAck", topics = "myTopic", containerFactory = "batchFactory")
public void listen(List<ConsumerRecord<Integer, String>> list, Acknowledgment ack) {
    ...
}

从版本2.2开始,侦听器可以接收完整的ConsumerRecords<?,?>对象,允许侦听器访问其他方法,如partitions()(返回列表中的TopicPartition实例)和records(TopicPartiction)(获取选择性记录)。同样,这必须是方法上的唯一参数(除了可选的Acknowledgement,当使用手动提交或Consumer<?,?>参数时)。以下示例显示了如何执行此操作:

@KafkaListener(id = "pollResults", topics = "myTopic", containerFactory = "batchFactory")
public void pollResults(ConsumerRecords<?, ?> records) {
    ...
}

注解属性

从2.0版本开始,id属性(如果存在)被用作Kafka消费者组.id属性,如果存在,则覆盖消费者工厂中配置的属性。您也可以显式设置groupId或将idIsGroup设置为false,以恢复以前使用使用者工厂group.id的行为。
您可以在大多数注释财产中使用属性占位符或SpEL表达式,如下例所示:

@KafkaListener(topics = "${some.property}")

@KafkaListener(topics = "#{someBean.someProperty}",
    groupId = "#{someBean.someProperty}.group")

从2.1.2版本开始,SpEL表达式支持一个特殊的令牌:__listener。它是一个伪bean名称,表示存在此注释的当前bean实例。
考虑以下示例:

@Bean
public Listener listener1() {
    return new Listener("topic1");
}

@Bean
public Listener listener2() {
    return new Listener("topic2");
}

给定前面示例中的bean,我们可以使用以下内容:

public class Listener {

    private final String topic;

    public Listener(String topic) {
        this.topic = topic;
    }

    @KafkaListener(topics = "#{__listener.topic}",
        groupId = "#{__listener.topic}.group")
    public void listen(...) {
        ...
    }

    public String getTopic() {
        return this.topic;
    }

}

如果在不太可能的情况下,您有一个名为__listener的实际bean,那么您可以使用beanRef属性来更改表达式标记。以下示例显示了如何执行此操作:

@KafkaListener(beanRef = "__x", topics = "#{__x.topic}",
    groupId = "#{__x.topic}.group")

从版本2.2.4开始,您可以直接在注释上指定Kafka消费者财产,这些属性将覆盖消费者工厂中配置的同名财产。您不能以这种方式指定group.id和client.id财产;它们将被忽略;对这些属性使用groupId和clientIdPrefix注释财产。
财产被指定为具有普通Java财产文件格式的单个字符串:foo:bar、foo=bar或foo-bar。

@KafkaListener(topics = "myTopic", groupId = "group", properties = {
    "max.poll.interval.ms:60000",
    ConsumerConfig.MAX_POLL_RECORDS_CONFIG + "=100"
})

以下是Using RoutingKafkaTemplate中示例的相应侦听器示例。

@KafkaListener(id = "one", topics = "one")
public void listen1(String in) {
    System.out.println("1: " + in);
}

@KafkaListener(id = "two", topics = "two",
        properties = "value.deserializer:org.apache.kafka.common.serialization.ByteArrayDeserializer")
public void listen2(byte[] in) {
    System.out.println("2: " + new String(in));
}

5、获取Consumer group.id

当在多个容器中运行相同的侦听器代码时,能够确定记录来自哪个容器(由其group.id使用者属性标识)可能会很有用。
您可以在侦听器线程上调用KafkaUtils.getConsumerGroupId()来完成此操作。或者,您可以访问方法参数中的组id。

@KafkaListener(id = "bar", topicPattern = "${topicTwo:annotated2}", exposeGroupId = "${always:true}")
public void listener(@Payload String foo,
        @Header(KafkaHeaders.GROUP_ID) String groupId) {
...
}

6、容器线程命名

TaskExecutor用于调用使用者和侦听器。您可以通过设置容器ContainerProperties的consumerExecutor属性来提供自定义执行器。当使用池执行器时,请确保有足够的线程可用于处理使用它们的所有容器的并发性。当使用ConcurrentMessageListenerContainer时,来自执行器的线程将用于每个使用者(并发)。
如果不提供使用者执行器,则会为每个容器使用SimpleAsyncTaskExecutor。这个执行器创建的线程的名称类似于<beanName>-C-<n>。对于ConcurrentMessage ListenerContainer,线程名称的<beanName>部分变为<beanName]-m,其中m表示使用者实例。每次启动容器时,n递增。因此,使用容器的bean名称,在容器第一次启动后,该容器中的线程将被命名为container-0-C-1、container-1-C-1等;容器0-C-2、容器1-C-2等。
从3.0.1版本开始,您现在可以更改线程的名称,无论使用哪个执行器。将AbstractMessageListenerContainer.changeConsumerThreadName属性设置为true,将调用AbstractMessage ListenerContaner.threadNameSupplier来获取线程名称。这是一个函数<MessageListenerContainer,String>,默认实现返回container.getListenerId()。

7、@KafkaListener作为元注解

从2.2版本开始,您现在可以使用@KafkaListener作为元注释。以下示例显示了如何执行此操作:

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@KafkaListener
public @interface MyThreeConsumersListener {

    @AliasFor(annotation = KafkaListener.class, attribute = "id")
    String id();

    @AliasFor(annotation = KafkaListener.class, attribute = "topics")
    String[] topics();

    @AliasFor(annotation = KafkaListener.class, attribute = "concurrency")
    String concurrency() default "3";

}

您必须为主题、topicPattern或topicPartitions中的至少一个别名(通常为id或groupId,除非您在使用者工厂配置中指定了group.id)。以下示例显示了如何执行此操作:

@MyThreeConsumersListener(id = "my.group", topics = "my.topic")
public void listen1(String in) {
    ...
}

8、在类上使用@KafkaListener 

在类级别使用@KafkaListener时,必须在方法级别指定@Kafka Handler。传递消息时,转换后的消息有效负载类型用于确定调用哪种方法。以下示例显示了如何执行此操作:

@KafkaListener(id = "multi", topics = "myTopic")
static class MultiListenerBean {

    @KafkaHandler
    public void listen(String foo) {
        ...
    }

    @KafkaHandler
    public void listen(Integer bar) {
        ...
    }

    @KafkaHandler(isDefault = true)
    public void listenDefault(Object object) {
        ...
    }

}

从2.1.3版本开始,您可以指定@KafkaHandler方法作为默认方法,如果其他方法不匹配,则会调用该方法。最多可以指定一种方法。当使用@KafkaHandler方法时,负载必须已经转换为域对象(因此可以执行匹配)。使用自定义反序列化程序、JsonDeserializer或其TypePriority设置为TYPE_ID的JsonMessageConverter。有关详细信息,请参阅序列化、反序列化和消息转换。

@KafkaHandler(isDefault = true)
public void listenDefault(Object object, @Header(KafkaHeaders.RECEIVED_TOPIC) String topic) {
    ...
}

如果对象是一个字符串,这将不起作用;topic参数还将获得对对象的引用。
如果您需要默认方法中有关记录的元数据,请使用以下方法:

@KafkaHandler(isDefault = true)
void listen(Object in, @Header(KafkaHeaders.RECORD_METADATA) ConsumerRecordMetadata meta) {
    String topic = meta.topic();
    ...
}

9、@KafkaListener属性修改

从2.7.2版本开始,现在可以在创建容器之前以编程方式修改注释属性。为此,请将一个或多个KafkaListenerAnnotationBeanPostProcessor.AnnotationEnhancer添加到应用程序上下文中。AnnotationEnhancer是一个BiFunction<Map<String,Object>、AnnotatedElement、Map<String、Object>,并且必须返回属性映射。属性值可以包含SpEL和/或属性占位符;在执行任何解析之前调用增强器。如果存在多个增强器,并且它们实现了Ordered,则会按顺序调用它们。

@Bean
public static AnnotationEnhancer groupIdEnhancer() {
    return (attrs, element) -> {
        attrs.put("groupId", attrs.get("id") + "." + (element instanceof Class
                ? ((Class<?>) element).getSimpleName()
                : ((Method) element).getDeclaringClass().getSimpleName()
                        +  "." + ((Method) element).getName()));
        return attrs;
    };
}

10、@KafkaListener生命周期管理

Springboot Kafka整合(开发实例、连接、配置TOPICS、发送消息)—官方原版
大家好,我是Doker品牌的Sinbad,欢迎点赞和评论,您的鼓励是我们持续更新的动力!欢迎加微信进入技术群聊! 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值