前言
项目关键点进行复盘
项目关键点:iris,yms,yts,并发
一、事务管理器,写锁阻塞,事务传播机制
参考资料:结合Spring事务源码分析TransactionSynchronizationAdapter之afterCommit回调方法事务回滚问题.
1.业务演变过程:
代码逻辑:
try {
1.更新本地单据数据,本地写操作
2.rpc调用远程接口,跨服务写操作
3.发事件到mq,可靠消息保证跨领域分布式事务
} catch {
4.抓异常
5.更新单据为生效失败状态,本地写事务提交
}
场景复盘:这是审批流程的部分代码,业务希望无论如何要使审批流程正常走完,即便try代码块中有调用失败,也要在catch中吞掉异常,并记录失败状态,走补偿机制(重新执行或定时任务接口)
try中2远程写操作封装在另一service业务类中,该类标记@Transactional注解,事务传播属性为默认required。
问题出现:该service中的异常未处理,向上抛出。因spring管理的声明式事务是基于aop实现的,该事务在切入时,默认加入外层的当前主事务,在逻辑异常时将事务已标记回滚,回到主类。而主类catch住异常,走5本地写提交时发现,事务已被标记为回滚,主事务无法正常提交,报出rollback only异常。
场景终态:流程抛出rollback only异常,只要业务报错就走不下去,数据永远回滚,生效失败状态永远不会出现。
2.业务改进:
以上异常问题在主子类对事务处理不一致导致,未能满足业务要求,遂想出改进方案:
①.让事务提交:service子类中不抛出异常,而是catch异常封装一个result携带异常信息返回,走事务提交逻辑(注意:如未进行rpc调用则直接返回异常result,已rpc远程写则需要考虑分布式事务问题,回滚远端数据)。但是此方案需要改动其他service类逻辑,不采用。
②.让主事务回滚,再开一个新事务处理生效失败标记操作,可尝试改动
catch {
1.抓异常
2.更新单据为生效失败状态,本地写事务提交(在新事务中完成此操作)
3.抛出封装后的业务异常
}
操作:catch块中抛异常 让主事务也回滚,然后新开事务来处理失败标记场景
执行2更新单据为生效失败方法的事务传播属性改为required_new
之前学习中,可以看到很多弱一致性或不加入事务的写操作可采用这种方法,标记为required_new后,新开事务,在自己的事务中执行业务并提交事务,使之互不干扰。但此场景下依然有问题
问题出现: 依然失败,报错数据库写锁阻塞。之前的学习中,本事务和新开事务一般操作的为不相干的数据,二者互不影响。但此事务中,try块里1操作写本地事务,占用着本条数据行,事务还未结束。catch中开启新事务操作同一行数据,造成数据库写锁阻塞,造成死锁。
3.再次方案改进
复盘之前方案:1中问题是主子事务操作不一致,则2改进点为将二者变为一致,新开事务进行改状态。2中问题为新旧事务操作同一数据,则改进点为,在本事务结束,或者说数据落盘之后(connection.commit()方法执行后),再进行标记生效失败操作。可以想到事务同步回调机制方案,尝试改动:
catch {
1.回调方法,在当前事务结束时调用(更新单据为生效失败状态,本地写事务提交)
2.抓异常
3.抛出封装后的业务异常
}
操作: catch中抛出异常,让主事务也回滚(和2一样),然后利用同步机制,在主事务结束时触发回调机制
TransactionSynchronizationManager.registerSynchronization机制 TransactionSynchronizationAdapter的回调方法afterCompletion(此时事务回滚已落盘,所以回调机制成功与否不影响本次事务的提交与回滚)来执行更新单据为生效失败状态操作
catch (Exception e) {
afterTransactionCompletion(id);
throw new RuntimeException(e.getMessage(),e);
}
private void afterTransactionCompletion(String id) {
if (TransactionSynchronizationManager.isActualTransactionActive()) {
TransactionSynchronizationManager.registerSynchronization(new TransactionSynchronizationAdapter() {
@Override
public void afterCompletion(int status) {
if (status == 1) {
//修改单据为生效失败
}
}
});
}
}
(额外:若回调方法也有一系列操作,其中如果有异常场景:当回调操作中service的propagation = Propagation.REQUIRES_NEW时,已执行的部分事务会回滚;当propagation = Propagation.REQUIRED时,已执行的部分事务不会回滚)。