如何使用Spring Kafka实现有状态的消息侦听器?

时间:2018-08-02 16:19:58

标签: java spring apache-kafka spring-kafka

我想使用Spring Kafka API实现有状态的侦听器。

给出以下内容:

  • 一个ConcurrentKafkaListenerContainerFactory,并发设置为“ n”
  • Spring @Service类上的@KafkaListener批注方法

然后将创建“ n”个KafkaMessageListenerContainers。其中的每一个都有自己的KafkaConsumer,因此会有“ n”个消费者线程-每个消费者一个。

使用消息时,将使用轮询基础KafkaConsumer的同一线程来调用@KafkaListener方法。由于只有一个侦听器实例,因此该侦听器必须是线程安全的,因为将有来自“ n”个线程的并发访问。

我不想考虑并发访问,而将状态保存在一个侦听器中,我知道该侦听器只能由一个线程访问。

如何使用Spring Kafka API为每个Kafka使用者创建一个单独的侦听器?

1 个答案:

答案 0 :(得分:3)

您是正确的;每个容器有一个侦听器实例(无论是配置为@KafkaListener还是MessageListener)。

一种解决方法是将原型范围为MessageListener的原型与n个KafkaMessageListenerContainer bean(每个都有1个线程)一起使用。

然后,每个容器将获得其自己的侦听器实例。

使用@KafkaListener POJO抽象是不可能的。

但是,通常最好使用无状态bean。

编辑

我发现了另一种使用SimpleThreadScope的解决方法...

@SpringBootApplication
public class So51658210Application {

    public static void main(String[] args) {
        SpringApplication.run(So51658210Application.class, args);
    }

    @Bean
    public ApplicationRunner runner(KafkaTemplate<String, String> template, ApplicationContext context,
            KafkaListenerEndpointRegistry registry) {
        return args -> {
            template.send("so51658210", 0, "", "foo");
            template.send("so51658210", 1, "", "bar");
            template.send("so51658210", 2, "", "baz");
            template.send("so51658210", 0, "", "foo");
            template.send("so51658210", 1, "", "bar");
            template.send("so51658210", 2, "", "baz");
        };
    }

    @Bean
    public ActualListener actualListener() {
        return new ActualListener();
    }

    @Bean
    @Scope("threadScope")
    public ThreadScopedListener listener() {
        return new ThreadScopedListener();
    }

    @Bean
    public static CustomScopeConfigurer scoper() {
        CustomScopeConfigurer configurer = new CustomScopeConfigurer();
        configurer.addScope("threadScope", new SimpleThreadScope());
        return configurer;
    }

    @Bean
    public NewTopic topic() {
        return new NewTopic("so51658210", 3, (short) 1);
    }

    public static class ActualListener {

        @Autowired
        private ObjectFactory<ThreadScopedListener> listener;

        @KafkaListener(id = "foo", topics = "so51658210")
        public void listen(String in, @Header(KafkaHeaders.RECEIVED_PARTITION_ID) int partition) {
            this.listener.getObject().doListen(in, partition);
        }

    }

    public static class ThreadScopedListener {

        private void doListen(String in, @Header(KafkaHeaders.RECEIVED_PARTITION_ID) int partition) {
            System.out.println(in + ":"
                    + Thread.currentThread().getName() + ":"
                    + this.hashCode() + ":"
                    + partition);
        }

    }

}

(容器并发为3)。

工作正常:

bar:foo-1-C-1:1678357802:1
foo:foo-0-C-1:1973858124:0
baz:foo-2-C-1:331135828:2
bar:foo-1-C-1:1678357802:1
foo:foo-0-C-1:1973858124:0
baz:foo-2-C-1:331135828:2

唯一的问题是作用域无法自行清理(例如,当容器停止运行且线程消失时。根据您的使用情况,这可能并不关键。

要解决此问题,我们需要容器提供一些帮助(例如,停止该事件后,在监听器线程上发布事件)。 GH-762