强一致性
2PC(prepare + commit)
解决不同数据库的事务一致性问题。由协调者和参与者两个角色完成。
第一阶段:先执行DML语句,锁定资源,但是不提交。
第二阶段:根据第一阶段的返回结果,决定是commit还是rollback。
缺点:1、同步阻塞的性能问题,锁定资源后要等待所有节点返回,不适合高并发场景。
2、单点故障问题,二阶段时,如果协调者挂掉,存在悬而不决的问题,虽然协调者会重新选择一个协调者出来,但是无法解决因前一个协调者宕机导致参与者同步阻塞的问题;如果参与者挂掉,不知道是否要回滚。
3、数据不一致问题,存在脑裂问题,协调者发出commit后,只有一部分参与者收到。
MySQL的XA规范就是2PC的例子,RM资源管理器作为参与者,一般是MySQL实例,TM事务管理者作为协调者,一般是binlog或者应用本身。
3PC(CanCommit + PreCommit + DoCommit)
2PC的第一阶段拆分为两步,第一阶段只是询问,第二阶段才锁定资源。
2PC只有协调者有超时判断,3PC增加了参与者的超时判断。协调者响应超时或者失败时,因为一阶段已经都同意修改了,认为大概率能够commit,因此解决了2PC“保守”的问题。
缺点:如果协调者发送了回滚的指令,超时未收到仍然会存在不一致性的问题。
参考:理解2PC,3PC与TCC_我,大虫的博客-CSDN博客_tcc和3pc的区别
最终一致性
TCC
解决不同服务之间的事务一致性问题,和2PC的思路一样,只是增加了重试的能力,要求幂等,因为commit或者cancel失败会一直重试。重试是由TCC框架来完成的,并非业务方自己去做。
拆分为多个小事务,try阶段只是预留资源,不会锁定资源,不存在同步阻塞的问题,只保证最终一致性。
消息队列
发送失败:扣减和写消息表放在同一个事务里,根据消息表不停的发送消息直至发送成功,会带来重复消息。RocketMQ支持事务消息,会定时回调,根据业务情况,决定是否真正提交。
丢失消费:ACK机制。
重复消费:加钱和写去重表放在同一个事务里,每次消费消息时会检查去重表。
事务状态表
一个事务需要N步执行完,就有N个状态,如果没有执行到状态N,就要不断重试,要求幂等。
对账
比如微博的关注关系(有两张表,关注表、粉丝表)、订单系统的两个查询维度(卖家、买家)、从订单支付到下发仓库再到出仓完成。
可以先保证一张表的数据准确,另一张表以该表为基准进行校准。
全量对账:每天运行定时任务比对两个表。
增量对账:可以是一个定时任务,也可以采用消息中间件。
弱一致性
基于状态的补偿
库存每扣一次,都会生成一条流水记录,从初始的“占用”状态到订单支付成功之后的“释放”状态。对于创建失败的订单或者超时未支付的订单,这些订单对应的库存进行回收。
不会在乎,先创建订单还是先扣库存,保证了不会超卖,相比事务状态表的不断重试,又很好的保证了性能。
妥协方案
先扣库存,后创建订单,如果订单创建失败,先重试,不行就回退库存。回退库存失败,进行人工干预。
参考:分布式事务——2PC、3PC 和 TCC | huzb的博客
面试被问分布式事务(2PC、3PC、TCC),这样解释没毛病! - 程序员小富 - 博客园
分布式事务:XA,2PC,3PC,TCC_kusedexingfu的博客-CSDN博客_2pc tcc xa
2PC与3PC区别_这瓜保熟么的博客-CSDN博客_2pc和3pc的区别
幂等性接口如何设计
1、新增插入的接口,根据uuid主键唯一性保证,不会重复插入。如使用雪花算法生成uuid。
2、删除接口,根据唯一主键删除。
3、查询接口本身就是幂等的。
4、关键是修改接口:
1)根据状态修改,比如只有是已支付状态,才能进行退款操作。
2)加锁修改(悲观锁、乐观锁、分布式锁),读也要加锁,不然读的数据被修改了,根据读的数据去修改就不对了。
5、对于前端点击多次的去重问题,可以采用token机制。对于一条记录只颁发一个token。
参考:答面试官问:怎么实现接口幂等性 | Laravel China 社区 (learnku.com)
接口幂等性如何设计? | 不才陈某技术博客 (chenjiabing666.github.io)