【Spring连载】使用Spring访问 Apache Kafka(六)----应用程序事件

发布时间:2024年01月17日

【Spring连载】使用Spring访问 Apache Kafka(六)----应用程序事件Application Events


以下Spring应用程序事件由监听器容器及其consumers发布:

  • ConsumerStartingEvent —— 在消费者线程第一次启动时发布,并且在它开始poll之前。
  • ConsumerStartedEvent —— 当消费者即将开始poll时发布。
  • ConsumerFailedToStartEvent —— 如果在容器属性consumerStartTimeout时间内没有ConsumerStartingEvent发布,则发布此事件。此事件可能表示配置的任务执行器没有足够的线程来支持使用它的容器及其并发性。发生这种情况时,会记录一条错误消息。
  • ListenerContainerIdleEvent —— 在idleInterval(如果配置了)之内没有收到消息时发布。
  • ListenerContainerPartitionIdleEvent —— 在idlePartitionEventInterval(如果配置了)之内没有收到来自该分区的消息时发布。
  • ListenerContainerPartitionNoLongerIdleEvent —— 当从先前发布过ListenerContainerPartitionIdleEvent的分区中消费记录后发布。
  • NonResponsiveConsumerEvent —— 当consumer在poll方法中被阻塞时发布。
  • ConsumerPartitionPausedEvent —— 在分区消费暂停时由每个consumer发布。
  • ConsumerPartitionResumedEvent —— 在分区消费恢复时由每个consumer发布。
  • ConsumerPausedEvent —— 在容器暂停时由每个consumer发布。
  • ConsumerResumedEvent —— 在容器恢复时由每个consumer发布。
  • ConsumerStoppingEvent —— 由每个consumer在停止之前发布。
  • ConsumerStoppedEvent —— 在consumer关闭后发布。
  • ConsumerRetryAuthEvent —— 当consumer 的authentication或者authorization失败并正在重试时发布。
  • ConsumerRetryAuthSuccessfulEvent —— 成功重试authentication或authorization时发布。只能在之前发生过ConsumerRetryAuthEvent时发生。
  • ContainerStoppedEvent —— 当所有consumer都停止时发布。

默认情况下,应用程序上下文的event multicaster调用calling thread上的事件监听器。如果将multicaster更改为使用异步执行器,则在事件包含对consumer的引用时,不得调用任何consumer方法。
ListenerContainerIdleEvent有以下属性:

  • source:发布事件的监听器容器实例。
  • container:监听器容器或者如果source 容器是子容器,则为父监听器容器。
  • id:监听器ID(或容器bean名称)
  • idleTime:发布事件时容器空闲的时间。
  • topicPartitions:在事件生成时为容器分配的topics和分区。
  • consumer:对Kafka Consumer对象的引用。例如,如果先前调用了consumer的pause()方法,则可以在接收到事件时使用resume()方法。
  • paused:容器当前是否暂停。

ListenerContainerNoLongerIdleEvent 具有相同的属性,除了idleTime和paused。
ListenerContainerPartitionIdleEvent有以下属性:

  • source:发布事件的监听器容器实例。
  • container:监听器容器或者如果source 容器是子容器,则为父监听器容器。
  • id:监听器ID(或容器bean名称)
  • idleTime:发布事件时partition消费所空闲的时间。
  • topicPartition:触发事件的topic和partition
  • consumer:对Kafka Consumer对象的引用。例如,如果先前调用了consumer的pause()方法,则可以在接收到事件时使用resume()方法。
  • paused:consumer的分区消费当前是否暂停

ListenerContainerPartitionNoLongerIdleEvent具有相同的属性,除了idleTime和paused。
NonResponsiveConsumerEvent有以下属性:

  • source:发布事件的监听器容器实例。
  • container:监听器容器或者如果source 容器是子容器,则为父监听器容器。
  • id:监听器ID(或容器bean名称)
  • timeSinceLastPoll:离上次poll有多久了
  • topicPartitions:在事件生成时为容器分配的topics和分区。
  • consumer:对Kafka Consumer对象的引用。例如,如果先前调用了consumer的pause()方法,则可以在接收到事件时使用resume()方法。
  • paused:容器当前是否暂停。

ConsumerPausedEvent, ConsumerResumedEvent 和 ConsumerStoppingEvent有以下属性:

  • source:发布事件的监听器容器实例。
  • container:监听器容器或者如果source 容器是子容器,则为父监听器容器。
  • partitions:涉及的TopicPartition实例。

ConsumerPartitionPausedEvent, ConsumerPartitionResumedEvent 有以下属性:

  • source:发布事件的监听器容器实例。
  • container:监听器容器或者如果source 容器是子容器,则为父监听器容器。
  • partition:涉及的TopicPartition实例。

ConsumerRetryAuthEvent 有以下属性:

  • source:发布事件的监听器容器实例。
  • container:监听器容器或者如果source 容器是子容器,则为父监听器容器。
  • reason
    • AUTHENTICATION——由于身份验证异常而发布的事件。
    • AUTHORIZATION——由于授权异常而发布的事件。

