我们的订单系统和商品(库存)是两个系统,当我们下订单后,我们就要去修改库存。
分布式系统要满足cap定理。一致性,可靠性,可用性。
我们没法都满足,只能满足两个。因为我们是电商项目。我们要满足服务的高可用。所以我们满足可靠性,和可用性。但是,我们又得满足一致性,这个时候,这个矛盾的解决又要依靠base理论中的最终一致性。我们无法满足强一致,但是我们可以满足最终一致性。
在电商系统中,我们用mq来保证最终一致性。保证mq一定会被消费。保证mq会被消费,我们主要有ack机制和序列化机制。
补充知识:
/floor/blog/1587537
/hu_zhiting/article/details/77164138
分布式事务及解决方案
1、 经典事务
经典事务,是指传统的单机数据库事务,必须具备ACID原则:
l 原子性(A)
所谓的原子性就是说,在整个事务中的所有操作,要么全部完成,要么全部不做,没有中间状态。对于事务在执行中发生错误,所有的操作都会被回滚,整个事务就像从没被执行过一样。
l 一致性(C)
事务的执行必须保证系统的一致性,就拿转账为例,A有500元,B有300元,如果在一个事务里A成功转给B50元,那么不管并发多少,不管发生什么,只要事务执行成功了,那么最后A账户一定是450元,B账户一定是350元。
l 隔离性(I)
所谓的隔离性就是说,事务与事务之间不会互相影响,一个事务的中间状态不会被其他事务感知。
l 持久性(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、 柔性事务和刚性事务
l 刚性事务:严格遵循ACID原则的事务, 例如单机环境下的数据库事务。
l 柔性事务:指遵循BASE理论(基本可用,最终一致)的事务, 通常用在分布式环境中。
因为分布式事务的特点,只能采用柔性事务,常用的柔性事务解决方案有:
u 两阶段提交(Two Phase Commit, 2PC)
n 有成熟的解决框架,实现简单。
n 资源锁定周期长,执行效率低,不适合高并发场景
n 故障恢复困难
u TCC补偿型事务(Try-Confirm-Cancle)
n 实现很复杂,开发成本高
n 资源锁定粒度小,执行效率高
n 强隔离性,严格的数据一致性
n 事务执行周期短
u 异步确保,基于可靠MQ服务
n 实现较为复杂,成本略高
n 对MQ的可靠性要求高
n 事务执行周期长
u 最大努力通知
n 实现简单,成本低
n 不保证最终一致
3、 柔性事务的实现方案
3.1、 两阶段提交(2PC)
3.1.1、 原理
两阶段提交,基于XA协议,以及JTS协议JTA接口。
XA协议指的是TM(事务管理器)和RM(资源管理器)之间的接口,由Tuxedo提出。XA中大致分为两部分:事务管理器和本地资源管理器。其中本地资源管理器往往由数据库实现,比如Oracle、DB2这些商业数据库都实现了XA接口,而事务管理器作为全局的调度者,负责各个本地资源的提交和回滚。XA实现分布式事务的原理如下:
l 正常情况
l 异常情况
在JavaEE平台下,WebLogic、Webshare等主流商用的应用服务器提供了JTA的实现和支持。而在Tomcat下是没有实现的,但是可以借助第三方的框架Jotm、Automikos等来实现,两者均支持spring事务整合。
3.1.2、 缺点
l 缺陷:
n 两阶段提交中的第二阶段, 协调者需要等待所有参与者发出yes请求, 或者一个参与者发出no请求后, 才能执行提交或者中断操作. 这会造成长时间同时锁住多个资源, 造成性能瓶颈, 如果参与者有一个耗时长的操作, 性能损耗会更明显.
n 实现复杂, 不利于系统的扩展, 不推荐.
现在几乎很少使用
3.2、 TCC(Try-Confirm-cancle)
3.2.1、 原理
TCC是基于业务层面的事务定义。锁粒度完全由业务自己控制。它本质是一种补偿的思路。它把事务运行过程分成 Try、Confirm / Cancel 两个阶段。在每个阶段的逻辑由业务代码控制。这样就事务的锁粒度可以完全*控制。业务可以在牺牲隔离性的情况下,获取更高的性能。
l Try 阶段
n Try :尝试执行业务
u 完成所有业务检查( 一致性 )
u 预留必须业务资源( 准隔离性 )
l Confirm / Cancel 阶段:
n Confirm :确认执行业务
u 真正执行业务
u 不做任务业务检查
u Confirm 操作满足幂等性
n Cancel :取消执行业务
u 释放 Try 阶段预留的业务资源
u Cancel 操作满足幂等性
u Confirm 与 Cancel 互斥
原理图:
3.2.2、 实例
在电商网站中,一个经典的案例是这样的:用户下单,基于订单系统实现,操作订单表;同时下单需要对商品进行减库存操作,在库存系统完成;同时还要对用户进行积分奖励,在用户中心完成。这样就出现了分布式事务。
我们用这个业务场景来解释TCC的过程:
l try:尝试执行业务
n 完成业务检查、预留必须业务资源
n 在本例中:判断库存是否充足,如果充足,锁定库存数据。
l confirm/cancel:
n Confirm :
u 确认执行业务,利用try阶段预留的资源进行操作,如果失败还要重试,因此要保证接口的幂等性
u 在本例中,我们在库存系统减库存,同时在用户中心给用户增加积分。
n Cancel :
u 如果confirm阶段出现异常、超时。则调用cancel取消执行业务,进行事务补偿(例如逆向操作)。释放try阶段锁定的资源。如果在事务补偿过程中出现异常,也必须进行重试。如果超过重试次数后,依然无法成功,则记录日志,以便后续人工介入进行事务补偿操作。
u 在本例中,我们尝试恢复库存。同时取消用户积分。
3.2.3、 优缺点
优点:
l TCC的try、confirm等操作全部有用户定义,因此锁定粒度由用户*控制。各个资源独立锁定,分别提交、释放,无需等待对方。失败后是执行cancel中的补偿型操作即可。
l 事务执行效率高,时间短
l 能够保证严格的数据一致性。
缺点:
l 正是因为需要用户编写所有的try、confirm、cancel操作,另外还有重试、幂等的要求,日志的记录等,实现复杂度较高,开发成本增加。
目前有一些开源的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进行异步通知来确保最终的数据一致性。
优点:
l 与tcc相比,实现方式较为简单,开发成本低。
缺点:
l 数据一致性完全依赖于消息服务,因此消息服务必须是可靠的。
l 需要处理被动业务方的幂等问题
l 被动业务失败不会导致主动业务的回滚
l 业务与消息服务耦合
3.3.1、 业务与消息耦合:
3.3.2、 业务与消息的分离
刚才的实现中,业务方在进行业务处理的同时,还需要对消息数据进行持久化,代码耦合,不方便以后的维护。
我们可以采用下面的模式,将消息与业务解耦:
优点:
l 消息系统与业务系统解耦
l 消息系统可以独立存储、独立伸缩
缺点:
l 每次消息发送,需要两次请求
l 业务处理系统,需要提供业务状态查询接口
3.3.3、 使用场景:
对事务一致性的时效要求不高的业务。例如跨行转账,支付宝余额宝转账。
3.4、 最大努力通知
3.4.1、 原理
最大努力通知主要用于对于事务的一致性不是特别敏感的事务,实现事务的弱一致性。可以通过消息中间件实现。与前面异步确保型操作不同的一点是, 在消息由MQ Server投递到消费者之后, 允许在达到最大重试次数之后正常结束事务.
l 主业务完成后,通过mq通知被动业务(允许消息丢失)
l 通知失败后主动方 按照一定的时间阶梯进行消息发送重试,直到超过最大重试次数为止
l 主动方提供业务查询接口,以便被动方后续进行通知失败后的补偿,恢复丢失消息。
3.4.2、 实现
实现:略。