引言

如果近几年从业于软件工程,特别是服务器端和后端系统开发,那么您很有可能已经被大量关于数据存储和处理的时髦词汇轰炸过了: NoSQL!大数据!Web-Scale!分片!最终一致性!ACID! CAP定理!云服务!MapReduce!实时! 在最近十年中,我们看到了很多有趣的进展,关于数据库,分布式系统,以及在此基础上构建应用程序的方式。

本文首先介绍什么是分布式系统,跟集群有什么区别。其次,引入事务ACID的概念以及在单机过渡到分布式后产生哪些问题和解决方式,包括CAP/BASE理论、2PC、3PC等。最后,介绍分布式事务中协调者的共识问题,从而引入Paxos、Raft分布式一致性协议算法。

分布式与集群

这里我们可以拿一个电商网站来举例,有登录、商品、下单、支付、物流5个功能。分别说明单体模式、集群模式和分布式的区别。

  • 单体模式:该方式,相当于一个整体的单机系统,将5个功能整合在一起对外提供服务;
  • 集群模式:当用户量上涨后,单体模式不能支撑时。可以将单体模式扩展出多个实例出来,通过负载均衡的方式来提高请求的并发;
  • 分布式:将单体模式中的5个功能,拆解为用户服务、商品服务、订单服务、交易服务、物流服务5个服务独立部署,即业务按照面向服务(SOA)的架构拆分整个网站系统,对外提供服务。

由此可以看出,分布式是指通过网络连接的多个组件,通过交换信息协作而形成的系统。而集群是指同一种组件的多个实例,形成的逻辑上的整体。

分布式事务

分布式事务就是指事务的资源分别位于不同的分布式系统的不同节点之上的事务。这里还是拿上一节的例子来说明,比如订单创建成功后同时要修改库存,订单服务和商品服务的操作必须是一个事务型操作,要么全部成功,要么全部失败。除此以外,产生分布式事务还有分库分表、业务微服务拆分等。下面从单体模式的ACID理论到分布式CAP理论演进存在的问题展开介绍。

ACID

ACID是数据库事务的四大特性,包含:原子性(Atomicity)、一致性(Consistency)、隔离性(Isolation)、持久性(Durability)。其中:

  • 原子性:一个事务内的多个操作共同组成一个原子操作,要么全部执行成功,要么全部执行失败;
  • 一致性:一个原子性操作过程中的多个操作产生的中间状态对外是不可见的,永远只有事务提交前、后的两种状态,即从事务提交前的一致性状态到事务提交后的一致性状态;
  • 隔离性:不同事务的原子操作之间是可以并发执行、相互隔离、互不影响的,如果多个事务操作同一个资源,会基于数据库锁来实现(比如悲观锁、乐观锁);
  • 持久性:事务一旦提交成功后,数据库的状态变化,即使机器宕机,也不会发生变化(比如写入WAL)。

在分布式模式下,引入全局事务管理器(协调器)来管理每个单体模式的事务提交或回滚,最终确定一个完整分布式事务是成功还是失败。

而在分布式环境下,每个单体服务与协调器的通信依靠网络,同时协调器以及单体服务本身,也有宕机不可用的可能,在这样的分布式环境该如何保证事务中的各单体服务的状态一致性,是需要解决的问题。

CAP

CAP理论起源于2000年,由加州大学伯克利分校的Eric Brewer教授在分布式计算原理研讨会(PODC)上提出,因此 CAP定理又被称作布鲁尔定理(Brewer’s theorem)。2年后,麻省理工学院的Seth Gilbert和Nancy Lynch 发表了布鲁尔猜想的证明,CAP理论正式成为分布式领域的定理

CAP 指 Consistency(一致性)、Availability(可用性)、Partition Tolerance(分区容错性)。其中:

  • 一致性 : 所有节点访问同一份最新的数据副本;
  • 可用性: 非故障的节点在合理的时间内返回合理的响应(不是错误或者超时的响应);
  • 分区容错性 : 分布式系统出现网络分区的时候,仍然能够对外提供服务。

补充说明:在分布式系统中,多个节点之前的网络本来是连通的,但是因为某些故障(比如部分节点网络出了问题)某些节点之间不连通了,整个网络就分成了几块区域,这就叫网络分区

由此可见,CAP理论中分区容错性P是一定要满足的,在此基础上,只能满足可用性 A 或者一致性 C。因此,分布式系统理论上不可能选择 CA 架构,只能选择 CP 或者 AP 架构。

当P发生时,要根据业务场景来考虑选择CP还是AP,当P没有发生时,要考虑如何保证CA。

  • CA:放弃分区容错性,加强一致性和可用性,其实就是传统的单机数据库选择;
  • AP:放弃强一致性,加强分区容错性和可用性,如Eureka,服务注册中心;
  • CP:放弃高可用性,加强一致性和分区容错性,如Zookeeper,集群选主。

BASE

