2017 年 10 月 29 日,来自华为公司中央软件院开源能力中心微服务架构师殷湘在又拍云 Open Talk 第 38 期活动上作了题为《微服务场景下的数据一致性解决方案》,以下是演讲是实录:

数据一致性是构建业务系统需要考虑的重要问题 , 以往我们是依靠数据库来保证数据的一致性。但是在微服务架构以及分布式环境下实现数据一致性是一个很有挑战的的问题。ServiceComb作为开源的微服务框架致力解决微服务开发过程中的问题。我们最近发起的ServiceComb-Saga项目来解决分布式环境下的数据最终一致性问题。本文将向大家介绍为什么数据一致性如此重要?Saga又是什么?

数据一致性的起因

在单体应用当中,单体应用的所以模块使用一个数据库,所以模块有数据关联的时候,或者需要同时写入相关联的数据的时候,数据一致性是通过数据库的事务来保障的,如果这个事物成功的话,所有的数据都会落库,如果失败,所有的数据都会落败。

在微服务场景下,数据一致性无法完全通过数据库保证。所以就衍生出了微服务场景下分布式事务的概念,微服务要做到做到四个独立:

  • 独立进程:每个微服务都在自己的进程中,采用轻量级通信协议
  • 独立部署:服务通过全自动的部署机制来进行独立部署
  • 独立技术:可用不同开发语言、数据存储技术
  • 独立团队:配备开发、测试、DBA、运维等对结果负责的全功能团队

解决方案对比

目前业界比较流行的解决方案有三种模式:事务模式、预约模式、补偿模式。

事务模式 -两阶段提交(2PC)

事物模式的代表就是两阶段提交,它的含义就是每个服务有自己的子事务,服务之间其实也是通过分布式事务来保障事务的一致性。

所以它在第一阶段的时候,这个协调器会发一个propose的请求给三个服务,如果每个服务都可以接受接下来的事务请求,它就会回复yes或者是no。回复之后就会到第二个阶段,如果前面三个服务回复的都是一个yes,那协调器就会发送commit这个请求,去执行这三个服务的事务操作,否则就会发送一个abort请求。执行完成之后,三个服务会回来一个ack,表示这个事务已经结束,或者是已经取消。

优点:有事务的原子性,因为在每次做事务操作的时候,都会锁定资源,所以所有进入协调器的请求,其实都是一个线性的请求,它不能去同步所有的事务。

缺点:它是一个阻塞式的模式,它需要锁定资源,它的复杂度也会相对比较高,比较难扩展。

预约模式 -TCC(Try-Confirm/Cancel)

预约模式比较典型的叫Try-Confirm/Cancel,缩写就是TCC。它也是一个两阶段的模式。第一个阶段它会发一个try的请求给三个服务,如果三个服务可以执行,它们就会回复yes,不能回复no,这和两阶段提交是一致的。不一样的地方是它不会锁定资源。所以在A处理完它的子事务,B可能还没有处理完它的子事务的时候,A可以接受其他的请求。在第二阶段,像两阶段提交一样,协调器会根据第一阶段ABC的返回结果去发送Confirm或者Cancel,这样的请求,最后ABC会返回ack。

优点:在try阶段如果失败,服务会回复至原状,什么意思呢?比如说要发一个会议邀请,如果用TCC的模式,第一阶段会先发一个询问的请求给所有的与会人员,询问是否可以在这一个时间点来参会,如果所有的参会人员都回答yes,第二步就会发送一个确认请求,实际的一个会议邀请,如果有任何一个人员不能参加,就会发送一个Cancel,取消这个会议。

缺点:

  1. 它不是原子操作,它可以并行处理好多的分布式的事务,然后它在Confirm这个阶段,只能重试,如果有一个服务失败的话,只能重试,直到成功,或者是采取回退措施,需要人工去做干预。

  2. 它需要一个额外的try流程,服务需要提供额外的try的结构,也就需要提供额外的reserved的状态。

补偿模式 -Saga

补偿模式典型的是Saga,Saga是一阶段的模式,它会直接发送事务的操作给ABC,ABC在收到之后,直接就会对这个事务进行相对应的处理。

Saga有两种恢复的策略,在没有操作的场景下,如果A成功、C成功,B失败了,怎么办?有两种方案:

向前恢复,像TCC一样,他就会采取重试的方式,再发送请求给B,直到B成功或者采取一个回退的措施,也就是人工干预。

向后恢复—补偿,如果B失败了,那么Saga不会继续执行后面的事务操作,而是会向之前已经事务操作成功完成的服务发送补偿的请求。这样保证数据达到一致的效果。

优点:不需要服务提供额外的接口,一般的服务会有commit和abort,比如说我有一个事务的操作的接口和取消的接口,所以它的侵入性比较低。

缺点:像TCC一样,因为它是比较容易执行的一个解决方案,所以它也是一个非原子的操作,因为它对事务的补偿不一定能恢复服务至原状态,比如说我发送会议请求,会议邀请,TCC会先发一个询问的消息,给所有参会人。但是在Saga的场景下,直接就会发送一个会议邀请给所有参与人。可能有些参会人就已经确认了不会参加,但有些人是可能不参加。那如果有一个不参加,Saga只能另一个会议的消息,告诉其他已经同意的参会人员,会议是需要取消,因为有某人不能参加。

Saga—华为的解决方案

Saga的定义就是一个长活的事务,它的长活事务有很多微服务的子事务而构成,每个子事务都需要定义一个对应的补偿操作Cx,现在业界已知的使用Saga的厂商,像微软,在他的游戏里面有使用Saga,Twitter和Uber也都在使用Saga作事务的一致性处理。

