跳到主要内容

04、分布式事务 实战 - 分布式事务的基本概念和理论知识

一、分布式系统架构

1.简介

随着互联网的快速发展,传统的单体架构已不能满足海量用户的需求。于是,更多的互联网企业开始对原有系统进行改造和升级,将用户产生的大规模流量进行分解,分而治之,在不同的服务器上为用户提供服务,以满足用户的需求。慢慢地,由原来的单体系统架构演变为分布式系统架构

2.分布式事务

随着互联网的不断发展,企业积累的数据越来越多。当单台数据库难以存储海量数据时,人们便开始探索如何将这些数据分散地存储到多台服务器的多台数据库中,逐渐形成了分布式数据库。如果将数据分散存储,对于数据的增删改查操作就会变得更加复杂,尤其是难以保证数据的一致性问题,这就涉及了常说的分布式事务

3.分布式系统架构产生的背景

在互联网早期,互联网企业的业务并不是很复杂,用户量也不大,一般使用单体系统架构快速实现业务。此时,系统处理的流量入口更多来自 PC 端

随着用户量爆发式增长,此时的流量入口不再只有 PC 端,更多来自移动端 App、H5、微信小程序、自助终端机、各种物联网设备和网络爬虫等。用户和企业的需求也开始变得越来越复杂。在不断迭代升级的过程中,单体系统变得越来越臃肿,系统的业务也变得越来越复杂,甚至难以维护。修改一个很小的功能可能会导致整个系统的变动,并且系统需要经过严格测试才能上线,一个很小的功能就要发布整个系统,直接影响了系统中其他业务的稳定性与可用性

此时开发效率低下,升级和维护系统成本很高,测试周期越来越长,代码的冲突率也会变得越来越高。最让人头疼的是,一旦有开发人员离职,新入职的人需要很长的时间来熟悉整个系统。单体架构已经无法支撑大流量和高并发的场景

面对单体系统架构的种种问题,解决方案是对复杂、臃肿的系统进行水平拆分,把共用的业务封装成独立的服务,供其他业务调用,把各相关业务封装成子系统并提供接口,供其它系统或外界调用,以此达到降低代码耦合度,提高代码复用率的目的。此时,由于各个子系统之间进行了解耦,因此对每个子系统内部的修改不会影响其他子系统的稳定性。这样一来降低了系统的维护和发布成本,测试时也不需要把整个系统再重新测试一遍,提高了测试效率。在代码维护上,各个子系统的代码单独管理,降低了代码的冲突率,提高了系统的研发效率

4.架构目标和架构原则

好的分布式系统架构并不是一蹴而就的,而是随着企业和用户的需求不断迭代演进的,能够解决分布式系统当前最主要的矛盾,同时对未来做出基本的预测,使得系统架构具备高并发、高可用、高可扩展性、高可维护性等非功能性需求,能够快速迭代,以适应不断变化的需求

分布式系统架构的设计虽然比较复杂,但是也有一些业界遵循的原则。其中一些典型的架构原则来自《The Art of Scalability》,作者马丁 L. 阿伯特和迈克尔 T. 费舍尔分别是 eBay 和 PayPal 的 CTO。他们在书中总结了 15 项架构原则,分别如下所示:

  • N+1 设计
  • 回滚设计
  • 禁用设计
  • 监控设计
  • 设计多活数据中心
  • 使用成熟的技术
  • 异步设计
  • 无状态设计
  • 水平扩展而非垂直升级
  • 设计时至少要有两步前瞻性
  • 非核心则购买
  • 使用商品化硬件
  • 小构建、小发布和快试错
  • 隔离故障
  • 自动化

二、分布式系统架构演进

互联网企业的业务飞速发展,促使系统架构不断变化。总体来说,系统架构大致经历了单体应用架构 —— 垂直应用架构 —— 分布式架构 —— SOA 架构 —— 微服务架构的演变, 很多互联网企业的系统架构已经向服务化网格(Service Mesh)演变。接下来简单介绍一下系统架构的发展历程

1.单体应用架构