BASE理论起源于2008年, 由eBay的架构师Dan Pritchett在ACM上发表。BASE理论是对 CAP 中一致性C和可用性A权衡的结果,其来源于对大规模互联网系统分布式实践的总结,是基于CAP 定理逐步演化而来的,它大大降低了我们对系统的要求。

分布式一致性的3种级别:

  • 强一致性:系统写入了什么,读出来的就是什么;
  • 弱一致性 :不一定可以读取到最新写入的值,也不保证多少时间之后读取到的数据是最新的,只是会尽量保证某个时刻达到数据一致的状态;
  • 最终一致性:弱一致性的升级版,系统会保证在一定时间内达到数据一致的状态。

BASE 理论本质上是对 CAP 的延伸和补充,更具体地说,是对 CAP 中 AP 方案的一个补充。即,AP 方案只是在系统发生分区的时候放弃一致性,而不是永远放弃一致性。在分区故障恢复后,系统应该达到最终一致性

BASE理论三要素:基本可用(Basically Available)、软状态(Soft-state)、最终一致性(Eventually Consistent)。其中:

  • 基本可用:指分布式系统在出现不可预知故障的时候,允许损失部分可用性。但是,这绝不等价于系统不可用。比如系统出现故障,响应时间变慢但还是可以访问。比如系统访问量剧增,保证核心功能可用,非核心功能暂不可用。
  • 软状态:系统中的数据存在中间状态(即允许系统在不同节点的数据副本之间进行数据同步的过程存在延时,不一致),该状态不影响系统的整体可用性。比如服务注册中心,如果读取不一致,顶多服务请求有些许倾斜,但至少不会出现服务不可用。
  • 最终一致性:强调的是系统中所有的数据副本,在经过一段时间的同步后,最终能够达到一个一致的状态。因此,最终一致性的本质是需要系统保证最终数据能够达到一致,而不需要实时保证系统数据的强一致性。业界比较推崇是最终一致性级别,但是某些对数据一致要求十分严格的场景比如银行转账还是要保证强一致性。

一致性C

一致性这个词在不同的上下文中具有不同的含义,这个概念理解会有一些混淆:

  • 在事务的上下文中,这个一致性就是常规的概念,即所有参与事务的节点状态保持一致,要么全部成功提交,要么全部失败回滚,不会出现一些节点成功一些节点失败的情况;
  • 在分布式系统上下文中,这个一致性是指线性一致性,即多副本的系统能够对外表现地像只有单个副本一样,且所有操作都以原子的方式生效。

以上两种一致性是有区别的,在实现分布式事务时,需要引入协调者,由于是单点的,就会涉及可用性问题,因此分布式事务的节点需要在协调者故障时就新协调者选取达成共识(consensus),尽快恢复协调者的可用性,从而恢复分布式事务的运转,从而恢复分布式系统本身对外的可用性。

总结来说,分布式事务中的一致性是通过协调者内部的原子操作与多阶段提交协议保证的,不需要共识。但解决分布式事务一致性带来的可用性问题需要用到共识。

实现方案

2PC

两阶段提交是一种使分布式系统中所有节点在进行事务提交时保持一致性而设计的一种协议,现在很多数据库都是采用的两阶段提交协议来完成 分布式事务 的处理。换句话说,我们要解决的是在分布式系统中,我们所有服务的数据处理要么都成功、要么都失败,即所有服务的 原子性问题

由于,每个节点可以知道本身的执行状态,不知道其他节点的执行状态,因此需要引入协调者组件来统一管理全部节点。

2PC分为投票阶段和提交阶段:

  • 第一阶段投票:协调者Coordinator向参与者Cohort询问是否可以执行操作请求并等待响应,Cohort执行事务操作并记录重做和回滚日志,并向coordinator发送agreement或abort响应表示操作结果;相当于每个cohort向coordinator投票,所有cohort投完票后,进入提交阶段;
  • 第二阶段提交:cohort都是agreement,coordinator会向每个cohort发送commit请求,cohort完成操作并释放资源后向协调者返回完成消息,coordinator收到每个cohort完成消息后结束整个事务。一旦有cohort是abort,则coordinator向所有cohort发起rollback请求,cohort根据回滚日志对操作回滚,并向协调者返回完成消息。

2PC缺点有:

  • 单点故障:coordinator存在挂掉可能性;
  • 同步阻塞:由于单点故障,则部分cohort一直等待coordinator的commit或ollback请求,处于阻塞状态,资源没有释放;
  • 数据不一致:由于单点故障,部分cohort事务提交了,部分cohort事务没有提交。

3PC

三阶段提交为了解决2pc中由于coordinator单点故障导致的阻塞问题数据不一致的问题并没有,下面描述),3pc引入超时机制和准备阶段,coordinator和cohort在规定时间内没有收到响应,根据当前状态选择commit或abort。如下图示:

