【Spring连载】使用Spring访问 Apache Kafka(十一)----事务

【Spring连载】使用Spring访问 Apache Kafka(十一)----事务Transactions

  • 一、概览
  • 二、使用KafkaTransactionManager
  • 三、事务同步Transaction Synchronization
  • 四、使用消费者发起的事务Consumer-Initiated Transactions
  • 五、KafkaTemplate本地事务
  • 六、transactionIdPrefix
  • 七、KafkaTemplate事务和非事务发布Transactional and non-Transactional Publishing
  • 八、批处理监听器的事务Transactions with Batch Listeners
  • 九、精确一次语义Exactly Once Semantics

本文描述Spring for Apache Kafka如何支持事务。

一、概览

Spring for Apache Kafka通过以下方式对事务添加了支持:

  • KafkaTransactionManager:用于正常的Spring事务支持(@Transactional、TransactionTemplate等)。
  • 事务性KafkaMessageListenerContainer
  • 使用KafkaTemplate的本地事务
  • 与其他事务管理器的事务同步

可以通过向DefaultKafkaProducerFactory提供transactionIdPrefix来启用事务。在这种情况下,工厂维护事务producers的缓存,而不是管理单个共享的Producer。当用户对producer调用close()时,它会被返回到缓存中等待重用,而不是实际关闭。每个生产者的transactional.id属性是transactionIdPrefix + n,其中n以0开头,并为每个新生产者递增。对于运行多个实例的应用程序,每个实例的transactionIdPrefix必须是唯一的。
另请参阅精确一次语义。
另请参阅transactionIdPrefix。
使用Spring Boot,只需设置spring.kafka.producer.transaction-id-prefix属性。Boot将自动配置KafkaTransactionManager bean并将其注入到监听器容器中。
你现在可以在生产者工厂上配置maxAge属性。这对于以下场景很有用:在使用事务生产者时,生产者可能因broker的transactional.id.expiration.ms而闲置。对于当前的kafka-clients,这可能会导致ProducerFencedException而不进行再平衡(rebalance)。通过将maxAge设置为小于transactional.id.expiration.ms,如果生产者超过了其max age,工厂将刷新生产者。

二、使用KafkaTransactionManager

KafkaTransactionManager是Spring框架的PlatformTransactionManager的一个实现。它在构造函数中提供了对生产者工厂的引用。如果你提供自定义的生产者工厂,它必须支持事务。参阅ProducerFactory.transactionCapable()。你可以使用具有正常Spring事务支持(@Transactional, TransactionTemplate等)的KafkaTransactionManager。如果一个事务是active的,在事务范围内执行的任何KafkaTemplate操作都会使用事务的Producer。管理器根据成功或失败提交或回滚事务。你必须配置KafkaTemplate,使用与事务管理器相同的ProducerFactory。

三、事务同步Transaction Synchronization

本节涉及producer-only事务(不是由监听器容器启动的事务);有关在容器启动事务时链接(chaining)事务的信息,请参阅使用消费者发起的事务。如果你想发送记录到kafka并执行一些数据库更新,你可以使用普通的Spring事务管理,比如DataSourceTransactionManager。

@Transactional
public void process(List<Thing> things) {
    things.forEach(thing -> this.kafkaTemplate.send("topic", thing));
    updateDb(things);
}

@Transactional注解的拦截器启动事务,KafkaTemplate会将事务与该事务管理器同步;每次发送都将参与该事务。当该方法退出时,数据库事务将提交,然后提交Kafka事务。如果您希望以相反的顺序执行提交(首先是Kafka),请使用嵌套的@Transactional方法,外部方法配置为使用DataSourceTransactionManager,内部方法配置为使用KafkaTransactionManager。
如果同步事务的提交失败(在主事务提交之后),则异常将被抛出给调用方。以前,它被静默地忽略(只在debug级别记录)。如有必要,应用程序应采取补救措施,以补偿已提交的主事务。

四、使用消费者发起的事务Consumer-Initiated Transactions

在容器中使用KafkaTransactionManager来启动Kafka事务,并用@Transactional注解监听器方法来启动另一个事务。请参阅Kafka事务与其他事务管理器的示例,了解链接JDBC和Kafka事务的示例应用程序。

五、KafkaTemplate本地事务

你可以使用KafkaTemplate在本地事务中执行一系列操作。下面的例子展示了如何这样做:

boolean result = template.executeInTransaction(t -> {
    t.sendDefault("thing1", "thing2");
    t.sendDefault("cat", "hat");
    return true;
});

