【发布时间】:2021-07-21 16:05:40
【问题描述】:
我正在尝试为 Rabbit 和 Kafka 使用一个事务管理器 (ChainedTransactionManager),链接 RabbitTransactionManager 和 KafkaTransactionManager。我们打算实现尽力而为的 1 阶段提交。
为了测试它,事务方法在 2 次操作(向 Rabbit 交换器发送消息并在 Kafka 中发布和事件)之后抛出异常。运行测试时,日志提示已启动回滚,但消息最终还是出现在 Rabbit 中。
- 注意事项:
- 我们使用 QPid 来模拟内存中的 RabbitMQ 进行测试(版本 7.1.12)
- 我们使用内存中的 Kafka 进行测试 (spring-kafka-test)
- 其他相关框架/库:spring-cloud-stream
出现问题的方法如下:
@Transactional
public void processMessageAndEvent() {
Message<String> message = MessageBuilder
.withPayload("Message to RabbitMQ")
.build();
outputToRabbitMQExchange.output().send(message);
outputToKafkaTopic.output().send(
withPayload("Message to Kafka")
.setHeader(KafkaHeaders.MESSAGE_KEY, "Kafka message key")
.build()
);
throw new RuntimeException("We want the previous changes to rollback");
}
这是主要的 Spring-boot 应用程序配置:
@SpringBootApplication
**@EnableTransactionManagement**
public class MyApplication {
public static void main(String[] args) {
SpringApplication.run(MyApplication.class, args);
}
}
这是 TransactionManager 的配置:
@Bean
public RabbitTransactionManager rabbitTransactionManager(ConnectionFactory cf) {
return new RabbitTransactionManager(cf);
}
@Bean(name = "transactionManager")
@Primary
public ChainedTransactionManager chainedTransactionManager(RabbitTransactionManager rtm, BinderFactory binders) {
ProducerFactory<byte[], byte[]> pf = ((KafkaMessageChannelBinder) binders.getBinder("kafka", MessageChannel.class))
.getTransactionalProducerFactory();
KafkaTransactionManager<byte[], byte[]> ktm = new KafkaTransactionManager<>(pf);
ktm.setTransactionSynchronization(AbstractPlatformTransactionManager.SYNCHRONIZATION_ON_ACTUAL_TRANSACTION);
return new ChainedKafkaTransactionManager<>(ktm, rtm);
}
最后是application.yml文件中的相关配置:
spring:
application:
name: my-application
main:
allow-bean-definition-overriding: true
cloud:
stream:
bindings:
source_outputToRabbitMQExchange:
content-type: application/json
destination: outputToRabbitMQExchange
group: ${spring.application.name}
sink_outputToKafkaTopic:
content-type: application/json
destination: outputToKafkaTopic
binder: kafka
rabbit:
bindings:
output_outputToRabbitMQExchange:
producer:
transacted: true
routing-key-expression: headers.myKey
kafka:
bindings:
sink_outputToKafkaTopic:
producer:
transacted: true
binder:
brokers: ${...kafka.hostname}
transaction:
transaction-id-prefix: ${CF_INSTANCE_INDEX}.${spring.application.name}.T
default-binder: rabbit
kafka:
producer:
properties:
max.block.ms: 3000
transaction.timeout.ms: 5000
enable.idempotence: true
retries: 1
acks: all
bootstrap-servers: ${...kafka.hostname}
当我们执行该方法时,我们可以看到消息仍在 Rabbit 中,尽管日志显示事务要回滚。
我们可能遗漏或误解了什么?
【问题讨论】:
标签: apache-kafka rabbitmq spring-kafka spring-cloud-stream spring-rabbit