image-20210303125735991

  • 第一阶段CanCommit:协调者Coordinator向参与者Cohort询问是否可以执行操作请求并等待响应YES或NO。
  • 第二阶段PreCommit:在上一个阶段,如果Coordinator收到所有Cohort返回YES,则Coordinator发送PreCommit请求给Cohort,后续操作如2PC第一阶段。如果Cohort返回NO或者部分Cohort响应超时,则Coordinator向所有Cohort发送abort请求来中断事务,同时Cohort没有收到abort请求或其他来自Coordinator的消息,则也会自动中断事务。
  • 第三阶段DoCommit:在上一个阶段,如果Coordinator收到所有Cohort的ACK消息后,则Coordinator发送doCommit请求给Cohort,后续操作如2PC第二阶段。如果Cohort返回NO或者部分Cohort响应超时,则发送中断请求操作给Cohort进行日志回滚并响应给Coordinator中断事务。其中,即使Cohort没有收到Coordinator的doCommit请求,也会在一定时间内自动提交。

3PC缺点有:

  • 虽然3pc解决了阻塞问题,但当第二阶段,preCommit发出后,有的Cohort完成事务处于待提交状态,但是Coordinator挂了没有发送请求或Cohort没有收到请求,此时有的Cohort事务中断了,有的Cohort事务自动提交了,从而导致数据不一致问题。

TCC

2PC、3PC都要求Cohort本身支持事务的能力,业务微服务拆分后的场景,一些服务并具备事务,需要通过业务逻辑来控制提交或回滚。所以说事务的提交和回滚就得提升到业务层面而不是数据库层面了,而 TCC 就是一种业务层面或者是应用层的两阶段提交。

TCC是Try、Confirm、Cancel 3个操作的缩写。Try操作对应2PC的一阶段Try,Confirm对应2PC的二阶段commit,Cancel对应2PC的二阶段rollback。

TCC事务的处理流程与2PC两阶段提交类似,不过2PC通常都是在跨库的DB层面,而TCC本质上就是一个应用层面的2PC,需要通过业务逻辑来实现。这种分布式事务的实现方式的优势在于,可以让应用自己定义数据库操作的粒度,使得降低锁冲突、提高吞吐量成为可能

TCC需要几个条件约束:

  • 由于网络超时导致不停重试,此时try、commit、cancel对应的操作需要具备幂等性
  • 没有执行try的时候,执行cancel,要具备空回滚能力。

Seata

Seata是一款开源的分布式事务解决方案,致力于在微服务架构下提供高性能和简单易用的分布式事务服务。具有特性:

  • 微服务框架支持:目前已支持 Dubbo、Spring Cloud、Sofa-RPC、Motan 和 grpc 等RPC框架,其他框架持续集成中;
  • AT 模式:提供无侵入自动补偿的事务模式,目前已支持 MySQL、 Oracle 、PostgreSQL和 TiDB的AT模式,H2 开发中;
  • TCC 模式:支持 TCC 模式并可与AT混用,灵活度更高;
  • SAGA 模式:为长事务提供有效的解决方案;
  • XA 模式:支持已实现 XA 接口的数据库的 XA 模式;
  • 高可用:支持基于数据库存储的集群模式,水平扩展能力强。

共识算法

为了提高事务管理器的可用性,防止事务信息的丢失,可以采用多副本机制提高系统的容错性,类似一个高容错、高可用性的分布式存储系统,解决事务管理器或协调器的单点故障问题。

在分布式存储系统中,通常以多副本冗余的方式实现数据的可靠存储。同一份数据的多个副本必须保证一致,而数据的多个副本又存储在不同的节点中,这里的分布式一致性问题就是存储在不同节点中的数据副本(或称为变量)的取值必须一致。

在单机版的数据库系统中,一般通过事务来保证数据的一致性和完整性。而在分布式系统中,为了保证分布式一致性,就需要引入分布式共识算法来保证各个节点之间的一致性。

共识算法保证的就是节点间的强一致性,与应用层面感知的强一致性、最终一致性是有所区别的。

Paxos和Raft算法

Paxos算法诞生于 1900 年,这是一种解决分布式系统一致性的经典算法 。但是,由于Paxos算法非常难以理解和实现,不断有人尝试简化这一算法。到了2013 年才诞生了一个比 Paxos 算法更易理解和实现的分布式一致性算法Raft 算法

  • Paxos:Google的Chubby分布式锁服务,采用了Paxos算法;
  • Raft(muti-paxos):etcd分布式键值数据库,采用了Raft算法,还有braft, tikv 等;
  • ZAB(muti-paxos):ZooKeeper分布式应用协调服务,Chubby的开源实现,采用ZAB算法,但没有抽象成通用的 library。

通过 RAFT 提供的一致性状态机,可以解决复制、修复、节点管理等问题,极大的简化当前分布式系统的设计与实现,让开发者只关注于业务逻辑,将其抽象实现成对应的状态机即可。

基于这套框架,可以构建很多分布式应用:

  • 分布式锁服务,比如 Zookeeper;
  • 分布式存储系统,比如分布式消息队列、分布式块系统、分布式文件系统、分布式表格系统等;
  • 高可靠元信息管理,比如各类 Master 模块的 HA。

JRAFT介绍

JRAFT设计

Raft Group

Multi Raft Group

参考资料