web-dev-qa-db-ja.com

複数のKafkaListenerContainerFactoryを追加する際の問題

こんにちは私は現在Spring Kafkaに手を出して、リスナーに単一のKafkaListenerContainerFactoryを追加することに成功しました。次に、複数のKafkaListenerContainerFactoryを追加したいと思います(jsonにメッセージがあるトピック用に1つ、文字列用にもう1つ)。以下のコードを参照してください。

@EnableKafka
@Configuration
public class KafkaConsumersConfig {

    private final KafkaConfiguration kafkaConfiguration;

    @Autowired
    public KafkaConsumersConfig(KafkaConfiguration kafkaConfiguration) {
        this.kafkaConfiguration = kafkaConfiguration;
    }

    @Bean
    public KafkaListenerContainerFactory<?> kafkaJsonListenerContainerFactory(){
        ConcurrentKafkaListenerContainerFactory<String,Record> factory = new ConcurrentKafkaListenerContainerFactory<>();
        factory.setConsumerFactory(jsonConsumerFactory());
        factory.setConcurrency(3);
        factory.setAutoStartup(true);
        return factory;
    }

    @Bean
    public ConsumerFactory<String,Record> jsonConsumerFactory(){
        JsonDeserializer<Record> jsonDeserializer = new JsonDeserializer<>(Record.class);
        return new DefaultKafkaConsumerFactory<>(jsonConsumerConfigs(),new StringDeserializer(), jsonDeserializer);
    }

    @Bean
    public Map<String,Object> jsonConsumerConfigs(){
        Map<String,Object> propsMap = new HashMap<>();
        propsMap.put(ConsumerConfig.BOOTSTRAP_SERVERS_CONFIG,  kafkaConfiguration.getBrokerAddress());
        propsMap.put(ConsumerConfig.GROUP_ID_CONFIG, kafkaConfiguration.getJsonGroupId());
        propsMap.put(ConsumerConfig.ENABLE_AUTO_COMMIT_CONFIG, kafkaConfiguration.getAutoCommit());
        propsMap.put(ConsumerConfig.AUTO_COMMIT_INTERVAL_MS_CONFIG, kafkaConfiguration.getAutoCommitInterval());
        propsMap.put(ConsumerConfig.SESSION_TIMEOUT_MS_CONFIG, kafkaConfiguration.getSessionTimeout());
        return propsMap;
    }
    @Bean
    public KafkaListenerContainerFactory<?> kafkaFileListenerContainerFactory(){
        ConcurrentKafkaListenerContainerFactory<String,String> factory = new ConcurrentKafkaListenerContainerFactory<>();
        factory.setConsumerFactory(fileConsumerFactory());
        factory.setConcurrency(3);
        factory.setAutoStartup(true);
        return factory;
    }

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

    @Bean
    public Map<String,Object> fileConsumerConfigs(){
        Map<String,Object> propsMap = new HashMap<>();
        propsMap.put(ConsumerConfig.BOOTSTRAP_SERVERS_CONFIG,  kafkaConfiguration.getBrokerAddress());
        propsMap.put(ConsumerConfig.GROUP_ID_CONFIG, kafkaConfiguration.getFileGroupId());
        propsMap.put(ConsumerConfig.ENABLE_AUTO_COMMIT_CONFIG, kafkaConfiguration.getAutoCommit());
        propsMap.put(ConsumerConfig.AUTO_COMMIT_INTERVAL_MS_CONFIG, kafkaConfiguration.getAutoCommitInterval());
        propsMap.put(ConsumerConfig.SESSION_TIMEOUT_MS_CONFIG, kafkaConfiguration.getSessionTimeout());
        return propsMap;
    }
}

これを実行すると、次のエラーが発生します。

Description:

Parameter 1 of method kafkaListenerContainerFactory in org.springframework.boot.autoconfigure.kafka.KafkaAnnotationDrivenConfiguration required a bean of type 'org.springframework.kafka.core.ConsumerFactory' that could not be found.
    - Bean method 'kafkaConsumerFactory' in 'KafkaAutoConfiguration' not loaded because @ConditionalOnMissingBean (types: org.springframework.kafka.core.ConsumerFactory; SearchStrategy: all) found beans 'jsonConsumerFactory', 'fileConsumerFactory'


Action:

Consider revisiting the conditions above or defining a bean of type 'org.springframework.kafka.core.ConsumerFactory' in your configuration.

私は何が間違っているのですか?

Spring Bootの Kafka Auto Configuration に依存しないようです。

SpringBootはKafkaAutoConfigurationで提供します:

