利用mq的最终一致性,解决分布式事务。

时间:2024-11-14 17:46:52


 我们的订单系统和商品(库存)是两个系统,当我们下订单后,我们就要去修改库存。

分布式系统要满足cap定理。一致性,可靠性,可用性。

我们没法都满足,只能满足两个。因为我们是电商项目。我们要满足服务的高可用。所以我们满足可靠性,和可用性。但是,我们又得满足一致性,这个时候,这个矛盾的解决又要依靠base理论中的最终一致性。我们无法满足强一致,但是我们可以满足最终一致性。

在电商系统中,我们用mq来保证最终一致性。保证mq一定会被消费。保证mq会被消费,我们主要有ack机制和序列化机制。




补充知识:

/floor/blog/1587537

/hu_zhiting/article/details/77164138

 



分布式事务及解决方案

 

 

1、 经典事务

经典事务,是指传统的单机数据库事务,必须具备ACID原则:

原子性(A

所谓的原子性就是说,在整个事务中的所有操作,要么全部完成,要么全部不做,没有中间状态。对于事务在执行中发生错误,所有的操作都会被回滚,整个事务就像从没被执行过一样。

 

一致性(C

事务的执行必须保证系统的一致性,就拿转账为例,A500元,B300元,如果在一个事务里A成功转给B50元,那么不管并发多少,不管发生什么,只要事务执行成功了,那么最后A账户一定是450元,B账户一定是350元。

 

隔离性(I

所谓的隔离性就是说,事务与事务之间不会互相影响,一个事务的中间状态不会被其他事务感知。

 

持久性(D

所谓的持久性,就是说一单事务完成了,那么事务对数据所做的变更就完全保存在了数据库中,即使发生停电,系统宕机也是如此。

 

 

2、 分布式事务

2.1、 什么是分布式事务

事务发生时,其中的参与者主要包含:应用服务、数据库。在经典事务中,无论是应用服务还是数据库都是单机部署,因此可以完全遵循ACID原则。但是现在主流的系统架构都是SOA的架构,以及现在日渐火热的微服务架构中,应用及数据库都会采用分布式集群部署。于是就产生了跨数据源或者跨服务的事务,这就是分布式事务。

 

A系统 开启事务,下单

B系统 开启事务,减库存

 

C系统 --mysql | Oracle

 

2.2、 解决分布式事务的思路

在分布式系统中,就不得不面对CAP的理论。

 

CAP定理是由加州大学伯克利分校Eric Brewer教授提出来的,他指出WEB服务无法同时满足一下3个属性:

· 一致性(Consistency) : 客户端知道一系列的操作都会同时发生(生效)

· 可用性(Availability) : 每个操作都必须以可预期的响应结束

· 分区容错性(Partition tolerance) : 即使出现单个组件无法可用,操作依然可以完成

具体地讲在分布式系统中,在任何数据库设计中,一个Web应用至多只能同时支持上面的两个属性。显然,任何横向扩展策略都要依赖于数据分区。因此,设计人员必须在一致性与可用性之间做出选择。

AP –> 牺牲C (最终一致性

 

而面对高并发的互联网行业,高可用显然会比一致性要重要的多,但是一致性也是不可抛弃的,如何解决这一矛盾?这个时候,大神们有总结出了BASE理论

· Basically Available(基本可用)

· Soft state(软状态)

· Eventually consistent(最终一致性)

BASE理论是对CAP中的一致性和可用性进行一个权衡的结果,理论的核心思想就是:我们无法做到强一致,但每个应用都可以根据自身的业务特点,采用适当的方式来使系统达到最终一致性Eventual consistency)。

 

 

2.3、 柔性事务和刚性事务

刚性事务:严格遵循ACID原则的事务, 例如单机环境下的数据库事务。

柔性事务:指遵循BASE理论(基本可用,最终一致)的事务, 通常用在分布式环境中。

 

因为分布式事务的特点,只能采用柔性事务,常用的柔性事务解决方案有:

两阶段提交(Two Phase Commit, 2PC

有成熟的解决框架,实现简单。

资源锁定周期长,执行效率低,不适合高并发场景

故障恢复困难

u TCC补偿型事务(Try-Confirm-Cancle

实现很复杂,开发成本高

资源锁定粒度小,执行效率高

强隔离性,严格的数据一致性

事务执行周期短

异步确保,基于可靠MQ服务

实现较为复杂,成本略高

MQ的可靠性要求高

事务执行周期长

最大努力通知

实现简单,成本低

不保证最终一致

 

3、 柔性事务的实现方案

3.1、 两阶段提交(2PC

3.1.1、 原理

两阶段提交,基于XA协议,以及JTS协议JTA接口。

XA协议指的是TM(事务管理器)和RM(资源管理器)之间的接口Tuxedo提出。XA中大致分为两部分:事务管理器和本地资源管理器。其中本地资源管理器往往由数据库实现,比如OracleDB2这些商业数据库都实现了XA接口,而事务管理器作为全局的调度者,负责各个本地资源的提交和回滚。XA实现分布式事务的原理如下:

 

正常情况

 

 

异常情况

 

 

JavaEE平台下,WebLogicWebshare等主流商用的应用服务器提供了JTA的实现和支持。而在Tomcat下是没有实现的,但是可以借助第三方的框架JotmAutomikos等来实现,两者均支持spring事务整合

 

3.1.2、 缺点

缺陷

两阶段提交中的第二阶段, 协调者需要等待所有参与者发出yes请求, 或者一个参与者发出no请求后, 才能执行提交或者中断操作. 这会造成长时间同时锁住多个资源, 造成性能瓶颈, 如果参与者有一个耗时长的操作, 性能损耗会更明显.

实现复杂, 不利于系统的扩展, 不推荐.

 

现在几乎很少使用

 

3.2、 TCCTry-Confirm-cancle

3.2.1、 原理

TCC是基于业务层面的事务定义。锁粒度完全由业务自己控制。它本质是一种补偿的思路。它把事务运行过程分成 TryConfirm / Cancel 两个阶段。在每个阶段的逻辑由业务代码控制。这样就事务的锁粒度可以完全*控制。业务可以在牺牲隔离性的情况下,获取更高的性能。

 

l Try 阶段

n Try :尝试执行业务

完成所有业务检查( 一致性 )

预留必须业务资源( 准隔离性 )

l Confirm / Cancel 阶段:

n Confirm :确认执行业务

u 真正执行业务

u 不做任务业务检查

u Confirm 操作满足幂等性

n Cancel :取消执行业务

释放 Try 阶段预留的业务资源

u Cancel 操作满足幂等性

u Confirm Cancel 互斥

 

原理图

 

 

3.2.2、 实例

在电商网站中一个经典的案例是这样的用户下单基于订单系统实现操作订单表;同时下单需要对商品进行减库存操作,在库存系统完成;同时还要对用户进行积分奖励,在用户中心完成。这样就出现了分布式事务。

 

我们用这个业务场景来解释TCC的过程

l try:尝试执行业务

完成业务检查预留必须业务资源

在本例中判断库存是否充足如果充足锁定库存数据

l confirm/cancel:

n Confirm

确认执行业务,利用try阶段预留的资源进行操作,如果失败还要重试,因此要保证接口的幂等性

在本例中我们在库存系统减库存同时在用户中心给用户增加积分

n Cancel

如果confirm阶段出现异常、超时。则调用cancel取消执行业务,进行事务补偿(例如逆向操作)。释放try阶段锁定的资源。如果在事务补偿过程中出现异常,也必须进行重试。如果超过重试次数后,依然无法成功,则记录日志,以便后续人工介入进行事务补偿操作。

u 在本例中,我们尝试恢复库存。同时取消用户积分。

3.2.3、 优缺点

优点

l TCCtry、confirm等操作全部有用户定义因此锁定粒度由用户*控制各个资源独立锁定分别提交释放,无需等待对方。失败后是执行cancel中的补偿型操作即可。

事务执行效率高时间短

能够保证严格的数据一致性

缺点

正是因为需要用户编写所有的tryconfirmcancel操作,另外还有重试、幂等的要求,日志的记录等,实现复杂度较高,开发成本增加。

 

目前有一些开源的TCC框架

TCC-transaction /changmingxie/tcc-transaction

spring-cloud-rest-tcc: /prontera/spring-cloud-rest-tcc

happylifeplat-transaction: /yu199195/happylifeplat-transaction

 

3.2.4、 使用场景

对一致性要求较高事务时效性比较敏感的业务

 

3.3、 异步确保

这种实现方式的思路,其实是源于ebay,后来通过支付宝等公司的布道,在业内广泛使用。其基本的设计思想是将远程分布式事务拆分成一系列的本地事务。如果不考虑性能及设计优雅,借助关系型数据库中的表即可实现。

 

 

在这个实现中将分布事事务拆分成了主动方事务和被动方事务通过mq进行异步通知来确保最终的数据一致性。

 

优点

tcc相比,实现方式较为简单,开发成本低。

缺点

l 数据一致性完全依赖于消息服务,因此消息服务必须是可靠的。

需要处理被动业务方的幂等问题

被动业务失败不会导致主动业务的回滚

业务与消息服务耦合

 

3.3.1、 业务与消息耦合:

 

3.3.2、 业务与消息的分离

 

刚才的实现中,业务方在进行业务处理的同时,还需要对消息数据进行持久化,代码耦合,不方便以后的维护。

 

我们可以采用下面的模式将消息与业务解耦

优点:

消息系统与业务系统解耦

消息系统可以独立存储独立伸缩

缺点

l 每次消息发送,需要两次请求

业务处理系统需要提供业务状态查询接口

 

3.3.3、 使用场景

对事务一致性的时效要求不高的业务例如跨行转账支付宝余额宝转账

 

3.4、 最大努力通知

3.4.1、 原理

最大努力通知主要用于对于事务的一致性不是特别敏感的事务,实现事务的弱一致性。可以通过消息中间件实现。与前面异步确保型操作不同的一点是, 在消息由MQ Server投递到消费者之后, 允许在达到最大重试次数之后正常结束事务.

主业务完成后,通过mq通知被动业务(允许消息丢失)

通知失败后主动方 按照一定的时间阶梯进行消息发送重试直到超过最大重试次数为止

主动方提供业务查询接口以便被动方后续进行通知失败后的补偿恢复丢失消息

3.4.2、 实现

实现