Как повторить попытку с весенней кафкой версии 2..2 - PullRequest
0 голосов
/ 11 февраля 2020

Просто пытаюсь найти простой пример с spring-kafka 2.2, который работает с KafkaListener, чтобы повторить последнее неудачное сообщение. Если сообщение терпит неудачу, оно должно быть перенаправлено на другой Topi c, где будут предприняты попытки повторных попыток. У нас будет 4 темы. topi c, retryTopi c, sucessTopi c и errorTopi c Если topi c не удается, следует перенаправить на retryTopi c, где будут сделаны 3 попытки повторения. Если эти попытки не удаются, необходимо перенаправить на errorTopi c. В случае успеха на topi c и retryTopi c, следует перенаправить на sucessTopi c.

1 Ответ

0 голосов
/ 11 февраля 2020

Немного проще с Spring Boot 2.2.4 и Spring для Apache Kafka 2.3.5:

(2.2.x показано ниже).

@SpringBootApplication
public class So60172304Application {

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

    @Bean
    public NewTopic topic() {
        return TopicBuilder.name("topic").partitions(1).replicas(1).build();
    }

    @Bean
    public NewTopic retryTopic() {
        return TopicBuilder.name("retryTopic").partitions(1).replicas(1).build();
    }

    @Bean
    public NewTopic successTopic() {
        return TopicBuilder.name("successTopic").partitions(1).replicas(1).build();
    }

    @Bean
    public NewTopic errorTopic() {
        return TopicBuilder.name("errorTopic").partitions(1).replicas(1).build();
    }

    @Bean
    public ApplicationRunner runner(KafkaTemplate<String, String> template) {
        return args -> {
            template.send("topic", "failAlways");
            template.send("topic", "onlyFailFirst");
            template.send("topic", "good");
        };
    }

    /*
     * A custom container factory is needed until 2.3.6 is released because the
     * container customizer was not applied before then.
     */
    @Bean
    ConcurrentKafkaListenerContainerFactory<?, ?> kafkaListenerContainerFactory(
            ConcurrentKafkaListenerContainerFactoryConfigurer configurer,
            ConsumerFactory<Object, Object> kafkaConsumerFactory,
            KafkaTemplate<Object, Object> template) {

        ConcurrentKafkaListenerContainerFactory<Object, Object> factory =
                new ConcurrentKafkaListenerContainerFactory<Object, Object>() {

                    @Override
                    protected void initializeContainer(ConcurrentMessageListenerContainer<Object, Object> instance,
                            KafkaListenerEndpoint endpoint) {

                        super.initializeContainer(instance, endpoint);
                        customizer(template).configure(instance);
                    }

        };
        configurer.configure(factory, kafkaConsumerFactory);
//      factory.setContainerCustomizer(customizer(template)); // after 2.3.6
        return factory;
    }

    private ContainerCustomizer<Object, Object, ConcurrentMessageListenerContainer<Object, Object>>
            customizer(KafkaTemplate<Object, Object> template) {

        return container -> {
            if (container.getContainerProperties().getTopics()[0].equals("topic")) {
                container.setErrorHandler(new SeekToCurrentErrorHandler(
                        new DeadLetterPublishingRecoverer(template,
                                (cr, ex) -> new TopicPartition("retryTopic", cr.partition())),
                        new FixedBackOff(0L, 0L)));
            }
            else if (container.getContainerProperties().getTopics()[0].equals("retryTopic")) {
                container.setErrorHandler(new SeekToCurrentErrorHandler(
                        new DeadLetterPublishingRecoverer(template,
                                (cr, ex) -> new TopicPartition("errorTopic", cr.partition())),
                        new FixedBackOff(5000L, 2L)));
            }
        };
    }

}

@Component
class Listener {

    private final KafkaTemplate<String, String> template;

    public Listener(KafkaTemplate<String, String> template) {
        this.template = template;
    }

    @KafkaListener(id = "so60172304.1", topics = "topic")
    public void listen1(String in) {
        System.out.println("topic: " + in);
        if (in.toLowerCase().contains("fail")) {
            throw new RuntimeException(in);
        }
        this.template.send("successTopic", in);
    }

    @KafkaListener(id = "so60172304.2", topics = "retryTopic")
    public void listen2(String in) {
        System.out.println("retryTopic: " + in);
        if (in.startsWith("fail")) {
            throw new RuntimeException(in);
        }
        this.template.send("successTopic", in);
    }

    @KafkaListener(id = "so60172304.3", topics = "successTopic")
    public void listen3(String in) {
        System.out.println("successTopic: " + in);
    }

    @KafkaListener(id = "so60172304.4", topics = "errorTopic")
    public void listen4(String in) {
        System.out.println("errorTopic: " + in);
    }

}
spring.kafka.consumer.auto-offset-reset=earliest

Результат:

topic: failAlways
retryTopic: failAlways
topic: onlyFailFirst
topic: good
successTopic: good
retryTopic: failAlways
retryTopic: failAlways
retryTopic: onlyFailFirst
errorTopic: failAlways
successTopic: onlyFailFirst

С пружинным загрузчиком 2.1.12 и пружиной для Apache Кафка 2.2.12:

@SpringBootApplication
public class So601723041Application {

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

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

    @Bean
    public NewTopic retryTopic() {
        return new NewTopic("retryTopic", 1, (short) 1);
    }

    @Bean
    public NewTopic successTopic() {
        return new NewTopic("successTopic", 1, (short) 1);
    }

    @Bean
    public NewTopic errorTopic() {
        return new NewTopic("errorTopic", 1, (short) 1);
    }

    @Bean
    public ApplicationRunner runner(KafkaTemplate<String, String> template) {
        return args -> {
            template.send("topic", "failAlways");
            template.send("topic", "onlyFailFirst");
            template.send("topic", "good");
        };
    }

    @Bean
    ConcurrentKafkaListenerContainerFactory<?, ?> kafkaListenerContainerFactory(
            ConcurrentKafkaListenerContainerFactoryConfigurer configurer,
            ConsumerFactory<Object, Object> kafkaConsumerFactory,
            KafkaTemplate<Object, Object> template) {

        ConcurrentKafkaListenerContainerFactory<Object, Object> factory =
                new ConcurrentKafkaListenerContainerFactory<Object, Object>() {

                    @Override
                    protected void initializeContainer(ConcurrentMessageListenerContainer<Object, Object> instance,
                            KafkaListenerEndpoint endpoint) {

                        super.initializeContainer(instance, endpoint);
                        customize(instance, template);
                    }

        };
        configurer.configure(factory, kafkaConsumerFactory);
        return factory;
    }

    @Bean
    ConcurrentKafkaListenerContainerFactory<?, ?> retryKafkaListenerContainerFactory(
            ConcurrentKafkaListenerContainerFactoryConfigurer configurer,
            ConsumerFactory<Object, Object> kafkaConsumerFactory,
            KafkaTemplate<Object, Object> template) {

        ConcurrentKafkaListenerContainerFactory<Object, Object> factory =
                new ConcurrentKafkaListenerContainerFactory<Object, Object>() {

                    @Override
                    protected void initializeContainer(ConcurrentMessageListenerContainer<Object, Object> instance,
                            KafkaListenerEndpoint endpoint) {

                        super.initializeContainer(instance, endpoint);
                        customize(instance, template);
                    }

        };
        configurer.configure(factory, kafkaConsumerFactory);
        RetryTemplate retryTemplate = new RetryTemplate();
        retryTemplate.setRetryPolicy(new SimpleRetryPolicy(3));
        FixedBackOffPolicy backOffPolicy = new FixedBackOffPolicy();
        backOffPolicy.setBackOffPeriod(5000L);
        retryTemplate.setBackOffPolicy(backOffPolicy);
        factory.setRetryTemplate(retryTemplate);
        return factory;
    }

    private void customize(ConcurrentMessageListenerContainer<Object, Object> container,
            KafkaTemplate<Object, Object> template) {

        if (container.getContainerProperties().getTopics()[0].equals("topic")) {
            container.setErrorHandler(new SeekToCurrentErrorHandler(
                    new DeadLetterPublishingRecoverer(template,
                            (cr, ex) -> new TopicPartition("retryTopic", cr.partition())),
                    0));
        }
        else if (container.getContainerProperties().getTopics()[0].equals("retryTopic")) {
            container.setErrorHandler(new SeekToCurrentErrorHandler(
                    new DeadLetterPublishingRecoverer(template,
                            (cr, ex) -> new TopicPartition("errorTopic", cr.partition())),
                    0)); // no retries here - retry template instead.
        }
    }

}

@Component
class Listener {

    private final KafkaTemplate<String, String> template;

    public Listener(KafkaTemplate<String, String> template) {
        this.template = template;
    }

    @KafkaListener(id = "so60172304.1", topics = "topic")
    public void listen1(String in) {
        System.out.println("topic: " + in);
        if (in.toLowerCase().contains("fail")) {
            throw new RuntimeException(in);
        }
        this.template.send("successTopic", in);
    }

    @KafkaListener(id = "so60172304.2", topics = "retryTopic", containerFactory = "retryKafkaListenerContainerFactory")
    public void listen2(String in) {
        System.out.println("retryTopic: " + in);
        if (in.startsWith("fail")) {
            throw new RuntimeException(in);
        }
        this.template.send("successTopic", in);
    }

    @KafkaListener(id = "so60172304.3", topics = "successTopic")
    public void listen3(String in) {
        System.out.println("successTopic: " + in);
    }

    @KafkaListener(id = "so60172304.4", topics = "errorTopic")
    public void listen4(String in) {
        System.out.println("errorTopic: " + in);
    }

}

РЕДАКТИРОВАТЬ

Чтобы изменить полезную нагрузку в опубликованной записи, вы можете использовать что-то вроде этого (вызов MyRepublisher.setNewValue("new value");).

public class MyRepublisher extends DeadLetterPublishingRecoverer {

    private static final ThreadLocal<String> newValue = new ThreadLocal<>();

    public MyRepublisher(KafkaTemplate<Object, Object> template,
            BiFunction<ConsumerRecord<?, ?>, Exception, TopicPartition> destinationResolver) {

        super(template, destinationResolver);
    }

    @Override
    protected ProducerRecord<Object, Object> createProducerRecord(ConsumerRecord<?, ?> record,
            TopicPartition topicPartition, RecordHeaders headers) {

        ProducerRecord<Object, Object> producerRecord = new ProducerRecord<>(topicPartition.topic(),
                        topicPartition.partition() < 0 ? null : topicPartition.partition(),
                        record.key(), newValue.get(), headers);
        newValue.remove();
        return producerRecord;
    }

    public static void setNewValue(String value) {
        newValue.set(value);
    }

}
...