分布式事务和数据一致性

分布式事务相关

数据一致性(状态一致性)

raft算法的要求是选择一个节点作为leader, 客户端的读写操作都发往leader,
leader先将请求记录在log中,然后再发送请求到follower,follower半数写入成功后才向client返回。
其它节点接受领导的命令进行相关的操作,只要半数人状态达成一致就行了。
raft协议分成两个阶段:
                    阶段一进行选主,为第二阶段服务。
                    阶段二进行领导相关命令的执行,及时反馈给领导
同时也实现了分布式事务:
常用的传统的分布式事务通常采用2pc,3pc来进行实现,raft协议的第二阶段就相当于是一个优化的2pc事务处理方法,
2PC/3PC都是分布式一致性算法的残次版本,Google Chubby的作者Mike Burrows说过这个世界上只有一种一致性算法,
那就是Paxos,其它的算法都是残次品。

假如数据库在提交事务的时候突然断电,那么它是怎么样恢复的呢?  因为分布式系统的核心就是处理各种异常情况,
这也是分布式系统复杂的地方,因为分布式的网络环境很复杂,这种“断电”故障要比单机多很多,所以我们在做分布式系统
的时候,最先考虑的就是这种情况。这些异常可能有机器宕机、网络异常、消息丢失、消息乱序、数据错误、不可靠的TCP、
存储数据丢失、其他异常等等...
本地事务数据库断电的这种情况,它是怎么保证数据一致性的呢?我们使用SQLServer来举例,
在使用SQLServer数据库是由两个文件组成的,一个数据库文件和一个日志文件,通常情况下,日志文件都要比数据库文件
大很多。数据库进行任何写入操作的时候都是要先写日志的,同样的道理,我们在执行事务的时候数据库首先会记录下这个
事务的redo操作日志,然后才开始真正操作数据库,在操作之前首先会把日志文件写入磁盘,那么当突然断电的时候,即使
操作没有完成,在重新启动数据库时候,数据库会根据当前数据的情况进行undo回滚或者是redo前滚,这样就保证了数据
的强一致性。

集群刚开始启动时raft协议是如何操作的:
raft协议中,集群中的机器刚开始启动的时候大家的身份都是follower,当follower超过一段时间
(超时时间:每个follower都在150ms and 300ms之间随机,之后看谁先timeout,谁就先成为candidate,然后
它会先投自己一票,再向其他节点发起投票邀请。)
没有收到leader发来的心跳就表示leader已经死掉,增加自己的当前任期号并转化为candidate身份,然后它会先投
自己一票,再向其他follower节点发起投票邀请。如果此时其他follower节点还没有到达超时时间,次轮选举还没有
给自己投票或者投票给其它candidate,此时就会回应投票邀请,把票投给此candidate,然后重置自己的选举timeout。
如果此candidate得到过半的投票就成为leader,之后定期开始向follower发送心跳。如果两个follower同时成为
candidate的话,如果最后得到的票数相同,则等待其他follower的选择timeout之后成为candidate,继续开始新
一轮的选举。

log复制:leader把变动的log借助心跳同步给follower,过半回复之后才成功提交,之后再下一次心跳之后,
follower也commit变动,在自己的node上生效。如果在长时间后由于出现网络分区集群出现两个leader,
leader开始对比选举的term,发现有更高的term存在时,他们会撤销未提交的修改,然后以最新的为准。Raft 
通过比较两份日志中最后一条日志条目的索引值和任期号定义谁的日志比较新。如果两份日志最后的条目的任期号不同,
那么任期号大的日志更加新。如果两份日志最后的条目任期号相同,那么日志比较长的那个就更加新。

Raft的日志机制来维护一个不同服务器的日志之间的高层次的一致性。这么做不仅简化了系统的行为也使得更加可预计,
同时他也是安全性保证的一个重要组件。Raft 维护着以下的特性,这些同时也组成了的日志匹配特性:
如果在不同的日志中的两个条目拥有相同的索引和任期号,那么他们存储了相同的指令。
如果在不同的日志中的两个条目拥有相同的索引和任期号,那么他们之前的所有日志条目也全部相同。

任期term的概念:
Raft 把时间分割成任意长度的任期,任期用连续的整数标记。每一段任期从一次选举开始,一个或者多个候选人尝试
成为领导者。如果一个候选人赢得选举,然后他就在接下来的任期内充当领导人的职责。在某些情况下,一次选举过程
会造成选票的瓜分。
在这种情况下,这一任期会以没有领导人结束;一个新的任期(和一次新的选举)会很快重新开始。Raft 保证了在
一个给定的任期内,最多只有一个领导者。