在企业发展的初期,一般公司的流量比较小,只需要一个应用将所有的功能代码打包成一个服务并部署到服务器上,就能支撑公司的业务需求。这种方式能够减少开发、部署和维护的成本。比如大家很熟悉的电商系统,里面涉及的业务主要有用户管理、商品管理、订单管理、支付管理、库存管理、物流管理等模块。企业发展初期,我们将所的模块写到一个 Web 项目中,再统一部署到一个 Web 服务器中,这就是单体应用架构,系统架构如下图所示:

 

这种架构的优点如下:

1、 架构简单,项目开发和维护成本低;
2、 所有项目模块部署在一起,对于小型项目来说,方便维护;

但是,其缺点也是比较明显的:

1、 所有模块耦合在一起,对于大型项目来说,不易开发和维护;
2、 项目各模块之间过于耦合,一旦有模块出现问题,整个项目将不可用;
3、 无法针对某个具体模块来提升性能;
4、 无法对项目进行水平扩展;

正是由于单体应用架构存在诸多缺点,才逐渐演变为垂直应用架构

2.垂直应用架构

随着企业业务的不断发展,单节点的单体应用无法满足业务需求。于是,企业将单体应用部署多份,分别放在不同的服务器上。然而,不是所有的模块都有比较大的访问量。如果想针对项目中的某些模块进行优化和性能提升,对于单体应用来说,是做不到的。于是,垂直应用架构诞生了

垂直应用架构就是将原来的项目应用拆分为互不相干的几个应用,以此提升系统的整体性能

同样以电商系统为例,在垂直应用架构下,我们可以将整个电商项目拆分为电商交易系统、后台管理系统、数据分析系统,系统架构如下图所示:

 

将单体应用架构拆分为垂直应用架构之后,一旦访问量变大,只需要针对访问量大的业务增加服务器节点,无须针对整个项目增加服务器节点

这种架构的优点如下:

1、 对系统进行拆分,可根据不同系统的访问情况,有针对性地进行优化;
2、 能够实现应用的水平扩展;
3、 各系统能够分担整体访问流量,解决了并发问题;
4、 子系统发生故障,不影响其他子系统的运行情况,提高了整体的容错率;

这种架构的缺点如下:

1、 拆分后的个系统之间相互独立,无法进行互相调用;
2、 各系统难免存在重叠地业务,会存在重复开发的业务,后期维护比较困难;

3.分布式架构

将系统演变为垂直应用架构之后,当垂直应用越来越多时,重复编写的业务代码就会越来越多。此时,我们需要将重复地代码抽象出来,形成统一的服务,供其它系统或者业务模块调用,这就是分布式架构

在分布式架构中,我们会将系统整体拆分为服务层和表现层。服务层封装了具体的业务逻辑供表现层调用,表现层则负责处理与页面的交互操作。分布式系统架构如下图所示:

 

这种架构的优点如下:

1、 将重复的业务代码抽象出来,形成公共的访问服务,提高了代码的复用性;
2、 可以有针对性地对系统和服务进行性能优化,以提升整体的访问性能;

这种架构的缺点如下:

1、 系统之间的调用关系变得复杂;
2、 系统之间的依赖关系变得复杂;
3、 系统维护成本高;

4.SOA 架构

在分布式架构下,当部署的服务越来越多时,重复的代码就会变得越来越多,不利于代码的复用和系统维护。为此,我们需要增加也给统一的调度中心对集群进行实时管理,这就是 SOA(面向服务)架构。SOA 系统架构如下图所示

 

这种架构的优点是通过注册中心解决了各个服务之间服务依赖和调用关系的自动注册与发现

这种架构的缺点如下:

1、 各服务之间存在依赖关系,如果某个服务出现故障,可能会造成服务器崩溃;
2、 服务之间的依赖与调用关系复杂,增加了测试和运维的成本;

5.微服务架构

微服务架构是在 SOA 架构的基础上进行进一步的扩展和拆分。在微服务架构下,一个大的项目拆分为一个个小的可独立部署的微服务,每个微服务都有自己的数据库。微服务系统架构如下图所示:

 

这种架构的优点如下:

