MySQL
的事务是数据一致性的典范,事务内的执行要么都成功,要么都失败。但业务系统涉及系统间的相互调用,涉及的数据库也不尽相同,因此实现数据一致性仍是有挑战的。数据库
首先了解强一致性和弱一致性。在微服务中,系统间经过HTTP
的方式相互调用,很难实现数据的强一致。咱们这里主要说弱一致性,也就是数据最终一致性。异步
数据一致性还有个重要的前提:支持幂等。也就是说,只要请求参数不变,那么不管重复请求多少次,结果都同样。在对接第三方支付时,这个词出现的频率仍是老高的。微服务
蜗牛要在一家电商网站买电子书,整个购买流程和涉及的系统虚构以下图。过程涉及检查它是否已经买过,而后是生成订单号、支付、交付(实际上订单系统不包含支付功能,这里简化处理)。性能
<center>网站
</center>spa
交付涉及三个系统,在任何一个系统内,数据库的事务都只能保证它服务内的数据一致。并且,若是在事务过程当中引入了调用第三方的HTTP
请求,数据库的事务执行结果甚至有可能会被污染。好比,HTTP
请求超时返回失败,但实际上请求却执行成功的场景。设计
参考以前写的 Saga Pattern模式,对任何一个外部服务的调用都引入两个行为:执行和补偿。补偿是对执行结果的修正。好比对于用户支付失败的场景,补偿行为能够是接口重试、能够是直接退款、还能够推送MQ
异步修复等。code
统一使用interface
来定义一套规范。每一种支付方式以及购买产品所调用的外部服务可能不尽相同,用interface
来达到统一调用的目的。补偿的行为都基于执行动做返回的错误,因此咱们须要实现本身的错误码。blog
type DeliverPattern interface { //是否须要执行交付流程 Check(ctx *context.Context) (bool, error) //支付及支付补偿 DoPay(ctx *context.Context) error PayCompensate(ctx *context.Context, doErr error) error //交付及对应的补偿 DoDeliver(ctx *context.Context) DeliverCompensate(ctx *context.Context, doErr error) error }
对于如何补偿,不一样的业务有不一样的补偿方式,当让不能一律而论。但总体的思想,我以为仍是不外乎两种。固然,下面的两种描述是本身这样称呼的。接口
事务类
首先即是数据库事务
类,任何一个流程失败,整个事务内的操做所有反向回滚。沿着这样的思路,接口定义中PayCompensate
应该实现DoPay
的回滚操做,而DeliverCompensate
应该实现DoPay
以及DoDeliver
的回滚操做。
咱们须要在操做的同时维护一个回滚操做的队列,任何一个Do
行为的完成,都须要在回滚队列中插入对应的回滚方法。当后面任何一个Do
操做失败,统一执行回滚队列的方法。
这样的困境在于你不能彻底保证回滚方法必定成功执行。并且出于性能考虑,还须要结合异步队列,经过后台重试来保证整个业务流程完全回滚成功或回滚失败。
状态类
每一个业务都会拆分红各个更小的块,就跟写代码空行同样,这里的DeliverPattern
也是根据业务流程拆分红更小的执行粒度。咱们能够为每一个Do
行为都设置一个状态码,相似于状态机,记录每一次购买的各个状态。
const ( StatusDoPaySuccess = 1 StatusDoPayCompensateSuccess = 2 StatusDoPayCompensateFailure = 3 )
这样咱们补偿方法中执行的再也不是回滚操做,而是Do
方法的重试。若是补偿成功,继续执行后续的操做,若是补偿失败,记录下该状态,后续看看怎么补偿。