Raft 协议将整个过程分为主要3个步骤:
领导者:和其他一致性算法相比,Raft 使用一种更强的领导能力形式。比如,日志条目只从领导者发送给其他的服
务器。这种方式简化了对复制日志的管理并且使得 Raft 算法更加易于理解。
领导选举:Raft 算法使用一个随机计时器来选举领导者。这种方式只是在任何一致性算法都必须实现的心跳机制上
增加了一点机制。在解决冲突的时候会更加简单快捷。
关系调整:Raft 使用一种共同一致的方法来处理集群成员变换的问题,在这种方法中,两种不同的配置都要求的大
多数机器会重叠。这就使得集群在成员变换的时候依然可以继续工作。

分布式事务

分布式事务:简单来说就是指对数据库的处理操作分布在不同的节点之上,而且操作的数据,分布于不同的数据库。
分布式事务,需要保证不同数据库的数据一致性。
分布式事务产生的原因:
1.数据库分库分表
处于数据量或者数据隔离的考虑,实际开发中需要进行分库分表。原来一个库现在变成了多个库,这时候要保证数据一致性,
就要用到分布式事务。
2.应用SOA
所谓的SOA话,就是业务的服务化。比如原来单机支撑了整个电商网站,现在对整个网站进行拆解,分离出了订单中心、
用户中心、库存中心。对于订单中心,有专门的数据库存储订单信息,用户中心也有专门的数据库存储用户信息,
库存中心也会有专门的数据库存储库存信息。这时候如果要同时对订单和库存进行操作,那么就会涉及到订单数据库
和库存数据库,为了保证数据一致性,就需要用到分布式事务。

CAP定理
CPA指的是,在一个分布式系统中,一致性(C)、可用性(A)、分区容错性(P),三者不可兼得。CPA是NoSQL数据库的基石。
一致性(Consistency) :在分布式系统中的所有数据备份,在同一时刻是否同样的值。(等同于所有节点访问同一份
最新的数据副本)
可用性(Availability):在集群中一部分节点故障后,集群整体是否还能响应客户端的读写请求。(对数据更新具备高可用性)
分区容错性(Partition tolerance):以实际效果而言,分区相当于对通信的时限要求。系统如果不能在时限内达成数据
一致性,就意味着发生了分区的情况,必须就当前操作在C和A之间做出选择。
CAP理论就是说在分布式存储系统中,最多只能实现上面的两点。而由于当前的网络硬件肯定会出现延迟丢包等问题,
所以分区容忍性是我们必须需要实现的。所以我们只能在一致性和可用性之间进行权衡,没有NoSQL系统能同时保证这三点。

两阶段提交这种解决方案属于牺牲了一部分可用性来换取的一致性。
BASE
BASE就是为了解决关系数据库强一致性引起的问题而引起的可用性降低而提出的解决方案。
BASE是下面三个术语的缩写:
基本可用(Basically Available)
软状态(Soft state)
最终一致(Eventually consistent)

常见的分布式事务解决方案(两类一致性(操作原子性(2pc,3pc)与副本一致性(raft/pasox)))

总结:2pc,3pc ==>解决分布式事务和数据一致性 ===提升===>pasox算法==简化===>raft算法

两阶段提交:
两阶段提交(Two Phase Commit, 2PC), 具有强一致性。
该协议将一个分布式的事务过程拆分成两个阶段: 投票阶段 和 事务提交阶段 。为了让整个数据库集群能够正常的运行,
该协议指定了一个“协调者”单点,用于协调整个数据库集群的运行。

第一阶段:投票阶段
该阶段的主要目的在于打探数据库集群中的各个参与者是否能够正常的执行事务,具体步骤如下:
协调者向所有的参与者发送事务执行请求,并等待参与者反馈事务执行结果。
事务参与者收到请求之后,执行事务但不提交,并记录事务日志。
参与者将自己事务执行情况反馈给协调者,同时阻塞等待协调者的后续指令。

第二阶段:事务提交阶段
在经过第一阶段协调者的询盘之后,各个参与者会回复自己事务的执行情况,这时候存在三种可能性:
1.所有的参与者都回复能够正常执行事务
2.一个或多个参与者回复事务执行失败
3.协调者等待超时
对于第一种情况,协调者将向所有的参与者发出提交事务的通知,具体步骤如下:
协调者向各个参与者发送 commit 通知,请求提交事务。
参与者收到事务提交通知之后,执行 commit 操作,然后释放占有的资源。
参与者向协调者返回事务 commit 结果信息

对于第二、三种情况,协调者均认为参与者无法成功执行事务,为了整个集群数据的一致性,所以要向各个参与者发
送事务回滚通知,具体步骤如下:
协调者向各个参与者发送事务 rollback 通知,请求回滚事务。
参与者收到事务回滚通知之后,执行 rollback 操作,然后释放占有的资源。
参与者向协调者返回事务 rollback 结果信息。