@Bean
@ConditionalOnMissingBean(ConsumerFactory.class)
public ConsumerFactory<?, ?> kafkaConsumerFactory() {

jsonConsumerFactoryfileConsumerFactoryがあるので、それらは自動構成によって提供されるものをオーバーライドします。

ただし、その一方で、KafkaAnnotationDrivenConfigurationでは、どのファクトリも適用できません。

@Bean
@ConditionalOnMissingBean(name = "kafkaListenerContainerFactory")
public ConcurrentKafkaListenerContainerFactory<?, ?> kafkaListenerContainerFactory(
        ConcurrentKafkaListenerContainerFactoryConfigurer configurer,
        ConsumerFactory<Object, Object> kafkaConsumerFactory) {

ConsumerFactoryBeanはConsumerFactory<Object, Object>タイプではないためです。

そう:

  • アプリケーションプロパティファイルに以下を追加して、SpringBoot自動構成からKafkaAutoConfigurationを除外するだけです。spring.autoconfigure.exclude=org.springframework.boot.autoconfigure.kafka.KafkaAutoConfiguration
  • または、KafkaListenerContainerFactoryBeanの1つをkafkaListenerContainerFactoryに名前変更して、ブートでオーバーライドします
  • または、ConsumerFactoryBeanの1つをConsumerFactory<Object, Object>タイプとして作成します。
16
Artem Bilan

私はコードの下でそれを達成しました、そしてそれは私のためにうまく働きます。

// LISTENER 1
@Bean
@ConditionalOnMissingBean(name = "yourListenerFactory1")
public ConsumerFactory<String, YourCustomObject1> yourConsumerFactory1() {
   Map<String, Object> props = new HashMap<>();
   props.put(ConsumerConfig.BOOTSTRAP_SERVERS_CONFIG, "localhost:9092");
   props.put(ConsumerConfig.GROUP_ID_CONFIG, "YOUR-GROUP-1");
   return new DefaultKafkaConsumerFactory<>(props, new StringDeserializer(),
     new JsonDeserializer<>(YourCustomObject1.class));
}


@Bean(name = "yourListenerFactory1")
public ConcurrentKafkaListenerContainerFactory<String, YourCustomObject1> 
  yourListenerFactory1() {
   ConcurrentKafkaListenerContainerFactory<String, YourCustomObject1> factory =
       new ConcurrentKafkaListenerContainerFactory<>();
   factory.setConsumerFactory(yourConsumerFactory1());
   ContainerProperties containerProperties = factory.getContainerProperties();
   containerProperties.setPollTimeout(...);
   containerProperties.setAckMode(AckMode...);
   return factory;
}


// LISTENER 2
@Bean
@ConditionalOnMissingBean(name = "yourListenerFactory2")
public ConsumerFactory<String, YourCustomObject2> yourConsumerFactory2() {
   Map<String, Object> props = new HashMap<>();
   props.put(ConsumerConfig.BOOTSTRAP_SERVERS_CONFIG, "localhost:9092");
   props.put(ConsumerConfig.GROUP_ID_CONFIG, "YOUR-GROUP-2");
   return new DefaultKafkaConsumerFactory<>(props, new StringDeserializer(),
      new JsonDeserializer<>(YourCustomObject2.class));
}


@Bean(name = "yourListenerFactory2")
public ConcurrentKafkaListenerContainerFactory<String, YourCustomObject2> 
   yourListenerFactory2() {
    ConcurrentKafkaListenerContainerFactory<String, YourCustomObject2> factory 
         =  new ConcurrentKafkaListenerContainerFactory<>();
    factory.setConsumerFactory(yourConsumerFactory2());
    ContainerProperties containerProperties = factory.getContainerProperties();
    containerProperties.setPollTimeout(...);
    containerProperties.setAckMode(AckMode...);
    return factory;
 }

また、spring.autoconfigure.excludeプロパティを次のように設定しましたITS Mustspring.autoconfigure.exclude=org.springframework.boot.autoconfigure.kafka.KafkaAutoConfiguration

これは私のコンシューマー構成です

消費者1

@KafkaListener(id = "your-cousumer-1",
  topicPattern = "your-topic-1",
  containerFactory = "yourListenerFactory1")
 public void consumer1(YourCustomObject1 data,
                       Acknowledgment acknowledgment,
      @Header(KafkaHeaders.RECEIVED_PARTITION_ID) List<Integer> partitions,
      @Header(KafkaHeaders.RECEIVED_TOPIC) List<String> topics,
      @Header(KafkaHeaders.OFFSET) List<Long> offsets) throws Exception { ... }

消費者2

  @KafkaListener(id = "your-cousumer-2",
                 topicPattern = "your-topic-2",
                 containerFactory = "yourListenerFactory2")
  public void consumer2(YourCustomObject2 data,
                        Acknowledgment acknowledgment,
      @Header(KafkaHeaders.RECEIVED_PARTITION_ID) List<Integer> partitions,
      @Header(KafkaHeaders.RECEIVED_TOPIC) List<String> topics,
      @Header(KafkaHeaders.OFFSET) List<Long> offsets) throws Exception { ...  }

また、私のkafkaテンプレートは

@Autowired
KafkaTemplate<String, Object> kafkaTemplate;
3
Hitesh Kumar

KafkaListener定義で各コンシューマーファクトリを次のように定義できます。

@KafkaListener(topics = "fileTopic", containerFactory = "fileConsumerFactory")
public void fileConsumer(...) {...}

@KafkaListener(topics = "jsonTopic", containerFactory = "jsonConsumerFactory")
public void jsonConsumer(...) {...}
2
ayortanli