关于分布式系统的思考(二)

接着上一篇的内容,详细介绍一些主流数据库在分布式场景下用到的算法和思想,主要说起数据一致性相关的一些策略,并分析其利弊和典型应用场景。git

对于数据库来讲,可能关心的最多的就是数据的一致性了,由此衍生出了不一样场景下的算法和策略。
在上一篇末尾说起了两种集群结构:中心化去中心化github

中心化

一种是中心化的,由中心节点去存储集群信息并管理集群状态,其它节点只需响应数据请求,而无需知道集群中其它节点的状况。
这种模式的核心即是选举或者指定一个节点做为集群的管理者,由管理者去协调跨节点的操做、备份数据和处理故障等。 算法

center

通常的,对于跨节点的操做,为了保证事务的原子性,提出了两步提交协议或三步提交协议,下面分别介绍。数据库

2pc

两步提交协议,顾名思义,就是将数据的提交分为两步:投票和决策。 segmentfault

2pc

首先,在第一阶段,微信

  1. 中心节点(在这里咱们称之为协调者)发起事务操做请求,包含事务内容,询问是否能够执行提交操做并等待响应;网络

  2. 其它节点(在这里称之为参与者)执行事务操做并记录undo/redo log,最后返回是否赞成提交。架构

而后,在第二阶段,协调者根据全部参与者的投票结果,若是是都赞成则通知全部参与者提交事务,不然回滚事务。
收到全部参与者回应后,完成事务。 socket

接着,咱们考虑下两步提交过程当中若是发生异常,会出现什么样的状况,会不会影响结果的一致性,并尝试解决。分布式

  • 在第一阶段时,有节点宕机

    1. 有参与者宕机,此时协调者接收到错误响应,可认为是失败,将中断事务。

    2. 协调者宕机,此时参与者等待协调者的操做通知,事务会阻塞直到协调者恢复。
      对于此种状况,解决的办法是能够设置多个协调者,一主多从,宕机后指定一台从做为新的主。

    参与者也须要记录事务的投票状态,以便新的协调者从新找回事务状态。

    1. 参与者和协调者都宕机了,如上一条,新的协调者将会获取不到参与者的事务状态(该参与者的状态只有本身和原协调者知道),会一直阻塞地等待全部参与者恢复。
      其它参与者也会处于两阶段之间,直到宕机的参与者恢复。

  • 在第二阶段,有节点宕机

    1. 有参与者宕机,此时未宕机的参与者会正常地提交/回滚事务,而因为并不知道宕机的时机,因此可能会致使数据的不一致。

    2. 协调者宕机,如果在发送通知前,那么参与者将阻塞地等待协调者恢复。可经过设置协调者的备份来解决,要求参与者记录事务状态。如果在发送通知后,不影响可忽略。

    3. 参与者与协调者都宕机了,如上两条,可能会致使数据的不一致或阻塞。

注意,以上的宕机若是替换为网络分区,也会是一样的状况。

能够看出,2pc的优势是简单直接,缺点是:

  • 当有故障发生会阻塞事务的执行,进而影响到相关资源的释放;

  • 协调者的单点问题;

  • 当二阶段有参与者宕机或者网络分区时,可能会致使数据不一致。

针对这些缺陷,出现了3pc。

3pc

三步提交协议,改进了2pc的一些缺陷,它增长了一个询问是否可提交阶段。如图所示:

3pc

第一阶段时,协调者询问各参与者是否能够执行事务提交,包含事务内容,并等待参与者的响应。
参与者收到请求后,若是认为能够成功执行事务,则返回赞成,不然停止事务。

第二阶段时,协调者根据第一阶段参与者的返回消息,决定是准备提交或是停止事务。若是都是赞成,那么发送预提交请求。
参与者收到请求后,会执行事务操做,并记录undo/redo log, 最后返回提交/停止事务。

第三阶段时,协调者根据第二阶段的响应,决定通知参与者提交/回滚事务,收到响应后完成事务。

3pc相比于2pc的优势在于:

  • 在协调者和参与者端都添加了超时机制,其中:参与者超时未应答均认为是失败;协调者在第二阶段超时未发送请求视为失败,而第三阶段超时未发送请求视为成功,参与者在通过了指定超时时间后提交事务。这样便具有了必定的容错性。
    不只如此,这样还能够有效减小阻塞时间。

  • 提供了协调者的主备方案,避免了单点问题。

