Spring for Apache Kafka通过以下方式对事务添加了支持:
可以通过向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是Spring框架的PlatformTransactionManager的一个实现。它在构造函数中提供了对生产者工厂的引用。如果你提供自定义的生产者工厂,它必须支持事务。参阅ProducerFactory.transactionCapable()。你可以使用具有正常Spring事务支持(@Transactional, TransactionTemplate等)的KafkaTransactionManager。如果一个事务是active的,在事务范围内执行的任何KafkaTemplate操作都会使用事务的Producer。管理器根据成功或失败提交或回滚事务。你必须配置KafkaTemplate,使用与事务管理器相同的ProducerFactory。
本节涉及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级别记录)。如有必要,应用程序应采取补救措施,以补偿已提交的主事务。
在容器中使用KafkaTransactionManager来启动Kafka事务,并用@Transactional注解监听器方法来启动另一个事务。请参阅Kafka事务与其他事务管理器的示例,了解链接JDBC和Kafka事务的示例应用程序。
你可以使用KafkaTemplate在本地事务中执行一系列操作。下面的例子展示了如何这样做:
boolean result = template.executeInTransaction(t -> {
t.sendDefault("thing1", "thing2");
t.sendDefault("cat", "hat");
return true;
});
回调函数中的参数是template本身(this)。如果回调正常退出,则事务被提交。如果抛出异常,则回滚事务。
如果有KafkaTransactionManager(或同步)事务在进行中,它不会被使用。取而代之的是一个新的“嵌套”事务。
有了EOSMode.V2(又名BETA),框架唯一支持的模式,不再需要使用相同的transactional.id,甚至对于消费者发起的事务;实际上,它在每个实例上必须是唯一的,就像生产者发起的事务一样。此属性在每个应用程序实例上必须具有不同的值。
通常情况下,当KafkaTemplate是事务性的(配置了一个具有事务能力的生产者工厂时),需要事务。当使用KafkaTransactionManager进行配置时,事务可以由TransactionTemplate、@Transactional方法,调用executeInTransaction,或者由监听器容器启动。任何在事务范围之外使用template 的尝试都会导致template 抛出IllegalStateException。你可以将template的allowNonTransactional属性设置为true。在这种情况下,template将通过调用ProducerFactory的createNonTransactionalProducer()方法,允许操作在没有事务的情况下运行;生产者将被缓存,或绑定线程,作为重用。请参阅使用DefaultKafkaProducerFactory。
当监听器在使用事务时失败时,会调用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;
}
}
你可以提供一个带有KafkaAwareTransactionManager实例的监听器容器。当这样配置时,容器在调用监听器之前启动事务。监听器执行的任何KafkaTemplate操作都会参与到事务中。如果监听器成功地处理了记录(或多个记录,当使用BatchMessageListener时),则在事务管理器提交事务之前,容器会使用producer.sendOffsetsToTransaction()向事务发送偏移量。如果侦听器抛出异常,则回滚事务并且consumer重新定位,以便在下一次poll中检索回滚的记录。有关更多信息和处理重复失败的记录,请参阅After-rollback Processor。
使用事务可以启用精确一次语义(EOS)。
这意味着,对于read→process-write序列,该序列只完成一次是被保证的。(读取和处理有至少一次语义)。
Spring for Apache Kafka 3.0及更高版本仅支持EOSMode.V2: