作者:季敏,阿里云分布式事务产品负责人、Seata 开源项目创始人
01 微服务架构下数据一致性的挑战
微服务开发的痛点
在 2019 年,我们基于 Dubbo Ecosystem Meetup,收集了 2000 多份关于“在微服务架构,哪些核心问题是开发者最关注的痛点?”的调研问卷。最终分布式事务问题在调研中占比最大,约占 54%。
在 Seata 出现之前,大家谈到分布式事务的态度是能避则避,大部分是靠写一些复杂的业务逻辑加消息最终一致性去解决数据一致性问题。但 Seata 开源之后,这些问题都变得简单起来。比如这里提到的无损上下线,核心关注的是服务的可用性还是其他问题。从我的理解角度看,我觉得它最终关注的是数据问题。无论前端的业务怎么去做交互,最终都会沉淀到数据的变化。如果业务的数据不一致,前面无论是什么样的架构都变得意义不大,因为最终数据才是企业的核心资产。
那么到底哪些场景会遇到分布式事务的问题呢?
第一个场景,单体架构在拆分成微服务架构之后,不同的服务可能由不同的团队维护和负责,这里会涉及上下游服务的发布部署联动。比如 C 服务发布的时候,并不会通知 A 服务和 B 服务,这个时候就会遇到末端服务上下线带来的数据一致性问题。
第二个场景,不可靠不稳定的基础设施带来的系统稳定性问题进而引发数据一致性问题,比如基础设施层的存储、计算和网络不稳定导致的业务问题等。
第三个场景,timeout 是分布式架构中服务调用比较难解的状态,一旦出现服务调用超时,我们无法确定业务逻辑最终是执行了,还是未执行,这里最终会演化为数据的一致性的问题。从单体到分布式架构的演进,服务从进程内调用演变为跨网络的调用,在链路上糅合网络的因素,加剧了数据一致性问题的出现的概率。
第四个场景,业务链路里除了会涉及到数据库,还会涉及到其他第三方的数据组件。比如库存类服务一般会涉及到 Redis 组件,如何实现数据库和缓存的数据一致性也是事务链路需要考虑的业务场景。
第五个场景,上下游服务调用时,需要在调用链路中传递一些业务参数,上游服务需要对这些参数做逻辑校验。当参数逻辑校验不合法时,如何将上游服务的数据回滚到执行前的数据初态,保证服务调用链的数据一致性。
总结起来,分布式事务的业务场景主要包括跨库、跨服务和多样性资源的数据一致性。分布式事务会尤其关注业务异常导致的事务回滚场景,从异常分类上主要包括业务异常和系统异常。
那么分布式事务是不是微服务架构独有的问题呢?其实不是,它在单体应用里也有相同的问题,只不过在微服务架构里这个问题更加凸显。
在单体架构下存在哪些分布事务的场景呢?比如一个单体多模块应用要去操作多个数据库。本地事务是通过数据库连接上的一个 SQLSession 完成的,只要跨出了本地事务,那就会涉及到分布式事务问题,即使不同的微服务去修改同一个数据库,这也会涉及到分布式事务问题,因为 SQLSession 是基于 Socket 连接的,所以它本身是不能通过序列化和反序列化将对象传递到其他服务。
整体来看,分布式事务是各类应用架构的通用问题,应用场景非常广泛。
分布式事务的解决方案
市面上的分布式事务方案有以下几类:
- XA 模式,它的性能相比其他事务模式要差一点,但能保证最严格的数据一致性。XA 模式需要设置为串行化隔离级别,相当于对数据添加了读写锁。另外连接资源需要在整个事务期间保持导致资源锁定问题影响到并发事务的吞吐。
- TCC 模式和 SAGA 模式,可以归结为业务层面的分布式事务解决方案,因为它不会拦截数据。比如 TCC 模式有 Try,Confirm 和 Cancel 接口,至于这个接口内的回滚和提交逻辑是业务层面需要去实现的,在框架层面只负责分布式协调。这要求业务开发对业务逻辑有充分的理解和设计能力,否则可能出现无法提交/回滚、提交和回滚逻辑的不对等、接口的幂等和时序问题 。
- 消息最终一致性,它最大的优点是可以实现异步化的解耦,同时可以结合消息的削峰填谷的优点。但它本身存在着一些问题,消息更多的是做单向 one way 的通知场景。在一些业务场景中可能存在消息消费失败的场景,但它无法去回滚消息发送方的数据。比如现金红包的业务,第一步从我的账户扣减红包金额,第二步通过消息消费的方式提现到我的银行卡。消息消费的时候我这个账户已经被注销掉了,那么消息消费即使去重试也会一直处在消费失败的状态。对于这类问题,消息是无法再把消息发送方的数据回滚掉。所以,消息适用于一致性要求不强的异步单向通知场景。
- 定时任务补偿,它的学习成本低,但开发成本比较高。尤其是微服务存在多跳节点的链路,在任意中间节点出现问题都需要考虑如何去补偿和订正所有已经完成变更的数据,需要补偿逻辑编写的非常详尽和严谨,比较适应于数据一致性和实时性要求不高的简单业务场景。
- AT 模式,它对一致性和性能做了综合考量,主要的特点是简单无侵入,强一致,学习成本低。缺点是需要遵守一定的开发规约,它并不是对所有的 SQL 类型都支持,有一定的使用限制。适应于通用的业务场景,但它并不适应于热点数据的高并发场景像 sku 的库存的扣减。因为 AT 模式有应用层的全局锁,当需要对相同的数据修改操作时,需要使用全局锁控制事务的并发时序,实现上存在锁排队等待的机制。
02 分布式事务 Seata 的架构演进
Seata 的起源
Seata 在阿里内部的产品代号叫 TXC,起源于阿里集团的五彩石项目。五彩石是女娲补天用的石子,当时阿里集团内正在做去 IOE 的架构演进,从单体架构演进到分布式架构,在演进的过程中必然会涉及到很多的中间件去解决分布式的问题,TXC 承担的主要的角色是保证服务的数据一致性。
TXC 和集团内的三大件都做了深度的集成,包括:服务调用框架 HSF 和开源的 Apache Dubbo 相似的一个产品;数据库层有分库分表的 TDDL 组件;异步消息的 MetaQ 组件。这三大件基本满足业务开发的常规需求,TXC 通过深度集成使开发人员无需设计和实现一致性逻辑,框架层面能天然保证一致性对开发人员完全是透明无感知。TXC 在集团内也有广泛的使用,日均百亿级的事务调用,标准 3 节点集群吞吐量可以达到近 10w TPS。
TXC 产品的 SLA 包括可用性 SLA 和性能 SLA。因为对于分布式事务来说,除了要保证基本的数据一致性外,也要保证系统的性能和吞吐量,SLO 指标定义每一次分布式事务调用额外的 rt 开销不能超过 xx ms。目前分布式事务的调用处理已能达到毫秒级,基本接近我们推导的理论上限值,在稳定性上能保证全年无故障。
分布式事务模型定义
下面我们一起来看下分布式事务模型的定义。
最开始去定义分布式事务模型时,我们需要充分考虑分布式事务应该是在哪一层去实现?
从开发者的视角看应用架构主要包括:最上层是应用开发框架,每个公司都有一套开发框架,可能是自研框架或者 Spring Cloud 体系等。向下一层是服务调用框架由类似 Apache Dubbo 这类的框架承担,在国内 Apache Dubbo 有着非常广泛的使用。再向下一层是数据中间件,这层主要包括 ORM 框架、事务、同步、对账等类型的中间件。最下层是与数据库的连接层,像 JDBC Java 版本的实现 mysql-connector-java 就属于这一层。
我们去做了一个简单的对比,到底应该在哪层实现分布式事务更合适一些,是在数据库、数据中间件层还是应用开发框架层?
在应用框架层实现,一致性相对比较弱。因为在开发框架层糅合了比较多的复杂的因素,比如会把服务调用的异常给糅合进去。比如服务调用的超时和重试机制,这就是为什么现在 TCC 模式它会有一些幂等、防悬挂和空回滚问题,因为融入了 RPC 的因素,所以会带来一些不确性问题。实际上,Seata 已经在框架层面解决了这类的问题。
在数据中间件层,它的一致性比应用框架要好,它存在主要的问题是因为不在数据库层实现,所以是有办法绕过中间件直接去修改数据库的,这时候就会存在事务并发脏写问题,归根到底是因为数据中间件不是修改数据的唯一界面,但可以通过一定的开发规约来规范使用避免问题的产生。
最好的数据一致性是在数据库层去实现,但在数据库层实现面临的主要问题是:
1. 主要依赖数据库厂商的实现,能力上参差不齐。比如 MySQL 在 5.7.7 版本才把XA做到真正可用,在之前的版本它对 XA prepare 持久化一直存在问题。
2. 无法约束跨服务的分布式事务。数据库层实现的分布式事务以数据库为核心资源,作用范围只能局限在数据库本身。如果要在更大的 scope 下做分布式事务,比如应用要跨服务,对于跨服务的分布式事务它就无法约束,所以还是需要有一个第三方的协调者从全局去协调跨服务的数据一致性。
最终,我们把差异化的 AT 事务模式做到了数据库中间件层,TCC 和 Saga 事务模式做到了应用开发框架层。
分布式事务模型定义不仅仅只是定义一个开发组件,它除了要实现了一整套的开发流程体系,还要包括了对编程模型的定义,性能,运维,安全,数据审计,可观测和高可用体系等。在理论模型上,最初我们的理论是比较匮乏的,后续逐步完善了角色模型的定义和 Spring 事务模型的延展。之所以是去延展了已有的模型,而不是完全新创造一套理论体系是因为这样做对开发者能大幅度降低学习的成本。此外,我们还做了一些基础理论的定义,包括一致性的定义,是解决多节点的一致性,还是业务应用架构数据的一致性,事务的模式定义,事务交互模型和事务的隔离性等。
什么是分布式事务,它在日常中解决了什么问题?
举个例子,我去做银行转账的时候,我给你转 100 元,恰好这个时候出现了网络超时,那么这 100 元到底有没有扣减。如果不确定就可能出现资损的问题,甚至可能影响企业的商誉。
我们都在谈分布式架构,但从整个应用的宏观视角,实际上并不是所有组件都是分布式的。比如从一个应用的架构的层面去看数据库,当然也包括今天流行的分布数据库,从整个应用层面来看它还是一个集中式的数据的存储。在分布式的应用架构里,每个节点都只掌握了部分的信息,如果要做一些问题的排查,必然需要一个集中式的组件掌握全链路的信息。对于分布式事务它的核心工作是做分布式协调,所以它需要掌握全局的事务和数据信息。
这就是为什么 Seata 有了 Transaction Coordinator 角色,对分布式事务,它要有一个上帝视角,充当第三方的协调器,真正去执行数据库操作的是 Resource Manager,你可以认为它是数据库的灵魂,充当了数据库的 Proxy。真正随着业务应用去做事务控制的是 Transaction Manager,它伴随着业务的执行链路去控制事务的边界和决议。Transaction Coordinator,Resource Manager 和 Transaction Manager 共同构成了分布式事务的角色模型。
分布式事务的架构演进
2019 年 1 月 Seata 正式开源,从 0.1 版本我们将主打的 AT 事务模式开源,0.4 版本正式纳入了 TCC 事务模式。AT 模式需要适配不同的数据库进行开发,在现有阶段很难满足对所有关系数据库的支持,以及业务链路的缓存资源支持,这就需要 TCC 事务模式去做 AT 事务模式的补充。
随着 Seata 的不断迭代和发布,对长事务解决方案的呼声越来越强。在 0.9 版本正式纳入 Saga 事务模式,它主要解决链路较长的微服务数据一致性问题,同时兼顾了微服务的可视化编排。在 1.1 版本,Seata 纳入了 XA 的事务模式。为什么纳入 XA 模式?Seata 支持了 AT、TCC 和 Saga 事务模式之后,市面上其他的事务模式主要包括消息最终一致性和 XA 模式。社区也陆续收到了用户的一些声音的反馈,用户在较新的业务中使用了 AT 事务模式,用户期望可以和原有的 XA 模式可以实现互联互通,形成统一的分布式事务选型。
Seata 通过社区驱动的模式推动技术的演进,打造了一站式的分布式事务的解决方案。针对不同的业务场景,Seata 都能使用不同事务模式满足业务的需求。如上图所示,为什么 Seata 要有四种事务模式?目前市面上没有一种分布事务的模式,能解决不同业务场景的数据一致性问题。
分布式事务是一款和业务耦合比较深的组件,从数据交互链路上看包括同步和异步,长事务和短事务,强一致和弱一致以及与性能的权衡考量。所以社区纳入了现在的四种事务模式,它们从改造成本、性能和隔离性上各有所长,这里就不展开介绍了。
03 如何基于 Seata 扩展 RPC 和数据库
Seata 开源社区现状
首先看一下 Seata 开源社区发展的现状。Seata 已经纳入了 AT、TCC、Saga、XA 四种事务模式,对于市面上主流的关系数据库和 RPC 框架做了广泛的支持,同时也被许多第三方社区做了主动和被动集成,目前已经和三十多个社区做了开源生态上的集成,这些集成依赖于 Seata 现有的可插拔扩展机制设计。
Seata 有着丰富的多语言生态体系,除了最开始的 Java 版本,对 Golang 的支持也变得越来越成熟,欢迎大家去试用 Golang 版本,给社区提出更多宝贵的意见和建议。另外,社区还在建设多语言版本包括 PHP、Python 等。
目前 Seata 开源产品已被上千家企业在业务系统中应用,金融企业也纷纷试点。金融类的业务对分布式事务的需求是强需求,而且对产品的各项能力要求非常严苛。像中信银行、光大银行、农行与社区也建立了合作,使用 Seata 陆续改造它们的一些核心账务系统,保证账务体系的数据一致性。这也一定程度上反映了 Seata 开源产品的成熟度。
目前 Seata 社区的 Star 数已经到达了 24k,contributor 超 300 多人。Seata 是非常开放的一个社区,非常欢迎大家能够参与到 Seata 社区的建设中。
Seata 企业实践案例
接下来介绍一些 Seata 比较典型的企业案例。
第一个案例:中航信航旅纵横项目。中航信是 Seata 最早的天使用户,接入的是 Seata 0.2 版本。如果大家出差比较频繁,应该会用到它们航旅纵横的 APP。中航信使用 Seata 解决机票和优惠券业务的数据一致性问题,在最早期的版本中,用户陪社区踩了不少坑。
第二个案例:滴滴出行二轮车事业部。它在 Seata 0.6.1 版本引入到了二轮车事业部的各个业务中,包括市面上大家看到青桔单车和内部资产的管理。
第三个案例:美团基础架构。美团基础架构团队基于开源 Seata 封装了内部分布式事务 Swan 项目,作为美团内部各业务解决分布式事务问题的基础组件。
第四个案例:盒马小镇。盒马小镇游戏互动中通过 Seata 控制采花偷花的流程,开发周期从原有的 20 天下降至 5 天,大幅度减少了开发的成本。综上可以发现分布式事务的两大价值。
- 业务数据的正确性,因为只有数据一致再谈架构才有意义。
- 提升开发效率,架构师和开发者可以专注于业务的设计和开发而无需关注数据一致性的问题。
Seata 生态扩展
上图是 Seata 开放生态扩展点的分层结构。最上层定义了 API 这一层,中间这一层包括注册配置中心、AT 数据库资源、分布式锁和负载均衡策略等,下层包括集群的存储模式,SQLParser,协议层和传输控制。在集群模式中支持无状态的基于 DB 或 Redis 的存储模式,此外社区正在做基于 Raft 的存算不分离的集群模式。
Seata 可插拔扩展点
Seata 在定义可插拔的扩展机制参考了 Dubbo SPI 的设计。Seata 的扩展点分为 Server 扩展点和 Client 扩展点。Client 扩展点大概有 30+ 个主要面向配置,服务注册发现,鉴权,SQL 解析和执行器等。Server 侧的扩展点包括锁、存储、事务模式的处理。
从当前 Seata 的扩展机制来看,比如你要去支持一个国产信创数据库如达梦、人大金仓,二次开发的成本是比较低的。只需要按照社区提供的文档,对现有的 SQL 解析和执行器 SPI 进行对应数据库的实现和配置加载,就能把整个流程跑起来。Seata 对市面上常见的 RPC 框架和关系数据库做了扩展实现。
Seata & RPC 集成扩展
目前 Seata 已经支持了 11 种常见的 RPC 框架,对 Dubbo 的适配社区支持了早期的 Alibaba Dubbo 版本和后续的 Apache Dubbo 版本。Seata 和 RPC 框架的集成比较轻量,核心是要把 Seata 的事务上下文通过服务调用链路传递到服务的上游,并对事务上下文的绑定和清除等操作。Seata 可以在 RPC 框架提供的 request/response 扩展点实现上述 Seata 的扩展逻辑,常见的 RPC 框架基本支持自定义 filter 或 interceptor 的加载。
右边是当前实现 RPC 接口扩展的样例,欢迎大家在 Dubbo 的生态去使用 Seata 去解决跨服务的数据一致性问题。
Seata & 数据库集成扩展
目前 Seata AT 模式数据库支持 MySQL、Oracle、PostgreSQL、TiDB、OceanBase、SQLServer 等数据库。社区仍有一些数据库适配相关的 PR 当前还在 review 状态中,未合并到主干中像达梦和 IBM DB2 等。就像刚才提到的,只要基于当前的数据库的扩展点去做对应的实现,就能实现尚未在社区支持的关系数据库适配。在近期的编程之夏活动中,社区提了 PolarDB 课题的支持,目前也已进入到测试状态,后续社区将完整的支持 top20 的关系数据库,同时也将增加对信创数据库的支持。
总结
Seata 起源于阿里内部电商业务体系,解决服务化过程中的服务一致性问题,经过了多年标准化建设和大促流量的洗礼,Seata 已成为交易、支付和物流场景的标准化组件。Seata 开源后,通过社区驱动的方式加速技术的探索和演进,致力于打造一站式的分布式事务解决方案,囊括了 AT、TCC、Saga 和 XA 多种事务模式,满足不同场景的用户需求。Seata 在社区生态上做了大量的集成与被集成,通过插件化的方案预留了丰富的扩展点,满足不同场景的用户对服务调用框架、数据库和注册配置中心的扩展需求。展望未来,Seata 社区立足于分布式事务解决方案来继续完善其生态,同时将探索和延展更广阔的 DataOPS 生态。