回调函数中的参数是template本身(this)。如果回调正常退出,则事务被提交。如果抛出异常,则回滚事务。
如果有KafkaTransactionManager(或同步)事务在进行中,它不会被使用。取而代之的是一个新的“嵌套”事务。

六、transactionIdPrefix

有了EOSMode.V2(又名BETA),框架唯一支持的模式,不再需要使用相同的transactional.id,甚至对于消费者发起的事务;实际上,它在每个实例上必须是唯一的,就像生产者发起的事务一样。此属性在每个应用程序实例上必须具有不同的值。

七、KafkaTemplate事务和非事务发布Transactional and non-Transactional Publishing

通常情况下,当KafkaTemplate是事务性的(配置了一个具有事务能力的生产者工厂时),需要事务。当使用KafkaTransactionManager进行配置时,事务可以由TransactionTemplate、@Transactional方法,调用executeInTransaction,或者由监听器容器启动。任何在事务范围之外使用template 的尝试都会导致template 抛出IllegalStateException。你可以将template的allowNonTransactional属性设置为true。在这种情况下,template将通过调用ProducerFactory的createNonTransactionalProducer()方法,允许操作在没有事务的情况下运行;生产者将被缓存,或绑定线程,作为重用。请参阅使用DefaultKafkaProducerFactory。

八、批处理监听器的事务Transactions with Batch Listeners

当监听器在使用事务时失败时,会调用AfterRollbackProcessor在回滚发生后执行一些操作。将默认的AfterRollbackProcessor与record监听器一起使用时,将执行seek,以便重新传递失败的记录。然而,使用batch监听器,整个batch将被重新传递,因为框架不知道batch中的哪个记录失败了。有关详细信息,请参阅After-rollback Processor。
当使用batch监听器时,有一种替代机制来处理batch的故障;BatchToRecordAdapter。当使用BatchToRecordAdapter配置batchListener设置为true的容器工厂时,监听器一次被一条记录唤起。这允许在 batch中进行错误处理,同时仍然可以根据异常类型停止处理整个batch。框架提供了默认的BatchToRecordAdapter,可以使用标准的ConsumerRecordRecoverer(如DeadLetterPublishingRecoverer)对其进行配置。以下测试用例配置片段说明了如何使用此功能:

public static class TestListener {
    final List<String> values = new ArrayList<>();

    @KafkaListener(id = "batchRecordAdapter", topics = "test")
    public void listen(String data) {
        values.add(data);
        if ("bar".equals(data)) {
            throw new RuntimeException("reject partial");
        }
    }
}

@Configuration
@EnableKafka
public static class Config {

    ConsumerRecord<?, ?> failed;

    @Bean
    public TestListener test() {
        return new TestListener();
    }

    @Bean
    public ConsumerFactory<?, ?> consumerFactory() {
        return mock(ConsumerFactory.class);
    }

    @Bean
    public ConcurrentKafkaListenerContainerFactory<String, String> kafkaListenerContainerFactory() {
        ConcurrentKafkaListenerContainerFactory factory = new ConcurrentKafkaListenerContainerFactory();
        factory.setConsumerFactory(consumerFactory());
        factory.setBatchListener(true);
        factory.setBatchToRecordAdapter(new DefaultBatchToRecordAdapter<>((record, ex) ->  {
            this.failed = record;
        }));
        return factory;
    }

}

九、精确一次语义Exactly Once Semantics

你可以提供一个带有KafkaAwareTransactionManager实例的监听器容器。当这样配置时,容器在调用监听器之前启动事务。监听器执行的任何KafkaTemplate操作都会参与到事务中。如果监听器成功地处理了记录(或多个记录,当使用BatchMessageListener时),则在事务管理器提交事务之前,容器会使用producer.sendOffsetsToTransaction()向事务发送偏移量。如果侦听器抛出异常,则回滚事务并且consumer重新定位,以便在下一次poll中检索回滚的记录。有关更多信息和处理重复失败的记录,请参阅After-rollback Processor。
使用事务可以启用精确一次语义(EOS)。
这意味着,对于read→process-write序列,该序列只完成一次是被保证的。(读取和处理有至少一次语义)。
Spring for Apache Kafka 3.0及更高版本仅支持EOSMode.V2:

  • V2-又名fetch-offset-request fencing
    这要求broker的版本为2.5或更高。
    对于模式V2,不必为每个“group.id/topic/dipartition”提供一个生产者,因为消费者元数据与偏移量一起被发送到事务,broker可以使用该信息来确定生产者是否被隔离(fenced )。

你可能感兴趣的:(spring,apache,kafka)