ConsumerStartingEvent, ConsumerStartingEvent, ConsumerFailedToStartEvent, ConsumerStoppedEvent, ConsumerRetryAuthSuccessfulEvent and ContainerStoppedEvent 有以下属性:

  • source:发布事件的监听器容器实例。
  • container:监听器容器或者如果source 容器是子容器,则为父监听器容器。
    所有容器(无论是子容器还是父容器)都会发布ContainerStoppedEvent。对于父容器,source属性和container属性是相同的。
    另外,ConsumerStoppedEvent 还有额外属性:
  • reason
    • NORMAL——consumer正常停止(容器停止)。
    • ERROR——抛出一个java.lang.Error
    • FENCED——事务producer被隔离,并且容器属性stopContainerWhenFenced为true。
    • AUTH——抛出AuthenticationException或AuthorizationException异常,并且没有配置authExceptionRetryInterval。
    • NO_OFFSET——分区没有偏移量,并且auto.offset.reset策略为none。
      你可以使用此事件在满足以下条件后重新启动容器:
if (event.getReason.equals(Reason.FENCED)) {
    event.getSource(MessageListenerContainer.class).start();
}

一、检测空闲和无响应的Consumers

虽然效率很高,但异步consumers的一个问题是检测它们何时处于空闲状态。如果在一段时间内没有消息到达,你可能需要采取一些操作。你可以配置监听器容器,以便在一段时间过去而没有消息传递时发布ListenerContainerIdleEvent。当容器空闲时,每隔idleEventInterval毫秒就会发布一个事件。要配置此特性,请在容器上设置idleEventInterval。下面的例子展示了如何这样做:

@Bean
public KafkaMessageListenerContainer(ConsumerFactory<String, String> consumerFactory) {
    ContainerProperties containerProps = new ContainerProperties("topic1", "topic2");
    ...
    containerProps.setIdleEventInterval(60000L);
    ...
    KafkaMessageListenerContainer<String, String> container = new KafKaMessageListenerContainer<>(...);
    return container;
}

下面的例子展示了如何设置@KafkaListener的idleEventInterval:

@Bean
public ConcurrentKafkaListenerContainerFactory kafkaListenerContainerFactory() {
    ConcurrentKafkaListenerContainerFactory<String, String> factory =
                new ConcurrentKafkaListenerContainerFactory<>();
    ...
    factory.getContainerProperties().setIdleEventInterval(60000L);
    ...
    return factory;
}

在以上两种情况下,在容器空闲时,每分钟发布一次事件。
如果由于某种原因,consumer poll()方法没有退出,则不会收到任何消息,也无法生成空闲事件(这是kafka-clients早期版本在无法访问broker时出现的问题)。在这种情况下,如果poll 没有在pollTimeout属性的3倍时间内返回,则容器将发布NonResponsiveConsumerEvent。默认情况下,此检查在每个容器中每30秒执行一次。配置监听器容器时,可以通过在ContainerProperties中设置monitorInterval(默认为30秒)和noPollThreshold(默认为3.0)属性来修改此行为。noPollThreshold应大于1.0,以避免由于race condition而获取到欺骗性的events。接收到这样的事件可以停止容器,从而唤醒consumer,使其可以停止。
如果容器发布了ListenerContainerIdleEvent,那么在随后收到record时,它将发布ListenerContainerNoLongerIdleEvent。

二、事件消费Event Consumption

你可以通过实现ApplicationListener来捕获文中提到的这些事件?—?要么是一个general listener,要么是仅接收特定事件的监听器。你还可以使用Spring Framework 4.2中引入的@EventListener。
下一个例子将@KafkaListener和@EventListener组合成一个类。ApplicationListener会获取所有容器的事件,因此,如果你想根据哪个容器空闲来采取特定操作,则可能需要检查监听器ID。你也可以为此目的使用@EventListener 的condition 。
事件通常在consumer线程上发布,因此与Consumer对象交互是安全的。
以下示例同时使用@KafkaListener和@EventListener:

public class Listener {
    @KafkaListener(id = "qux", topics = "annotated")
    public void listen4(@Payload String foo, Acknowledgment ack) {
        ...
    }
    @EventListener(condition = "event.listenerId.startsWith('qux-')")
    public void eventHandler(ListenerContainerIdleEvent event) {
        ...
    }
}

事件监听器查看所有容器的事件。因此,在前面的示例中,我们根据监听器ID缩小接收到的事件的范围。由于为@KafkaListener创建的容器支持并发,因此实际的容器被命名为id-n,其中n是每个实例支持并发的唯一值。这就是我们在条件中使用startsWith的原因。

如果希望使用空闲事件来停止监听器容器,则不应在调用监听器的线程上调用container.stop()。这样做会导致延迟和不必要的日志消息。相反,您应该将事件交给另一个线程,然后该线程可以停止容器。此外,你不应该停止一个子容器,你应该停止并发容器。

三、空闲时的当前位置Current Positions when Idle

请注意,当检测到空闲时,您可以通过在监听器中实现ConsumerSeekAware来获得当前位置。请参阅查找特定偏移Seeking to a Specific Offset中的onIdleContainer()。

文章来源:https://blog.csdn.net/gabriel_wang_sh/article/details/135616455
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。