1、 服务彻底拆分,各服务独立打包、独立部署和独立升级;
2、 每个微服务负责的业务比较清晰,利于后期扩展和维护;
3、 微服务之间可以采用REST和RPC协议进行通信;

这种架构的缺点如下:

1、 开发成本比较高;
2、 涉及各服务的容错性问题;
3、 涉及数据的一致性问题;
4、 设计分布式事务问题;

三、分布式事务场景

将一个大的应用系统拆分为多个可以独立部署的应用服务,需要各个服务远程协作才能完成某些事务操作,这就涉及分布式事务的问题。总的来讲,分布式事务会在 3 种场景下产生,分别是跨 JVM 进程、跨数据库实例和多服务访问单数据库

1.跨 JVM 进程

将单体项目拆分为分布式、微服务项目之后,各个服务之间通过远程 REST 或者 RPC 调用来协同完成业务操作。典型的场景是商城系统的订单微服务和库存微服务,用户在下单时会访问订单微服务。订单微服务在生成订单记录时,会调用库存微服务来扣减库存。各个微服务部署在不同的 JVM 进程中,此时会产生因跨 JVM 进程而导致的分布式事务问题。商城系统中跨 JVM 进程产生分布式事务的场景如下图所示:

 

2.跨数据库实例

单体系统访问多个数据库实例,也就是跨数据源访问时会产生分布式事务。例如,系统中的订单数据库和交易数据库放在不同的数据库实例中,当用户发起退款时,会同时操作用户的订单数据库和交易数据库(在交易数据库中执行退款操作,在订单数据库种将订单的状态变更为已退款)。由于数据分布在不同的数据库实例中,需要通过不同的数据库连接会话来操作数据库中的数据,因此产生了分布式事务。商城系统中跨数据库实例产生分布式事务场景如下图所示:

 

3.多服务访问单数据库

多个微服务访问同一个数据库,例如,订单微服务和交易微服务访问同一个数据库就会产生分布式事务,原因是多个微服务访问同一个数据库,本质上也是通过不同的数据库会话来操作数据库,此时就会产生分布式事务。商城系统中多服务访问但数据库产生分布式事务的场景如下图所示:

 

跨数据库实例场景和多服务访问单数据库场景,在本质上都会产生不同的数据库会话来操作数据库中的数据,进而产生分布式事务。这两种场景是比较容易被忽略的

四、数据一致性

在分布式场景下,当网络、服务器或者系统软件出现故障,就可能会导致数据一致性的问题

1.数据的一致性问题

数据多副本场景

如果数据的存储存在多副本的情况,当网络、服务器或者系统软件出现故障时,可能会导致一部分副本写入成功,一部分副本写入失败,造成各个副本之间数据的不一致

调用超时场景

调用超时场景包含同步调用超时和异步调用超时

同步调用超时往往是由于网络、服务器或者系统软件异常引起的,例如,服务 A 同步调用服务 B 时出现超时现象,导致服务 A 与服务 B 之间的数据不一致

异步调用超时是指服务 A 异步调用服务 B,同样是由于网络、服务器或者系统软件异常导致调用失败,出现服务 A 与服务 B 之间的数据不一致的情况。一个典型的场景就是支付成功的异步回调通知

缓存与数据库不一致场景

这种场景主要针对缓存与数据库。在高并发场景下,一些热数据会缓存到 Redis 或者其他缓存组件中。此时,如果对数据库中的数据进行新增、修改和删除操作,缓存中的数据如果得不到及时更新,就会导致缓存与数据库中数据不一致

多个缓存节点数据不一致场景

这种搽干净主要针对缓存内部各节点之间数据的不一致。例如在 Redis 集群中,由于网络异常等原因引起的脑裂问题,就会导致多个缓存节点数据不一致

2.数据一致性解决方案

业界对于数据一致性问题提出了相应的解决方案,目前比较成熟的方案有 ACID 特性、CAP 理论、Base 理论、DTP 模型、2PC(两阶段提交)模型、3PC(三阶段提交)模型、TCC 模型、可靠消息最终一致性模型、最大努力通知模型等

五、CAP 理论

CAP是一致性(Consistency)、可用性(Availability)和分区容忍性(Partition Tolerance)首字母的缩写。CAP 是分布式领域著名的理论

1.一致性

在互联网领域,企业往往会将一份数据复制多份进行存储。一致性是指用户对数据的更新操作(包括新增、修改和删除),要么在所有的数据副本都执行成功,要么在所有的数据副本都执行失败。也就是说,一致性要求对所有数据节点的数据副本的修改是原子操作。所有数据节点的数据副本的数据都是最新的,从任意数据节点读取的数据都是最新的状态

例如,在数据库主从集群模式种,应用程序向主数据库写数据,主数据库向应用程序返回写入结果并将数据同步到从数据库中。对于应用程序向从数据库读取数据的场景,结果要满足一致性,需要实现如下目标:

1、 应用程序向主数据库写数据失败,则向从数据库读取数据也失败;
2、 应用程序向主数据库写数据成功,则向从数据库读取数据也成功;

实现上述目标,需要在技术上满足如下条件:

1、 应用程序将数据写入主数据库后,将数据同步到从数据库中;
2、 数据写入主数据库后,主数据库将数据同步到从数据库存在一定的时间延迟,这个过程需要将从数据库锁定,避免应用程序向从数据库种读取出与主数据库不一致的数据,待数据同步完成后再释放从数据库的锁;

综上所述,一致性存在如下特点:

1、 存在数据同步的过程,应用程序的写操作存在一定的延迟;
2、 为了保证各节点数据的一致性,需要对相应的资源进行锁定,待数据同步完成后再释放锁定的资源;
3、 如果数据写入并同步成功,所有节点都会返回最新的数据相反地,如果数据写入或者同步失败,所有节点都不会存在最新写入的数据;

2.可用性

可用性指的是客户端访问数据的时候,额能够快速得到响应。需要注意的是,系统处于可用状态时,每个存储结点的数据可能会不一致,并不要求应用程序向数据库写入数据时能够立刻读取到最新的数据。也就是说,处于可用性状态的系统,任何事务的操作都可以得到响应的结果,不会存在超市或者响应错误的情况

例如,在数据库主从集群模式中,应用程序向主数据库写数据,主数据库向应用程序返回写入结果并将数据同步到从数据库中。u低于应用程序向从数据库读取数据的场景,如果要满足可用性,则需要实现如下目标:

1、 从数据库接收到应用程序读取数据的请求,能够快速响应结果数据;
2、 从数据库不能出现响应超时或者响应错误的情况;

实现上述目标,需要在技术上满足如下条件:

1、 应用程序将数据写入主数据库后,主数据库需要将数据同步到从数据库中;
2、 主数据库同步到从数据库的过程中,不能锁定从数据库的资源;
3、 应用程序向从数据库查询数据时,从数据库一定要返回数据此时如果主从数据同步还没有完成,从数据库也要返回数据,即使是旧数据也要返回,如果从数据库中连旧数据都没有,则返回一个默认数据,总智能感知从数据库不能出现响应超时或者响应错误的情况;

综上所述,可用性存在如下特点:

1、 所有请求都会被响应;
2、 不会存在响应超时或者响应错误的情况;
3、 如果对不同的应用程序设定了超时响应时间,一旦超过这个时间,系统将不可用;

3.分区容忍性

如果只是将存储系统部署并运行在一个节点上,当系统出现故障时,整个系统将不可用。如果将存储系统部署并运行在多个不同的节点上,并且这些节点处于不同的网络中,这就形成了网络分区。此时,不可避免地会出现网络问题,导致节点之间的通信出现失败的情况,但是,此时的系统仍能对外提供服务,这就是分区容忍性

例如,在数据库主从集群模式中,应用程序向主数据库写数据,主数据库向应用程序返回写入结果并将数据同步到从数据库中。对于应用程序向数据库读取数据的场景,如果要满足分区容忍性,则需要实现如下目标:

1、 主数据库向从数据库同步数据,无论同步结果是成功还是失败,都不会影响数据的写操作;
2、 不管是主数据库还是从数据库,其中一个节点挂掉,并不会影响另一个节点继续对外提供服务;

实现上述目标,需要在技术上满足如下条件:

