因为在分布式系统中,为了保证数据的高可用,会将数据保留多个副本(replica),这些副本分布在不同的物理机器上。所以可能导致数据在进行写入副本的过程中出现丢失等故障,造成数据不一致的问题。
Consistency 一致性、Availability 可用性、Partition Tolerance 分区容错性
Consistency 一致性:这个和数据库ACID的一致性类似,但这里关注的所有数据节点上的数据一致性和正确性,而数据库的ACID关注的是在在一个事务内,对数据的一些约束。系统在执行过某项操作后仍然处于一致的状态。也就是说,在分布式系统中,更新操作执行成功后所有的用户都应该读取到最新写入的值。
Availability 可用性:每一个操作总是能够在一定时间内返回结果。所以系统操作成功or失败的结果一定要在给定的时间内返回。但也就是每一次请求都能得到一个及时的、非错的响应,但是不保证请求的结果是基于最新写入的数据。
Partition Tolerance:分区容错性。是否能够对数据进行分区,由于节点之间的网络问题,即使一些消息丢包或者延迟,整个系统依然能继续提供服务。考虑到性能和可伸缩性。
强一致性:当更新操作完成之后,任何多个后续进程or线程的访问都会返回最新的更新过的值。 牺牲了可用性。
弱一致性:不保证后续进程or线程返回最新的更新过的值。用户读到某一个操作对系统特定数据的更新需要一段时间,该段时间称为**“不一致性窗口”**。也就是说,系统在数据写入成功之后,不承诺立即可以读取到最新写入的数据,也不会具体的承诺多久之后可以读到。
最终一致性:弱一致性的一种特例。系统保证在没有后续更新的前提下,系统最终返回上一次更新操作的值。在没有故障发生的前提下,不一致窗口的时间主要是受到通信延迟,系统负载和复制副本的个数影响。
DNS是一个典型的最终一致性系统。
由于CAP 理论中,三点只能实现其中两点,但是由于网络硬件肯定会出现或多或少的延迟丢包的问题,所以分区容忍性是我们必须要实现的。所以我们只能在一致性和可用性中进行抉择。
对于web2.0网站来说,关系数据库的很多主要特性却往往无用武之地
BASE是Basically Available(基本可用)、Soft state(软状态)和Eventually consistent(最终一致性)三个短语的简写,BASE是对CAP中一致性和可用性权衡的结果,其来源于对大规模互联网系统分布式实践的结论,是基于CAP定理逐步演化而来的,其核心思想是即使无法做到强一致性(Strong consistency),但每个应用都可以根据自身的业务特点,采用适当的方式来使系统达到最终一致性(Eventual consistency)。
也就是说分布式系统在出现不可预知故障的时候,允许损失部分可用性。
这绝不等价于系统不可用。比如:
(1)响应时间上的损失。正常情况下,一个在线搜索引擎需要在0.5秒之内返回给用户相应的查询结果,但由于出现故障,查询结果的响应时间增加了1~2秒
(2)系统功能上的损失:正常情况下,在一个电子商务网站上进行购物的时候,消费者几乎能够顺利完成每一笔订单,但是在一些节日大促购物高峰的时候,由于消费者的购物行为激增,为了保护购物系统的稳定性,部分消费者可能会被引导到一个降级页面
软状态指的是允许系统中的数据存在中间状态,并认为该中间状态的存在不会影响系统的整体可用性 。也就说允许系统在不同节点的数据副本之间进行数据同步的过程存在延时。
最终一致性强调的是所有的数据副本,在经过一段时间的同步之后,最终都能够达到一个一致的状态。因此,最终一致性的本质是需要系统保证最终数据能够达到一致,而不需要实时保证系统数据的强一致性。
总的来说,BASE理论面向的是大型高可用可扩展的分布式系统,和传统的事物ACID特性是相反的,它完全不同于ACID的强一致性模型,而是通过牺牲强一致性来获得可用性,并允许数据在一段时间内是不一致的,但最终达到一致状态。
但同时,在实际的分布式场景中,不同业务单元和组件对数据一致性的要求是不同的,因此在具体的分布式系统架构设计过程中,ACID特性和BASE理论往往又会结合在一起。
TCC的核心思想是针对每一个操作,都要注册一个与其对应的确认和补偿(撤销)操作。
优点: 跟2PC比起来,实现以及流程相对简单了一些,但数据的一致性比2PC也要差一些
缺点: 缺点还是比较明显的,在2,3步中都有可能失败。TCC属于应用层的一种补偿方式,所以需要程序员在实现的时候多写很多补偿的代码,在一些场景中,一些业务流程可能用TCC不太好定义及处理。
两阶段提交协议是协调所有分布式原子事务参与者,并决定提交或者是取消(回滚)的分布式算法。
有两类机器(节点):一类是协调者Coordinator,一类是事务参与者(participants、cohorts或者workers)。事务参与者可以看做是数据副本。
协议中保证:1、 假设每一个节点都会记录写前日志并持久化存储,即使节点发生故障日志也不会丢失。
2、假设节点不会发生永久性故障而且任意两个节点都可以互相通信。
协调者将通知事务参与者 准备提交or取消事务,然后进入表决过程;
表决过程中,参与者将告知协调者自己的决策:同意or取消。同意则表示事务参与者本地作业执行成功;取消表示本地作业执行故障。
coordinator将基于第一个阶段投票结果进行决策:提交or取消。
当且仅当所有的参与者都同意的时候才会通知all参与者提交事务,否则coordinator将通知all participants取消事务。
参与者接收到协调者发来的消息之后执行相应的操作。
so,如果协调者出错同时参与者也出错时,两阶段无法保证事务执行的完整性。
考虑协调者再发出commit消息之后宕机,而唯一接收到这条消息的参与者同时也宕机了。
那么即使协调者通过选举协议产生了新的协调者,这条事务的状态也是不确定的,没人知道事务是否被已经提交。
三阶段提交协议在coordinator和participants中都引入了超时机制。并且将两阶段提交协议的第一个阶段拆分成了两步。询问,然后再锁资源,最后真正提交。
B. 其中一个Cohort向coordinator发送了No响应,或者等待超时之后,Coordinator都没有接收到Cohort的响应,那么就中断事务。
2PC和3PC的不同
3PC中,对于双方(coordinator和cohort)都加入了超时机制。(2PC中只有协调者拥有超时机制,就是说在一定时间内没有收到cohort的消息则默认失败)
3PC中,是在2PC的准备阶段和提交阶段之间,插入预提交阶段,使得3PC拥有CanCommit、PreCommit、DoCommit三个阶段。
PreCommit是一个缓冲,保证了在最后提交阶段之前各个参与节点的状态时一致的。
转一个维基百科的例子:
三阶段提交是“非阻塞”协议。
三阶段提交在两阶段提交的第一阶段与第二阶段之间插入了一个准备阶段,
使得原先在两阶段提交中,参与者在投票之后,由于协调者发生崩溃或错误,
而导致参与者处于无法知晓是否提交或者中止的“不确定状态”所产生的可能相当长的延时的问题得以解决。 举例来说,假设有一个决策小组由一个主持人负责与多位组员以电话联络方式协调是否通过一个提案,以两阶段提交来说,主持人收到一个提案请求,打电话跟每个组员询问是否通过并统计回复,然后将最后决定打电话通知各组员。
要是主持人在跟第一位组员通完电话后失忆,而第一位组员在得知结果并执行后老人痴呆,那么即使重新选出主持人,也没人知道最后的提案决定是什么,也许是通过,也许是驳回,不管大家选择哪一种决定,都有可能与第一位组员已执行过的真实决定不一致,老板就会不开心认为决策小组沟通有问题而解雇。
三阶段提交即是引入了另一个步骤,主持人打电话跟组员通知请准备通过提案,以避免没人知道真实决定而造成决定不一致的失业危机。
为什么能够解决二阶段提交的问题呢?
回到刚刚提到的状况,在主持人通知完第一位组员请准备通过后两人意外失忆,即使没人知道全体在第一阶段的决定为何,全体决策组员仍可以重新协调过程或直接否决,不会有不一致决定而失业。
那么当主持人通知完全体组员请准备通过并得到大家的再次确定后进入第三阶段,
当主持人通知第一位组员请通过提案后两人意外失忆,这时候其他组员再重新选出主持人后,
仍可以知道目前至少是处于准备通过提案阶段,表示第一阶段大家都已经决定要通过了,此时便可以直接通过。
如果进入PreCommit后,Coordinator发出的是abort请求,假设只有一个Cohort收到并进行了abort操作,
而其他对于系统状态未知的Cohort会根据3PC选择继续Commit,此时系统状态发生不一致性。
**优点:**降低了阻塞范围,在等待超时后协调者或参与者会中断事务。避免了协调者单点问题,阶段3中协调者出现问题时,参与者会继续提交事务。
**缺陷:**脑裂问题依然存在,即在参与者收到PreCommit请求后等待最终指令,如果此时协调者无法与参与者正常通信,会导致参与者继续提交事务,造成数据不一致。
本地消息表核心思想是将分布式事务拆成本地事务进行处理,思路来源于ebay。
基本思路:分为消息生产方、消息消费方、本地消息表。
消息生产方:额外新建一个消息表,并记录消息发送的状态。消息表和业务数据要在一个事务中提交(就是说消息表和事务在同一个数据库中),然后消息经过MQ之后发送到消息的消费方。若消息发送失败,会进行重新发送。
消息消费方:处理该消息,并完成自己的业务逻辑。如果本地事务处理成功,表明已经处理成功,若处理失败,则重新执行。若是业务上的失败,可以给生产者发送一个业务补偿消息,通知生产方进行回滚等操作。
生产方和消费方定时扫描本地消息表,把还没处理完成的消息 or 失败的消息再发送一遍。
如果有靠谱的自动对账补账逻辑,这种方案还是非常实用的。
这种方案遵循BASE理论,采用的是最终一致性,笔者认为是这几种方案里面比较适合实际业务场景的,即不会出现像2PC那样复杂的实现(当调用链很长的时候,2PC的可用性是非常低的),也不会像TCC那样可能出现确认或者回滚不了的情况。
优点: 一种非常经典的实现,避免了分布式事务,实现了最终一致性。在 .NET中 有现成的解决方案。
缺点: 消息表会耦合到业务系统中,如果没有封装好的解决方案,会有很多杂活需要处理。
还有其他的几种分布式的实现方案见:(包括2PC TCC 本地消息表 MQ事务消息 Sagas 事务模型等)https://blog.csdn.net/hxpjava1/article/details/79409395
分布式系统是一个非常广泛的概念,它最终要落实到解决实际问题上,不同的问题有不同的方法和架构。所有的开源软件都是以某个应用场景出现,而纯粹以“分布式”概念进行划分的比较少见。
但如果以算法划分,到能分出几类: