默认情况下,应用程序上下文的event multicaster调用calling thread上的事件监听器。如果将multicaster更改为使用异步执行器,则在事件包含对consumer的引用时,不得调用任何consumer方法。
ListenerContainerIdleEvent有以下属性:
ListenerContainerNoLongerIdleEvent 具有相同的属性,除了idleTime和paused。
ListenerContainerPartitionIdleEvent有以下属性:
ListenerContainerPartitionNoLongerIdleEvent具有相同的属性,除了idleTime和paused。
NonResponsiveConsumerEvent有以下属性:
ConsumerPausedEvent, ConsumerResumedEvent 和 ConsumerStoppingEvent有以下属性:
ConsumerPartitionPausedEvent, ConsumerPartitionResumedEvent 有以下属性:
ConsumerRetryAuthEvent 有以下属性:
ConsumerStartingEvent, ConsumerStartingEvent, ConsumerFailedToStartEvent, ConsumerStoppedEvent, ConsumerRetryAuthSuccessfulEvent and ContainerStoppedEvent 有以下属性:
if (event.getReason.equals(Reason.FENCED)) {
event.getSource(MessageListenerContainer.class).start();
}
虽然效率很高,但异步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。
你可以通过实现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()。这样做会导致延迟和不必要的日志消息。相反,您应该将事件交给另一个线程,然后该线程可以停止容器。此外,你不应该停止一个子容器,你应该停止并发容器。
请注意,当检测到空闲时,您可以通过在监听器中实现ConsumerSeekAware来获得当前位置。请参阅查找特定偏移Seeking to a Specific Offset中的onIdleContainer()。