对于Saga,我们会对一个常数,把它分成N个子事务,每个子事务对应有一个补偿的操作,在正常情况下,T1T2T3Tn,会正常执行,没有任何一个服务失败,如果有一个服务失败,比如在执行到T3的时候失败了,因为T3没有成功执行,所以T3不需要补偿,但前面的T1T2已经成功执行,就需要补偿T1和T2。

正常情况下的案例

比如我们是一个做旅游规划的服务提供商,我们可能下面有机票预定服务,酒店预定服务,以及租车服务。在这三个服务约定成功之后,会要求用户做一个支付,所以这三个服务之间是没有先后的依赖关系可以并行操作,但支付一般期望是在所有预定成功之后才进行。所以可以根据这个请求构建一个这样的架构,在Saga收到用户请求的时候,首先会记录一个Saga Started事件,接下来向机票预定服务,发起事务的操作,同时记录Flight  Started事件,如果这个服务操作成功,它会返回,返回之后Saga会记录Flight Ended。同样的,对于酒店预定,也会记录相应的事件,租车也是一样,直到最后的支付完成之后,记录一个Saga Ended,表明整个长事务已经完成。

image.png

异常情况下的案例

收到请求,机票预定成功,如果在这个时候,酒店预定失败,Saga收到失败请求之后,就会记录Hotel Aborted这个事件,在持久化存储里面。现在只有机票预定服务的事务已经完成,租车和酒店预定以及支付都没有成功执行,所以需要补偿的,其实只有机票预定服务,所以Saga就会转换状态到发送补偿的请求给机票预定服务。然后在收到这个服务的返回结果之后就会记录Flight Compensated,表明补偿已经完成,最后是表明这个常务事务结束。

image.png

复杂业务场景案例

比如在网上购买东西,做了支付,这时候需要从仓库发货,如果还有另外一个服务,叫会员制服务,如果这个会员他购买的总量已经超过了一千,会升级他的会员资格为银卡会员,所以这里有一个条件的关系,另外如果在发货之后发现仓储已经小于10了,那么则需要去获取更多的货物补充。华为的方案是Saga在处理支付流程的时候请求里会带上SagaChildren,请求已知整个事物的流程图结构,支付服务操作之后即可知道需不需要升级会员资格,如需要会把会员服务放到SagaChildren,如不需要就只带仓储标签。同样在做仓储的时候,库存大于等于10,不需要做货物补充提醒时,需要在SagaChildren里面返回,就表明这个事务已经完成。


image.png

使用Saga的两个要求:

1、幂等

幂等是其任意多次执行所产生的影响均与一次执行的影响相同。

比如Saga首先发送了一个请求给car rental,但是car rental因为网络问题,很长时间没有回复导致了超时,这时使用向前恢复的场景,Saga就会再发送同样的请求给car rental,请求就回复了。如果没有幂等功能,就有可能租了两辆相同的车。

2、可交换

可交换是无论先执行哪个请求最终的的结果是一致的。

比如发一个交易请求超时了,再发请求时另外一个请求失败了,需要car rental进行补偿,在这种情况下,Saga就会发送一个补偿请求给car rental。但前面发送的T的请求,可能最终还是被car rental收到了,在这种情况下,如果不能满足可交换功能,就会导致数据不一致,也就是以为已经取消了租车预约,其实已经预约了租车服务。

image.png

华为Saga的系统架构

下图是华为的系统架构图,Saga Execution Component现在实现的是动态配置,也就是事务内容是通过JSON请求发过来,后续可能会做静态配置,也就是Saga的请求可能会从文件或者是动态配管中获取。

image.png

综合比较

下图是综合比较两阶段提交、TCC、Saga三种模式

image.png

事件驱动

事件驱动是建立在消息队列基础上,2PC/TCC/Saga的非集中式实现。事件驱动的方式里,没有集中式的协调性,每一个服务,除了自己的业务逻辑之外还需要额外的事件表来保存当前的事件的状态,所以相当于是把集中式的事务状态分布到了每一个服务当中。如果服务对事务的处理需要锁定资源,其实就是两阶段提交,如果有额外的中间状态,不锁定资源,其实和TCC也没有什么区别。如果没有中间状态,其实就是非集中式的Saga。

优点:服务自治,去中心化

缺点:服务互相耦合,新增服务导致服务间事件流转变化;服务与分布式事务带来的应用复杂性耦合;需要额外事件表;多语言支持需要多种实现;服务越多,服务间事件流转越 难可视化,难以定位问题。

image.png

一致性方案的选择 

领域驱动设计是微服务系统架构的最佳指南,微服务其实和领域驱动设计里面限界上下文概念是一一对应的,也就是说一个限界上下文,就等于一个微服务。

聚合与数据一致性

1、聚合内需要强一致

2、聚合之间,跨聚合是最终一致性

3、聚合的边界和强一致的边界是对等的。

总结

  • 微服务跟限界上下是一一对应的关系
  • 聚合的边界和强一致的边界是对等关系
  • 限界上下文里面可以包含一个或多个聚合
  • 微服务内,聚合通过数据库事务保证强一致
  • 微服务之间,应使用最终一致性

如果需要分布式强一致,先考虑设计是否合理而非追求最新技术,合理的设计能大大减少技术复杂度和商业成本。

为什么使用ServiceComb-Saga?

和平:低侵入

  • 减少业务代码集成/运维难度
  • 剥离业务与数据一致性复杂度

统一:集中式

  • 让运维监控更加简单
  • 可视化事务、调用链

高可用

  • 无状态、可集群、可分片
  • EventSourcing架构,对持久化存储无要求,可扩展以重现历史场