本博文碍于作者的学识与见解,难免会有疏漏错误之处,请谅解。
转载请注明出处: www.morcat.cn 谢谢~
前言
本次在做分库分表时,遇到了跨库事务的问题。通过在网上搜索了许多资料后找了一些解决该问题的方法与思路。本文将分为两部分分别介绍常用的分布式事务解决方案以及本次分库分表中如何去解决跨库事务的实践。
常用的分布式事务解决方案
2PC(two-phase commit)
2PC基本介绍
2PC即为两阶段提交,是一种在多节点间实现事务原子提交的算法,用来确保所有节点要么全部提交,要么全部中止。我们可以有多种方案来实现该算法,如基于XA协议的实现,阿里也提供了Seata中间件支持2PC算法。
根据图示可以发现我们引入了一个新组件叫做「协调者」(也称事务管理者),他的作用是协调所有参与者的提交与回滚操作。
2PC既然叫做两阶段提交,那必然是分成了两个阶段。
准备阶段: 协调者会在准备阶段给所有参与者都发送准备命令。如果参与者发现准备命令无法执行或者执行失败时,可以返回失败,如果执行完成则保存事务日志并返回成功。针对于数据库的操作,准备阶段会记录redolog以及undolog为后续的提交做准备。需要注意的是在准备阶段时,数据实际是没有被真正保存的。
提交阶段: 协调者在提交阶段会根据准备阶段各个参与者的返回结果,判断是执行事务还是回滚事务并向所有参与者发起提交命令。其中只有当准备阶段的所有节点都返回成功,协调者才会发送执行事务的命令。如果有一个参与者返回失败,那么协调者就会向所有参与者发送回滚事务的请求,即分布式事务执行失败。
2PC的优缺点
优点:
- a. 保证了事务是强一致性的
- b. 实现相对简单
缺陷:
- a. 同步阻塞问题,如果其中一个参与者响应超时,其他所有参与者都需要等待。
- b. 增加死锁风险,由于每个参与者都可能长时间锁定资源,因此死锁的风险大大加大了
- c. 单点故障问题,如果协调者出现问题。其他所有参与者将无法判断应该提交事务或回滚
2PC的简单实现
以下为2PC的一种简单实现方法的伪代码。
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface MultiTransactional {
/**
* 数据源名称
*/
String[] value() default {};
}
public class TwoPCTransactionAspect {
@Around("@annotation(com.xingren.jooq.multidb.annotation.MultiTransactional)")
public Object startMultiTransaction(ProceedingJoinPoint joinPoint) throws Throwable {
String[] dsNames = ((MethodSignature) joinPoint.getSignature()).getMethod()
.getAnnotation(MultiTransactional.class).value();
Callable callable = () -> joinPoint.proceed();
// 根据传入数据源的顺序构造事务链,在最内层事务调用包含业务逻辑的callable
Callable combined = Stream.of(dsNames).distinct()
.reduce(callable, (c, dsName) -> _startTransaction(c, dsName), (c1, c2) -> c2);
return combined.call();
}
private Callable _startTransaction(Callable callable, String dsName) {
DataSourceTransactionManager transactionManager = getTransactionManager(dsName);
return () -> {
try {
//准备阶段 此时执行的操作都只会记录undolog,redolog
Object result = callable.call();
//提交阶段 如果准备阶段没有参与者有异常,则会执行提交commit
transactionManager.commit(status);
return result;
} catch (Throwable e) {
//提交阶段 如果准备阶段有任何参与者出现异常,都会触发rollback
transactionManager.rollback(status);
throw e;
}
};
}
}
TCC
TCC分别指的是三个步骤,Try - Confirm - Cancel。与2PC类似,它在逻辑也会分成准备和提交两个阶段。但是最大的不同在于TCC需要业务服务自己去实现准备、执行、回滚的代码,因此可以做到非常灵活。
Try: 对资源的预留和锁定
Confirm: 确认操作,是对事务的真正执行。
Cancel 撤销操作,把预留阶段的资源撤销。
从图中其实我们能发现TCC与2PC的操作基本是一致的。只是2PC针对于数据库操作,TCC可以让服务方自己去实现相应操作。
TCC优缺点
优点:
- a. 实现很灵活,几乎可以满足任何分布式事务的场景
缺点:
- a. 与业务代码耦合度高,开发复杂度大大提升
- b. confirm,cancel操作需要考虑幂等性
- c. 其实实现思想还是依据2PC,因此包含了2PC所有缺点
TCC的简单实现
TCC的内部实现还是比较复杂的,因此可以引入一些专门的TCC框架,如ByteTCC,Himly等。
此处的实现仅仅是基于TCC思想的一种伪代码简单实现:
public class TCCService {
public void invoke() {
try {
// try
serviceA.doTry();
serviceB.doTry();
// confirm
serviceA.doConfirm();