缺点:

  • 第二阶段时,参与者在接收到预提交请求后发生网络分区,此参与者在超时后提交事务,而协调者在超时后认为事务失败并通知其它参与者回滚事务,最终致使数据不一致。若发生此状况,只能经过上层去协调解决这个问题,如上一篇提到的两种解决方案。(2pc也有相似缺陷)

  • 比2pc多了一个阶段,意味着同等状况下,耗时要多一点。

去中心化

另外一种则是去中心化的,由节点之间互相通讯去协商一致。比较有名的算法如Paxos。

Paxos算法在分布式领域具备很是重要的地位,Google Chubby的做者Mike Burrows曾经说过,这个世界上只有一种一致性算法,那就是Paxos,其它的都是残次品。

不过这个算法实在是难理解,难实现;之后有机会我会专门总结一篇文章分享下,有兴趣的道友能够先去看看《Paxos Made Simple》,写得很不错。

此外,考虑到集群中的节点数量并非一成不变的,因此若是使用的是通常的Hash算法,那么在集群新增节点或删除节点时,会致使节点间大量数据的迁移,进而影响可用性,故而提出了一致性Hash算法以减小数据的迁移量。

一致性Hash

通常的Hash算法,如对key取模而后分散到不一样的节点中:假设有3个节点,共有key分别为1~7的数据,分配结果以下图

hash1

如今,若是新增一个节点,那么分配结果变为:

hash2

能够发现大部分的节点都被从新分配到了不一样的节点上,即迁移数据是O(n)复杂度(n为数据总量),没法平滑地扩缩容。
接下来再来看下一致性Hash,它的分配方式是对key和节点作相同的Hash运算,而后将key分配到恰好大于或等于它Hash值的节点上(若节点都比它Hash值小,则分配到最小的节点上,即造成一个“环”);

仍是上面的那个例子,对key和节点都作对7取模的Hash计算,而后分配。先是有三个节点:

chash1

新增一个节点:

chash2

能够看出,增长一个节点后只有少许数据从5节点移动到4节点,极大的减小了数据迁移量。
可是,一致性Hash也有缺陷:查找效率低。通常须要逐个去比较Hash值直到找到恰好大于等于的节点,故查找复杂度为O(k)(k为节点数量)。
能够经过在节点中冗余一份节点表来加快查找。

总结

保证一致性,要么是经过共享存储,要么是经过消息协调。
数据库自己就是共享存储。
不论是2pc、3pc仍是paxos,都是经过节点间的交换消息去达到一致的状态,这也是分布式系统的经常使用作法。
了解了这些策略的原理后,不论是用Zookeeper、RabbitMQ、Redis或其它消息组件(甚至是基于socket通讯)去实现它,都是水到渠成的事情了。

超时是个好设计,由于它是不需询问即可以察觉错误的方式(毕竟没有错误就不会超时了),不少设计中都会将超时做为一种信号,并尝试容错/修复等操做。

在运行过程的一些错误并不能经过底层的策略彻底规避,须要根据具体业务在上层作相应的容错措施。

冗余是个好设计,几乎在各类组件的设计都能见到,经过牺牲一点空间较大地提升检索效率。

有机会的话,以后的篇章我会收集并比较几种典型分布式组件的具体实现,对这些组件有个更加直观和深刻的理解,以便充实和改进本身的知识结构并分享出来。

最后为方便查询,整理了下往期文章到github中:https://github.com/dengyuankai272/blog


做者信息
本文系力谱宿云LeapCloud旗下MaxLeap团队_基础服务组成员:吕舜 【原创】
力谱宿云LeapCloud 首发:https://blog.maxleap.cn/archi...
吕舜,主攻Java,对Python、数据分析也有关注。从业期间,负责过订阅系统、App制做云服务、开源BaaS平台、分布式任务调度系统等产品的设计研发工做。现任MaxLeap基础服务与架构成员,负责云服务系统相关的设计与开发。

相关文章
微服务实战:从架构到发布(一)
微服务实战:从架构到发布(二)
移动云平台的基础架构之旅(一):云应用
从应用到平台 – 云服务架构的演进过程

做者往期佳做
RabbitMQ在分布式系统的应用
关于分布式系统的思考

欢迎扫如下二维码,关注咱们的微信订阅号:
图片描述

相关文章
相关标签/搜索