一、微服务架构的数据一致性问题数据库
以电商平台为例,当用户下单并支付后,系统须要修改订单的状态而且增长用户积分。因为系统采用的是微服务架构,分离出了支付服务、订单服务和积分服务,每一个服务都有独立数据库作数据存储。当用户支付成功后,不管是修改订单状态失败仍是增长积分失败,都会形成数据的不一致。编程
为了解决例子中的数据一致性问题,一个最直接的办法就是考虑数据的强一致性。那么如何保证数据的强一致性呢?咱们从关系型数据库的 ACID 理论提及。网络
关系型数据库具备解决复琐事务场景的能力,关系型数据库的事务知足 ACID 的特性。架构
Atomicity:原子性(要么都作,要么都不作)并发
Consistency:一致性(数据库只有一个状态,不存在未肯定状态)分布式
Isolation:隔离性(事务之间互不干扰)函数
Durability: 永久性(事务一旦提交,数据库记录永久不变)微服务
具备 ACID 特性的数据库支持数据的强一致性,保证了数据自己不会出现不一致。高并发
然而微服务架构下,每一个微服务都有本身的数据库,致使微服务架构的系统不能简单地知足 ACID,咱们就须要寻找微服务架构下的数据一致性解决方案。性能
微服务架构的系统自己是一种分布式系统,而本文讨论的问题其实也就是分布式事务之数据一致性的问题,咱们来聊聊分布式系统的 CAP 理论和 BASE 理论。
CAP 是指在一个分布式系统下, 包含三个要素:Consistency(一致性)、Availability(可用性)、Partition tolerance(分区容错性),而且三者不可得兼。
C:Consistency,一致性,全部数据变更都是同步的。
A:Availability,可用性,即在能够接受的时间范围内正确地响应用户请求。
P:Partition tolerance,分区容错性,即某节点或网络分区故障时,系统仍可以提供知足一致性和可用性的服务
关系型数据库 单节点 保证了数据强一致性(C)和可用性(A),可是却没法保证分区容错性(P)。
然而在分布式系统下,为了保证模块的分区容错性(P),只能在数据强一致性(C)和可用性(A)之间作平衡。具体表现为在必定时间内,可能模块之间数据是不一致的,可是经过自动或手动补偿后可以达到最终的一致。
BASE 理论主要是解决 CAP 理论中分布式系统的可用性和一致性不可兼得的问题。BASE 理论包含如下三个要素:
BA:Basically Available,基本可用。
S:Soft State,软状态,状态能够有一段时间不一样步。
E:Eventually Consistent,最终一致,最终数据是一致的就能够了,而不是时时保持强一致。
BASE 模型与 ACID 不一样,知足 CAP 理论,经过 牺牲强一致性来保证系统可用性。因为牺牲了强一致性,系统在处理请求的过程当中,数据能够存在短时的不一致。
系统在处理业务时,记录每一步的临时状态。当出现异常时,根据状态判断是否继续处理请求或者退回原始状态,从而达到数据的最终一致。
例如,在上面的案例中,支付成功,订单也成功,但增长积分失败,此时,不该回滚支付和订单,而应经过一些 补偿方法 来让积分得以正确地增长。后面会讲到具体的实现方法。
在分享咱们的分布式事务实践方案以前,先看看早期解决分布式事务问题的二阶段提交协议。
二、二阶段提交协议
X/Open DTP(Distributed Transaction Process)是一个分布式事务模型,此模型主要使用二阶段提交(2PC,Two-Phase-Commit)来保证分布式事务的完整性。在这个模型里面,有三个角色:
AP:Application,应用程序,业务层。
RM:Resource Manager,资源管理器,关系型数据库或支持 XA 接口(XA 规范是 X/Open 组织定义的分布式事务规范)的组件。
TM: Transaction Manager ,事务管理器,负责各个 RM 的提交和回滚。
当应用程序(AP)调用了事务管理器(TM)的提交方法时,事务的提交分为两个阶段实行。
2.一、第一阶段(准备阶段)
TM 通知全部参与事务的各个 RM,给每一个 RM 发送 prepare 消息。
RM 接收到消息后进入准备阶段后,要么直接返回失败,要么建立并执行本地事务,写本地事务日志(redo 和 undo 日志),可是 不提交(此处只保留最后一步耗时最少的提交操做给第二阶段执行)。
2.二、第二阶段(提交 / 回滚阶段)
TM 收到 RM 准备阶段的失败消息或者获取 RM 返回消息超时,则直接给 RM 发送回滚(rollback)消息,不然发送提交(commit)消息。
RM 根据 TM 的指令执行提交或者回滚,执行完成后释放全部事务处理过程当中使用的锁(最后阶段释放锁)。
2.三、二阶段提交的利弊
优势
2PC 提供了一套完整的分布式事务的解决方案,遵循事务严格的 ACID 特性。
缺点
TM 经过 XA 接口与各个 RM 之间进行数据交互,从第一阶段的准备阶段,业务所涉及的数据就被锁定,而且锁定跨越整个提交流程。在高并发和涉及业务模块较多的状况下对数据库的性能影响较大。
二阶段是 反可伸缩模式 的,业务规模越大,涉及模块越多,局限性越大,系统可伸缩性越差。
在技术栈比较杂的分布式应用中,存储组件有不少 不支持 XA 协议。
二阶段的诸多弊端,致使分布式系统下没法直接使用此方案来解决数据一致性问题,但它提供了解决分布式系统下数据一致性问题的思路。。
下面就经过案例来分享咱们是如何保证微服务架构的数据一致性的。
可靠事件模式属于事件驱动架构,当某件重要事情发生时,例如更新一个业务实体,微服务会向消息代理发布一个事件。消息代理会向订阅事件的微服务推送事件,当订阅这些事件的微服务接收此事件时,就能够完成本身的业务,也可能会引起更多的事件发布。
这个过程可能致使出现不一致的地方在于:某个微服务在更新了业务实体后发布事件却失败;虽然微服务发布事件成功,可是消息代理未能正确推送事件到订阅的微服务;接受事件的微服务重复消费了事件。
可靠事件模式在于保证可靠事件投递和避免重复消费,可靠事件投递定义为
(a)每一个服务原子性的业务操做和发布事件
(b)消息代理确保事件传递至少一次。
避免重复消费要求服务实现幂等性,如支付服务不能由于重复收到事件而屡次支付。
为了描述方便,这里先定义两个概念:
业务异常:业务逻辑产生错误的状况,好比帐户余额不足、商品库存不足等。
技术异常:非业务逻辑产生的异常,如网络链接异常、网络超时等。
补偿模式使用一个额外的协调服务来协调各个须要保证一致性的微服务,协调服务按顺序调用各个微服务,若是某个微服务调用异常(包括业务异常和技术异常)就取消以前全部已经调用成功的微服务。
补偿模式建议仅用于不能避免出现业务异常的状况,若是有可能应该优化业务模式,以免要求补偿事务。如帐户余额不足的业务异常可经过预先冻结金额的方式避免,商品库存不足可要求商家准备额外的库存等。
咱们经过一个实例来讲明补偿模式,一家旅行公司提供预订行程的业务,能够经过公司的网站提早预订飞机票、火车票、酒店等。
假设一位客户规划的行程是,(1)上海-北京6月19日9点的某某航班,(2)某某酒店住宿3晚,(3)北京-上海6月22日17点火车。在客户提交行程后,旅行公司的预订行程业务按顺序串行的调用航班预订服务、酒店预订服务、火车预订服务。最后的火车预订服务成功后整个预订业务才算完成。
若是火车票预订服务没有调用成功,那么以前预订的航班、酒店都得取消。取消以前预订的酒店、航班即为补偿过程。
须要注意的是酒店的取消预订、航班的取消预订一样不能保证必定成功,因此补偿过程每每也一样须要实现最终一致性,须要保证取消服务至少被调用一次和取消服务必须实现幂等性。
咱们应该尽量经过设计避免采用补偿方式,好比上面的例子中,在预订火车票失败的时候能够提示客户更改其余的时间。
一个完整的TCC业务由一个主业务服务和若干个从业务服务组成,主业务服务发起并完成整个业务活动,TCC模式要求从服务提供三个接口:Try、Confirm、Cancel。
第一阶段:主业务服务分别调用全部从业务的try操做,并在活动管理器中登记全部从业务服务。当全部从业务服务的try操做都调用成功或者某个从业务服务的try操做失败,进入第二阶段。
第二阶段:活动管理器根据第一阶段的执行结果来执行confirm或cancel操做。若是第一阶段全部try操做都成功,则活动管理器调用全部从业务活动的confirm操做。不然调用全部从业务服务的cancel操做。
须要注意的是第二阶段confirm或cancel操做自己也是知足最终一致性的过程,在调用confirm或cancel的时候也可能由于某种缘由(好比网络)致使调用失败,因此须要活动管理支持重试的能力,同时这也就要求confirm和cancel操做具备幂等性。
在编程中一个幂等操做的特色是其任意屡次执行所产生的影响均与一次执行的影响相同。
幂等函数,或幂等方法,是指可使用相同参数重复执行,并能得到相同结果的函数。这些函数不会影响系统状态,也不用担忧重复执行会对系统形成改变。
在事务提交失败会重复提交达到最大重复次数时返回失败的 设计中 保持幂等性是尤其重要的