1. 事务概述
1.1 事务的定义
将一个活动涉及到的所有操作纳入到一个不可分割的执行单元,只要其中任一操作执行失败,都将导致整个执行单元回滚。
简单地说,事务提供一种“要么什么都不做,要么做全套(All or Nothing)”机制。
事务具备ACID的特性,即原子性、一致性、隔离性和持久性。
1.2 事务分类
一般而言,业界常将事务分为如下几种类型的事务:
- 本地事务
- 垮库事务
- 分布式事务
1.2.1 本地事务
即单一进程单一数据源事务。
就是在一个jvm程序中,只操作一个数据的事务,即我们常说的本地事务。当一个系统业务简单、并发量、数据量较小时,可采用这种方式解决数据一致性问题。
1.2.2 垮库事务
即单一进程多数据源事务。
当数据量和并发进一步增大时,虽然可以通过将应用程序集群化,但因为都是操作同一个数据库,而数据库的并发量和数据量达到一定程度后,数据库的性能会急速下降,从而成为整个系统的短板。这时可能就需要进行分库分表,如果进行分库分表,那么就相当于一个进程需要操作多个数据源,为了解决数据一致性问题,就需要垮数据源的事务。
1.2.3 分布式事务
即多进程多数据源事务。
当业务越来越复杂时,传统的单一服务架构模式将会变得越来越沉重,模块与模块间的耦合度越来越高,不利于系统的开发和维护,因此出现了当前比较流行的微服务架构模式。
当系统从单一模式重构成微服务模式时,将会出现一个业务单元需要多个微服务共同完成,也就会出现多个微服务操作各自数据源的情况,从而产生分布式事务问题。
2. 分布式事务
分布式事务指事务的参与者、支持事务的服务器、资源服务器以及事务管理器分别位于不同的分布式系统的不同节点之上。
简单的说,就是一次大的操作由不同的小操作组成,这些小的操作分布在不同的服务器上,且属于不同的应用,分布式事务需要保证这些小操作要么全部成功,要么全部失败。
本质上来说,分布式事务就是为了保证不同数据库的数据一致性。
假设一个电商下单操作,需要同时创建订单、扣减库存、增加积分这几个操作,只有当这几个操作都成功了,则算下单操作成功。具体如下图所示:
在本地事务中,主要为ACID四大特性(原子性、一致性、隔离性、持久性),而在分布式系统中,主要为CAP和BASE理论。
2.1 CAP理论
2.1.1 一致性(C)
在分布式系统完成某写操作后的任何读操作,都应该获取到该写操作写入的那个最新的值。
假设订单服务A在新增一个订单后,调用订单服务BCD时,都能查询到这个新增的订单。
2.1.2 可用性(A)
即在集群中一部分节点故障后,集群整体还能响应客户端的读写请求。
假设订单服务由ABCD4个节点组成的集群,只要集群中有一个节点存活,那么整个订单服务就能对外提供服务。
2.1.3 分区容忍性(P)
即分布式系统在遇到某节点或网络分区故障的时候,仍然能够对外提供满足一致性或可用性的服务。
分区容忍性不大好理解,感觉和可用性一样。我是这么理解的,如有说的不对的地方,希望大家指出来共同讨论。
假设上图下单服务,订单服务A、库存服务A、积分服务A部署在美国,其他部署在中国。则相当于整个系统部署在了不同的区域,即美洲区、亚洲区。分区容忍性是指美洲区的订单服务A、库存服务A、积分服务A都宕机了,那么剩下的存活节点是否能对外提供服务。和可用性的区别是跨服务,可用性是指一个服务,如库存服务是否高可用。而分区容忍性是跨服务的可用性,如某个地理区域的一个或多个服务不可用时,整个下单服务是否可用。
一般在架构设计分布式系统时,分区容忍性是必须要考虑的。而剩下的一致性和可用性因为本身的互斥关系(个人理解),只能选择一样,即要么是CP,要么是AP组合。
为什么一致性和可用性不能同时满足?
还是以上图为例,假设订单服务有订单服务ABCD几个节点组成。
如果需要满足一致性,那么需要ABCD几个节点都不能宕机,因为如果A宕机了,那么其他服务访问A时,系统就不可用了。
如果需要满足可用性,那么只需要ABCD有一个节点不宕机即可,但这时如果其他服务。
2.2 BASE理论
BASE理论是对CAP理论的延伸,思想是即使无法做到强一致性(CAP的一致性就是强一致性),但可以采用适当的采取弱一致性,即最终一致性。
BASE是指基本可用(Basically Available)、软状态( Soft State)、最终一致性( Eventual Consistency)。
2.2.1 基本可用
基本可用是指分布式系统在出现故障的时候,允许损失部分可用性(例如响应时间、功能上的可用性),允许损失部分可用性。需要注意的是,基本可用绝不等价于系统不可用。
响应时间上的损失:正常情况下搜索引擎需要在0.5秒之内返回给用户相应的查询结果,但由于出现故障(比如系统部分机房发生断电或断网故障),查询结果的响应时间增加到了1~2秒。
功能上的损失:购物网站在购物高峰(如双十一)时,为了保护系统的稳定性,部分消费者可能会被引导到一个降级页面。
2.2.2 软状态
软状态是指允许系统存在中间状态,而该中间状态不会影响系统整体可用性。分布式存储中一般一份数据会有多个副本,允许不同副本同步的延时就是软状态的体现。mysql replication的异步复制也是一种体现。
2.2.3 最终一致性
最终一致性是指系统中的所有数据副本经过一定时间后,最终能够达到一致的状态。弱一致性和强一致性相反,最终一致性是弱一致性的一种特殊情况。
3. 分布式事务解决方案
还是以上图为例,假设一个下单操作,需要新增订单成功、扣减库存成功、新增积分成功才算成功。对于两阶段提交和TCC而言,一般都有事务协调者、事务发起者(可以理解成下单服务)和事务参与者(可以理解成订单系统、库存系统、积分系统)3个角色。
3.1 两阶段提交
3.1.1 执行流程
执行一个下单操作时,告诉事务协调者,开启一个分布式事务。
第一阶段如下:
- 各个子系统执行相关业务逻辑代码后,将执行结果告诉协调者,数据未入库;
第二阶段如下:
- 当所有的子事务都执行完时,事务发起者告诉事务协调者,所有子事务都执行完成,事务协调者根据各个子事务执行的结果,广播给各个子事务,是该执行commit还是rollback操作。
3.1.2 优点
- 比较容易实现
- 对原来的业务代码入侵性较小
3.1.3 缺点
- 可能会破坏数据一致性。假设在第一阶段时,还有库存,但最终commit时,库存已经被其他线程更新成0了,这时库存就会被更新成负数。假设在更新库存数据时,数据库连接超时,从而导致扣减库存失败,但新增订单和新增积分却成功了。
3.1.4 开源框架
- LCN
3.2 TCC方案Try-Confirm-Cancel
3.2.1 执行流程
执行一个下单操作时,告诉事务协调者,开启一个分布式事务。
try阶段如下:
- 各个子系统,调用try接口,将数据库的数据锁定,并将执行结果告诉事务协调者,数据入库,但状态为预处理状态。
当所有的子事务都执行完时,事务发起者告诉事务协调者,所有子事务都执行完成,事务协调者根据各个子事务执行的结果,广播给各个子事务,是该执行commit还是rollback操作
confirm阶段如下:
- 各个子事务收到事务协调者的commit消息后,调用confirm接口,更新数据未最终状态。
cancel阶段:
- 各个子事务收到事务协调者的rollback消息后,调用cancel接口,执行回滚操作。
3.2.2 优点
- 在一定程度上保证了数据的一致性问题,至少不会出现库存为负的情况。
3.2.3 缺点
- 开发难道较大,以前只每个服务只需要一个接口(执行业务逻辑),现在需要三个接口,预处理接口、确认接口、回滚接口。
3.2.4 开源框架
- LCN
- seata:阿里巴巴的开源框架
3.3 基于消息中间件的最终一致性方案
3.3.1 执行流程
- 1. 订单服务将数据发送给MQ,此时MQ的数据状态为新增,如果失败,则异常结束。
- 2. 订单服务执行自身逻辑,并将数据持久化,如果失败,则异常结束。
- 3. 订单服务向MQ发送数据确认,此时MQ的数据状态为已确认。如果失败,可以异常结束回滚订单数据库,也可以不回滚订单数据。
下游服务从MQ中获取数据状态为已确认的数据,进行相应业务逻辑的处理。
如果订单服务向MQ发送确认数据失败,且不回滚订单数据,则下游服务还需要从MQ中获取状态为新增的数据,并向订单服务确认该数据是否有效,如果有效,则执行相关业务逻辑,如果无效,则什么都不做。
下游服务可以从MQ主动拉取数据,也可以让MQ主动推送数据到下游服务。
3.3.2 优点
- 系统解耦,各个子系统之间的耦合性降到最低。
3.3.3 缺点
- 数据一致性变弱,只能保证最终一致,对于一致性要求较高的系统不适应。