分布式事务产生的原因
- 数据库分库分表
- 微服务化
- 在微服务架构中,每个服务在用
本地事务
的时候,知道自己执行的事务是成功还是失败,但是无法知道其他服务节点的事务执行情况,因此需要引入协调者TM
,负责协调参与者RM
的行为,并最终决定这些参与者是否把事务进行提交。
随着微服务架构的流行,让分布式事务问题日益突出, 那么常见的分布式事务解决方案有哪些呢? 如何理解最终一致性和它的事务补偿机制呢?
刚性事务 - 强一致性
如上图,这是个标准的全局事务,事务管理器
控制着全局事务,管理事务的生命周期,并通过XA协议与资源管理器
协调资源;资源管理器负责控制和管理实际的资源 (这里的资源管理器,可以是一个DBMS,或者消息服务管理系统)
两阶段提交
它是XA用于在全局事务中协调多个资源的机制,常用于事务管理器
和资源管理器
之间,解决一致性问题,分两阶段:
- 提交事务请求
- 执行事务请求
2PC的问题
- 效率低,与本地事务相比,XA协议的系统开销比较大(数据被锁定的时间跨度整个事务,直到全局事务的结束),只有支持XA协议的资源才能参与分布式事务。
- 2PC是反可伸缩模式的,在事务处理过程中,参与者需要一直持有资源直到整个事务的结束,这样当业务规模越来越大的情况下,它的局限性就越明显。
- 数据不一致,在2pc中的第二阶段时,当TM向RM发送提交请求之后,发生局部的网络异常或者在发送提交请求过程中TM发生故障, 这会导致只有一部分RM收到了提交请求,然后没有收到提交请求的RM不会执行事务的提交,于是整个分布式系统便会出现数据不一致。
- 单点故障, 由于TM的重要性,一旦发生故障,整个事务失效
3PC的改进
增加了超时机制
, 主要解决单点故障问题,并减少资源锁定时间,一旦RM无法及时收到来至TM的信息之后,它会默认执行Commit操作, 而不会一直持有事务资源并处于阻塞状态。但是这种机制同样会导致数据不一致的问题,由于网络的原因,TM发送的回滚动作,没有被RM及时的收到,那么RM等待超时后就执行了提交操作,这样就和收到回滚操作并执行的RM之间存在了数据不一致的情况。
柔性事务 - 最终一致性
在2008年,eBay公布了基于BASE
准则的最终一致性解决方案,它主要采用了消息队列来辅助实现事务控制流程,其核心通过消息队列的方式来异步执行分布式处理的任务,如果事务失败,则可以发起人工重试的纠正流程(比如对账系统,对处于dead letter queue
的问题进行处理)
消息发送一致性
微服务架构下,需要通过网络进行通信,就自然引入了数据传输的不确定性,也就是CAP原理中的P-分区容错,而这里的消息发送一致性
是可靠消息的保证。
生成消息的业务动作与消息发送的一致(e.g: 如果业务操作成功,那么由这个业务操作所产生的消息一定会成功投递出去,否则就丢失消息)
如上图,保证消息发送一致性的一般流程如下:
- Producer先把消息发送给消息中间件服务,消息的状态标记为
待确认
,这个状态并不会被Consumer消费,对于长期待确认
的消息,消息中间件会调用Producer的查询接口,查看最新状态,根据结果决定是否删除消息。 - Producer执行完业务操作后,向消息中间件服务,发送确认消息
- 这时消息的状态会被更改为
待发送(可发送)
- Consumer监听并接收待发送状态的消息,执行业务处理
- Consumer业务处理后,向消息中间件服务发送
ACK
,确认消息已经收到(消息中间件服务将从队列中删除该消息)
消息的ACK确认流程中,任何一个环节都可能会出问题!
对未ACK
的消息,采用按规则重新投递的方式进行处理(很多MQ都提供at least once的投递,持久化和重试机制),一般还会设置重发
的次数, 超过次数的消息会进入dead letter queue
,等待人工干预或者延后定时处理。
业务接口的幂等性
消息的重复发送会导致业务接口出现重复调用的问题,主要原因就是消息没有及时收到ACK确认导致的, 那如何实现幂等性设计呢?
在实际的业务场景中, 业务接口的幂等性设计,常结合查询操作一起使用,
比如根据唯一标识
查询消息是否被处理过, 或者根据消费日志表,来维护消息消费的记录。
保证最终一致性的模式
- 可查询模式,任何一个服务操作都提供一个可查询接口,用来向外部输出操作执行的状态,下游Consumer可以通过接口得知服务操作执行的状态,然后根据不同的状态做不同的处理操作(执行或者取消), 该模式对业务接口有一定侵入性。
- 补偿模式, 有了查询模式,我们能够知道操作的具体状态,如果处于不正常状态,我们可以
修正
操作中出现的问题,或许是重新执行,或许取消已经完成的操作,通过修复是的整个分布式系统达到最终一致。 - 最大努力通知模式, 在调用支付宝交易接口或微信支付接口时,一般会在回调页面和接口里,解密参数,然后调用系统中更新交易状态相关的服务,将订单更新为付款成功。同时,只有当回调页面中输出了success字样或者标识业务处理成功相应状态码时,支付宝才会停止回调请求。否则,支付宝会每间隔一段时间后,再向客户方发起回调请求,直到输出成功标识为止。