2pc会有几个问题:
1.同步阻塞(两阶段提交执行过程中,所有的参与者都需要听从协调者的统一调度,期间处于阻塞状态而不能从事其他操作,
这样效率极其低下。)
2.数据不一致(但仍然存在数据不一致性的可能性,比如在第二阶段中,假设协调者发出了事务 commit 通知,
但是因为网络问题该通知仅被一部分参与者所收到并执行了commit 操作,其余的参与者则因为没有收到通知一直处于
阻塞状态,这时候就产生了数据的不一致性。)。
3.单点问题。

三阶段提交:
第一阶段:can_commit
该阶段协调者会去询问各个参与者是否能够正常执行事务,参与者根据自身情况回复一个预估值,相对于真正的执行事务,
这个过程是轻量的,具体步骤如下:
协调者向各个参与者发送事务询问通知,询问是否可以执行事务操作,并等待回复。
各个参与者依据自身状况回复一个预估值,如果预估自己能够正常执行事务就返回确定信息,并进入预备状态,
否则返回否定信息。

第二阶段:pre_commit
本阶段协调者会根据第一阶段的询盘结果采取相应操作,询盘结果主要有三种:
1.所有的参与者都返回确定信息
2.一个或多个参与者返回否定信息
3.协调者等待超时

针对第一种情况,协调者会向所有参与者发送事务执行请求,具体步骤如下:
协调者向所有的事务参与者发送事务执行通知。
参与者收到通知后,执行事务但不提交。
参与者将事务执行情况返回给客户端。
在上述步骤中,如果参与者等待超时,则会中断事务。

针对第二、三种情况,协调者认为事务无法正常执行,
于是向各个参与者发出abort 通知,请求退出预备状态,具体步骤如下:
协调者向所有事务参与者发送 abort 通知。
参与者收到通知后中断事务。

第三阶段:do_commit
如果第二阶段事务未中断,那么本阶段协调者将会依据事务执行返回的结果来决定提交或回滚事务,分为三种情况:
1.所有的参与者都能正常执行事务
2.一个或多个参与者执行事务失败
3.协调者等待超时

针对第一种情况,协调者向各个参与者发起事务提交请求,具体步骤如下:
协调者向所有参与者发送事务 commit 通知。
所有参与者在收到通知之后执行 commit 操作,并释放占有的资源。
参与者向协调者反馈事务提交结果。

针对第二、三种情况,协调者认为事务无法成功执行,于是向各个参与者发送事务回滚请求,具体步骤如下:
1.协调者向所有参与者发送事务 rollback 通知。
2.所有参与者在收到通知之后执行 rollback 操作,并释放占有的资源。
3.参与者向协调者反馈事务回滚结果。

升级的3PC方案旨在解决这些问题,主要有两个改进:增加超时机制。两阶段之间插入准备阶段。但三阶段提交也存在
一些缺陷,要彻底从协议层面避免数据不一致,可以采用Paxos或者Raft 算法。

TCC(Try-Confirm-Cancel):
TCC包含了三个阶段:Try,Confirm,Cancel,因此而得名「TCC」。
TCC的概念属于国产,因为支付宝的技术布道而广为人知。
其实,TCC算是一种编程模型,通常被理解为是一种柔性事务解决方案。
Try,尝试执行业务。完成所有业务检查,预留相应的业务资源。
Confirm,如果Try阶段执行成功,则此阶段利用Try阶段预留的资源,不再进行业务检查,而是执行真正的业务提交。
并且Confirm阶段的操作满足幂等性,以便支持重试。这个阶段有一个假设:只要Try阶段成功,那么Confirm阶段一定成功。
Cancel,此阶段是发生在Try阶段出现失败的时候,回滚之前的操作,释放Try阶段预留的业务资源,同样也满足幂等性。

异步确保型:
通过将一系列同步的事务操作变为基于消息执行的异步操作, 避免了分布式事务中的同步阻塞操作的影响。基于消息执行
就是基于消息中间件的两阶段提交,本质上是对消息中间件的一种特殊利用,它是将本地事务和发消息放在了一个分布式
事务里,保证要么本地操作成功成功并且对外发消息成功,要么两者都失败,开源的RocketMQ就支持这一特性.
最大努力通知型:
这是分布式事务中要求最低的一种, 也可以通过消息中间件实现, 与前面异步确保型操作不同的一点是, 在消息由
MQ Server投递到消费者之后, 允许在达到最大重试次数之后正常结束事务.

一致性模型

数据的一致性模型可以分成以下 3 类:
强一致性:数据更新成功后,任意时刻所有副本中的数据都是一致的,一般采用同步的方式实现。
弱一致性:数据更新成功后,系统不承诺立即可以读到最新写入的值,也不承诺具体多久之后可以读到。
最终一致性:弱一致性的一种形式,数据更新成功后,系统不承诺立即可以返回最新写入的值,但是保证最终会返回上
一次更新操作的值。

你可能感兴趣的:(分布式存储开发实战系列)