1、 主数据库向从数据库同步数据时,使用异步方式代替同步方式;
2、 尽量多增加一些从数据库节点,如果一个节点挂掉,其他从数据库节点继续提供服务;

综上所述,分区容忍性存在如下特点:

1、 一个节点挂掉,不影响其他节点对外提供服务;
2、 分区容忍性是分布式系统必须具备的基础能力;

4.CAP 的组合

在分布式系统中,不会同时具备 CAP 三个特性,只能同时具备其中的两个

在CAP 理论中,如果要满足一致性,需要在数据由主数据库同步到从数据库的过程中对数据库加锁,以防止同步的过程中应用程序向从数据库读取不一致的数据,数据同步完成后会释放从数据库的锁。如果数据同步失败,则需要从数据库返回错误信息或者超时信息

如果要满足可用性,则必须保证数据节点的可用性,无论何时查询从数据库中的数据,从数据库都要快速响应查询结果,不能出现响应超时或者返回错误信息的情况

由此可见,系统在满足分区容忍性的前提下,一致性和可用性就是矛盾的。那么 CAP 理论中的三个特性有哪些组合方式呢?很显然,有 AP、CP、CA 三种组合方式

AP

放弃一致性,追求系统的可用性和分区容忍性。这是实际工作中,大部分分布式系统在架构设计时的选择

在实际场景中,大部分分布式系统会采用 AP 的方式,舍弃了一致性,这并不代表就真的放弃了一致性。此时,架构设计方案采用了最终一致性,允许多个节点的数据在一定的时间内存在差异,一段时间后达到数据一致的状态

CP

放弃可用性,追求系统的一致性和分区容忍性。这种组合方式对于数据的一致性要求比较高,追求的是强一致性

在实际场景中,跨行转账业务需要每个银行系统都执行完转账操作的整个事务才算完成,这是典型的 CP 方式

CA

放弃分区容忍性,追求系统的一致性和可用性。此时系统不会进行分区,也不会考虑网络不通和节点挂掉的问题。主数据库和从数据库不再进行数据同步,此时系统也不再是一个标准的分布式系统

六、Base 理论

分布式系统最多只能同时满足 CAP 理论中的两个特性。在实际场景中,大部分分布式系统会采用 AP 方式,即舍弃一致性,保证可用性和分区容忍性。但是通常情况下还是要保证一致性,这种一致性与 CAP 中描述的一致性有所区别:CAP 中的一致性要求的是强一致性,即任何时间读取任意节点的数据都必须一致,而这里的一致性指的是最终一致性,允许在一段时间内每个节点的数据不一致,但经过一段时间后,每个节点的数据达到一致

Base 理论概述

Base 理论是对 CAP 理论中 AP 的一个扩展,它通过牺牲强一致性来获得可用性。Base 理论中的 Base 是基本可用(Basically Available)、软状态(Soft State)和最终一致性(Eventually Consistent)的缩写。当系统出现故障时,Base 理论允许部分数据不可用,但是会保证核心功能可用;允许数据在一段时间内不一致,但是经过一段时间,数据最终是一致的。符合 Base 理论的事务可以称为柔性事务

基本可用

基本可用是指分布式系统出现故障时,允许其损失系统的部分可用性,比如响应时间或者功能上的损失,但是要保证系统基本可用。例如在电商业务场景中,添加购物车和下单功能出现故障时,商品浏览功能仍然可用

软状态

软状态是指允许系统中存在中间状态,这些中间状态不会影响系统的整体可用性,只是允许系统各个节点之间的数据同步存在延迟。例如在电商业务场景中,订单中的 “支付中”、“退款中” 等状态就是中间状态,当达到一段时间后,就会变成 “支付成功” 或者 “退款成功” 的状态

最终一致性

最终一致性是指系统中各个节点的数据副本经过一段时间的同步,最终能够达到一致的状态。最终一致性需要保证数据经过一段时间的同步达到一致,并不要求各个节点的数据保持实时一致。例如在电商业务场景中,订单中的 “支付中” “退款中” 等状态,最终会变成 “支付成功”、“退款成功” 的状态,经过一段时间的延迟,能够使得订单中的状态与最终的交易结果一致