Spring Kafka非Kafka发起事务如何保证数据库提交先于Kafka提交
是否属于竞态条件
是。这个报错是典型的跨资源事务提交顺序导致的竞态问题:你的业务逻辑中,Kafka消息的可见时机早于数据库事务的提交时机,Service-B在收到Kafka事件后立即发起对Service-A的更新请求时,Service-A的数据库事务还未完成提交,对应的Employee记录尚未对其他事务可见,所以触发记录不存在的报错。
数据库先提交、Kafka后提交的实现方案
Spring官方在废弃ChainedKafkaTransactionManager后,推荐使用事务同步机制实现多资源的提交顺序控制,针对你当前根事务为数据库事务的场景,可通过以下两种方式实现:
方案1:强一致事务型投递(推荐)
该方案可保证数据库和Kafka要么都提交成功,要么都回滚,不会出现数据不一致:
- 配置Kafka生产者启用事务支持:在配置文件中添加
spring.kafka.producer.transaction-id-prefix=自定义前缀,确保KafkaTemplate自动装配事务支持 - 调整KafkaTransactionManager的事务同步策略:在配置类中声明KafkaTransactionManager Bean时,添加以下配置:
@Bean public KafkaTransactionManager kafkaTransactionManager(ProducerFactory producerFactory) { KafkaTransactionManager ktm = new KafkaTransactionManager(producerFactory); // 绑定到当前存在的事务(即你的DataSourceTransactionManager dstm) ktm.setTransactionSynchronization(AbstractPlatformTransactionManager.SYNCHRONIZATION_ON_ACTUAL_TRANSACTION); return ktm; }
配置完成后,你的原有业务代码不需要做任何修改,Kafka事务会自动注册到数据库事务的提交后回调阶段,只有数据库事务提交成功后,Kafka的事务才会提交,消息才会对Service-B可见,完全避免竞态问题。
方案2:轻量提交后回调(适用允许短时间不一致的场景)
如果不需要强一致的事务保证,可手动注册Spring事务的提交后回调,确保只有数据库提交成功后才发送Kafka消息,修改后的代码示例如下:
@Transactional("dstm") public void someMethod(String in) { Employee employee = this.repository.save(new Employee(in)); if(employee.isSomeCondition()) { // 注册事务提交后回调,数据库提交成功才会执行发消息逻辑 TransactionSynchronizationManager.registerSynchronization(new TransactionSynchronization() { @Override public void afterCommit() { sendToKafka(employee); } }); } }
注:该方案的缺点是如果发Kafka消息失败,数据库已经提交,会出现数据不一致,需要配合重试或补偿机制使用。
你提到的Gary Russell的说明是针对根事务由Kafka发起的场景,而你当前根事务由数据库发起的场景,只要开启了KafkaTransactionManager的事务同步配置,Spring的事务同步机制会自动按照依赖顺序提交,完全满足你的需求。
内容的提问来源于stack exchange,提问作者kiranreddykasa
相关产品推荐
相关产品推荐

