一、背景
在咱们应用 Seata
作为分布式事务时,有些时候咱们的分布式时候并不是每次都能够胜利的,而对于这些失败的分布式事务就须要进行告诉。这篇文章简略记录一下如何实现告诉。
二、性能实现
- 此处模仿邮件告诉,然而不真正发送邮件,只是简略记录一个日志。
三、注意事项
1、假如咱们的分布式事务回滚失败,在 AT
模式中是会锁定表记录数据的。前期须要获取这条记录的 全局锁
操作,都会失败。
举例:
假如存在如下数据表记录数据
账号 | 金额 |
---|---|
zhangsan | 100 |
zhangsan
这条记录参加分布式事务。
- 在分布式事务中将 zhangsan 的金额批改成 90.
- 另外一个共事,间接操作数据库,将 zhangsan 的记录批改成 80.(此时是能够批改胜利的,因为 Seata 是二阶段提交,在第一阶段完结后,会开释记录的本地锁,不开释记录的全局锁)
- 接下来持续别的分布式事务操作,然而产生了异样,此时分布式事务会回滚失败,因为 zhangsan 的金额变成了 80, 和之前的对应不上。
- 如果再次对 zhangsan 进行分布式事务操作或者须要获取全局锁的操作,那么都会失败。
四、实现步骤
1、编写一个类实现 FailureHandler 接口
FailureHandler
的全类名为:io.seata.tm.api.FailureHandler
,它有一个默认的实现DefaultFailureHandlerImpl
, 此处咱们写一个类继承这个类。
package com.huan.seata.handler;
import io.seata.tm.api.DefaultFailureHandlerImpl;
import io.seata.tm.api.GlobalTransaction;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
/**
* Seata 分布式事物失败的解决
*
* @author huan.fu 2021/10/8 - 下午 1:51
*/
@Component("failureHandler")
@Slf4j
public class EmailSeataFailureHandler extends DefaultFailureHandlerImpl {
@Override
public void onBeginFailure(GlobalTransaction tx, Throwable cause) {super.onBeginFailure(tx, cause);
log.warn("邮件告诉: 分布式事物出现异常:[onBeginFailure],xid:[{}]", tx.getXid());
}
@Override
public void onCommitFailure(GlobalTransaction tx, Throwable cause) {super.onCommitFailure(tx, cause);
log.warn("邮件告诉: 分布式事物出现异常:[onCommitFailure],xid:[{}]", tx.getXid());
}
@Override
public void onRollbackFailure(GlobalTransaction tx, Throwable originalException) {super.onRollbackFailure(tx, originalException);
log.warn("邮件告诉: 分布式事物出现异常:[onRollbackFailure],xid:[{}]", tx.getXid());
}
@Override
public void onRollbackRetrying(GlobalTransaction tx, Throwable originalException) {super.onRollbackRetrying(tx, originalException);
log.warn("邮件告诉: 分布式事物出现异常:[onRollbackRetrying],xid:[{}]", tx.getXid());
}
}
2、退出到 Spring 中的 BeanName 的值
在测试的时候发现 FailureHandler
退出到 Spring 中的 beanName 必须是failureHandler
,否则报错,是 seata 的主动配置中如下代码决定的。
BEAN_NAME_FAILURE_HANDLER
常量的值为 failureHandler
/**
* 源码
* The type Seata auto configuration
*/
@ConditionalOnProperty(prefix = SEATA_PREFIX, name = "enabled", havingValue = "true", matchIfMissing = true)
public class SeataAutoConfiguration {private static final Logger LOGGER = LoggerFactory.getLogger(SeataAutoConfiguration.class);
@Bean(BEAN_NAME_FAILURE_HANDLER)
@ConditionalOnMissingBean(FailureHandler.class)
public FailureHandler failureHandler() {return new DefaultFailureHandlerImpl();
}
@Bean
@DependsOn({BEAN_NAME_SPRING_APPLICATION_CONTEXT_PROVIDER, BEAN_NAME_FAILURE_HANDLER})
@ConditionalOnMissingBean(GlobalTransactionScanner.class)
public GlobalTransactionScanner globalTransactionScanner(SeataProperties seataProperties, FailureHandler failureHandler) {if (LOGGER.isInfoEnabled()) {LOGGER.info("Automatically configure Seata");
}
return new GlobalTransactionScanner(seataProperties.getApplicationId(), seataProperties.getTxServiceGroup(), failureHandler);
}
}
五、测试
拜访curl -X GET http://localhost:50027/createOrder\?accountId\=1\&amount\=10\&hasException\=true
,而后手动打断点,批改数据库数据,使数据不统一,而后代码跑出异样,此处回滚失败,因为 undo_log 表中的数据对不起来。
六、残缺代码
https://gitee.com/huan1993/spring-cloud-parent/tree/master/seata/seata-springboot-failure-handler