前言
同一个商品在很多仓库都有库存 库存-锁定是剩余的商品数量
下了一个订单 里面有多件商品 要分别找每个商品是否还有库存
在一个订单下的多个商品只要有一个是没有库存的 整个订单就要回滚
写Sql语句的时候 先不用找到具体的字段关系 可以先用数字来代替 然后在数据库哪里好使了就焕
抛异常来判断返回的值是什么 而不是用 if else来判断
1. 分布式事务
异常回滚在同一个服务器中很简单
如果在一个逻辑下包含本服务自身事务异常回滚+远程服务器里的事务异常回滚
就涉及到分布式异常
在分布式事务中
A1
B
A2
上面的逻辑就涉及远程调用B服务器的事务异常逻辑
两个情况:
情况一:A无异常 数据库正常操作 B因为远程调用返回给A的是操作失败
A以为B事务异常 所以A操作数据库逻辑回滚 数据库无变动
但其实B只是 因为远程操作时长过长导致失败 网络抖动等都是原因 B已经在数据库里做了操作 数据库记录已经变化
情况二:
A2出现异常 抛出异常 希望 A1和 B都事务回滚 数据库操作
A1还好说因为都在同一段代码里 可以直接回滚 但是B无法直接回滚 因为B在另一个服务器里 除非我们调用B 否则无法回滚 因为事务是一个链接
不要求强一致 远程的库存扣了就扣了 无法立即在同一时间回滚 但是可以等一会慢慢的 最终一致 远程库存回滚把减了的库存再加回来就行 不用此时事务同时回滚
一两个系统不可用 为了保证稳定性 我们会将部分客户转到降级页面 (当前页面不可用)
2. 分布式事务的解决方案
1)刚性事务 ACID 强一致 的2PC
有一个中间管理资源 问每一微服务的本地事务 是否准备好了
当每一个微服务的本地事务都回复准备好了 才开始提交事务
一旦中间管理资源 发现有那个本地食物没提交成功 就全部回滚
这就是强一致性 详细管理所有 并且锁定所有
2)柔性事务 BASE 最终一致性
本地服务保存订单 远程锁库存 本地服务扣积分
最终一致性就保证远程的服务 本来加了200 为了模拟事务回滚 就在代码里检测到异常后-200 达成回滚的效果
try{
调用远程服务操作接口获得准备数据
}confirm{
确认提交 真的操作数据
}
catch{
发现整个流程其他阶段报异常,模拟事务回滚
调用远程服务操作接口 【把刚才的操作如果是+ 现在就- 恢复原样】
}
2.1 springcloud alibaba seata 解决分布式事务
既然事务是一个与数据库的链接
如果想要用seta 去控制分布式事务问题
seta势必要管理好 各服务与数据库的链接 核心 管理好数据源 datasource
seta 的管理就是包装 数据源 将数据源包装过后再使用
下面不适用SpringBoot 2.0 因为已经循环依赖bean
但意思就是 上面与配置文件关联的这个duriddatasource 以参数的形式传进 下面的@PRIMARY 以该bean为主的 方法里 通过代理包装原本的数据源 是seta代理过后的数据源成为主数据源
datasourceAutoConfiguration 是 spring 里的 数据源配置类
里面还维护了其他配置类
配置类中注入了很多数据源 hrika就是spring默认的数据源
上面的注解的意思是 如果我们自己没有配数据源 就会默认走这个数据源
我们可以模仿上面的createDataSource也写一个配置类 注入到容器里 我们自己配数据源 就不会走到上面那个默认的数据源了
这样就能全面的防御和使用 达到分布式事务的目的
但是seta 的 核心 是获得全局锁 就是对所有的事务【即与数据库的链接】串行化操作 此时此刻要把这个请求所到的微服务与数据库的链接锁住 等到整个大业务没问题释放锁
但是全局的锁住事务 就无法达到并发 此时若再有其他请求过来也需要这个数据库的链接 就要等待
所以这个分布式事务的内核是一个串行化 请求需要等待上一个请求的事务释放链接
2. 柔性事务-最大努力通知型-支持大并发
上面的代码也还是在用try去感知 在本地去通知 和 回滚
有没有什么工具可以借助 让这个工具去分发 通知 并且不用写同步代码 现在要感知异常后等待所有事务回滚才结束
MQ 在这个大流程里 如果三个业务有一个失败了 可以发送到MQ里 其他两个业务监听TOPIC发现这个业务失败了 要回滚 就消费消息 执行回滚catch代码 支持并发 有一个失败了 其他两个回滚是异步的 不像上面的手写代码 要等待同步
3. 存在的问题
如果在本地事务中调用远程服务,那样本地该事务的操作就与提供远程服务的接口强依赖了,如果远程服务出现了问题,那么就会拖长事务,事务长时间没有提交,数据库连接就不会被释放,随着太多的数据库连接被占用,可能会导致数据库崩溃。