一、使用场景
随着spring cloud 的应用,许多微服务应用系统开始普及。目前市面上分布式事务管理的方案很多,二阶段提交,三阶段提交,阿里的seata,以及一些tcc补偿性事务方案。其中阶段提交、tcc都是具有代码入侵性的,如果分布式事务多的情况下,该方案使用起来业务复杂度以及性能稳定性都会大打折扣。seata是一个不错的选择。但是有一种场景,seata确无法使用。
场景:
目前随着国外制裁,国内兴起了国产化的浪潮。于是出现了许多新老系统的过度与替换。例如我现在有10个微服务系统,分别都使用了各自的数据库。同时不可能一次性所有系统都进行统一替换更新,需要一个一个系统的去替换更新。如果10个系统中可能是hibernate框架,更或者其他语言**.net系统,C# 系统等。那么seata在此场景下是无法使用的。那这种情况下我们无法通过分布式事务来做到多系统事务一致性。
为了后面业务便于理解我们定义一种场景
A系统为订单系统,数据库为 db_a
B系统为库存系统,数据库为db_b
c系统为库存系统,数据库为db_c
当下订单以后需要对db_b 中某张表做数据新增,同时需要对db_c系统中的库存表做数据更新。
//下单
public void payOrder(){
// B系统 调用
serviceA.insertOrder();
// C系统 调用
serviceB.updateOrder();
}
public class ServiceB{
public void insertOrder(){
}
}
public class ServiceC{
public void updateOrder(){
}
}
解决方案:
通过全局事务管理+多数据源切换来实现
问题1:你可能会说你这不就搞成单系统了吗?
答:是的,你可以在将所有serviceB,servceC的服务都封装到一个jar包中,在未来你开始替换重构服务B,C时,你可以直接引用并调用。将serviceA.insertOrder(),改为一个通过代理的实现,以后只需改变实现就可以无缝切换。
二、多数据源切换实现
这里只给与大家点播如何实现,具体源码可留言。
//下单
@changeDs("db_a")
public void payOrder(){
// B系统 调用
serviceA.insertOrder();
// C系统 调用
serviceB.updateOrder();
}
public class ServiceB{
@changeDs("db_b")
public void insertOrder(){
}
}
public class ServiceC{
@changeDs("db_c")
public void updateOrder(){
}
}
1、定义@changeDs注解
后面写一个实现 MethodInterceptor 的拦截器来拦截标注了该注解的方法。
2、全局配置dataSourceMap
读取yml中配置的多个数据源,并以db_a 为key写入dataSourceMap中
3、线程队列参数实现数据源的栈队列
ThreadLocal<Deque> 来实现栈,先进后出,后进新出。
获取到db-a时,则通过改key去dataSourceMap 获取对应的数据源。
4、数据源执行拦截
当B方法执行sql时,则做数据源切换,具体实现AbstractRoutingDataSource类。可以参考
https://zhuanlan.zhihu.com/p/696226314
三、全局事务实现
原理:
我们将进行编程式事务,事务的声明与回滚可以见如下简单代码
Connection conn = ...;
try {
// 开启事务:关闭事务的自动提交
conn.setAutoCommit(false);
// 核心操作
// 提交事务
conn.commit();
}catch(Exception e){
// 回滚事务
conn.rollBack();
}finally{
// 释放数据库连接
conn.close();
}
可以不论你使用的是什么事务管理器,springboot使用的是PlatformTransactionManager事务管理器,他的实现其实也就是对所有的Connection 做统一管理。因此我们只需要把每一个执行的Connection 存起来,最后请求结束以后再做提交或者回滚即可。
1、将connection 交给自定义的事务管理器
通过 AbstractDataSource 实现方法 getconnection()是将 Connection交给自定义事务管理管理起来。(定义一个线程变量即可)
2、在方法执行完成以后做提交
根据上面线程变量中的Connection 全部做提交,或者全部做回滚
注:当然你也可以将undolog日志执行前的快照做保存,以便于突然系统突然崩溃最回滚。