开源分布式应用系统协调
2021-02-26 15:27:43 阿炯

分布式应用系统

分布式系统(distributed system)是建立在网络之上的软件系统。正是因为软件的特性,所以分布式系统具有高度的内聚性和透明性。因此,网络和分布式系统之间的区别更多的在于高层软件特别是操作系统,而不是硬件。

分布式系统是一组计算机,透过网络相互连接传递消息与通信后并协调它们的行为而形成的系统。组件之间彼此进行交互以实现一个共同的目标。把需要进行大量计算的工程数据分割成小块,由多台计算机分别计算,再上传运算结果后,将结果统一合并得出数据结论的科学。分布式系统的例子来自有所不同的面向服务的架构,大型多人在线游戏,对等网络应用。目前分布式计算项目通常使用世界各地上千万志愿者计算机的闲置计算能力,通过互联网进行数据传输志愿计算。虽然现在有了计算能力超强的超级计算机,但这些设备造价高昂,而一些科研机构的经费却又十分有限,借助分布式计算可以花费较小的成本来达到目标。

在计算机网络中,这种统一性、模型以及其中的软件都不存在。用户看到的是实际的机器,计算机网络并没有使这些机器看起来是统一的。如果这些机器有不同的硬件或者不同的操作系统,那么这些差异对于用户来说都是完全可见的。如果一个用户希望在一台远程机器上运行一个程序,那么,他必须登陆到远程机器上,然后在那台机器上运行该程序。分布式系统和计算机网络系统的共同点是:多数分布式系统是建立在计算机网络之上的,所以分布式系统与计算机网络在物理结构上是基本相同的。

在一个分布式系统中,一组独立的计算机展现给用户的是一个统一的整体,就好像是一个系统似的。系统拥有多种通用的物理和逻辑资源,可以动态的分配任务,分散的物理和逻辑资源通过计算机网络实现信息交换。系统中存在一个以全局的方式管理计算机资源的分布式操作系统。通常,对用户来说,分布式系统只有一个模型或范型。在操作系统之上有一层软件中间件(middleware)负责实现这个模型。

分布式计算机系统的体系结构可用处理机之间的耦合度为主要标志来加以描述。耦合度是系统模块之间互联的紧密程度,它是数据传输率、响应时间、并行处理能力等性能指标的综合反映,主要取决于所选用体系结构的互联拓扑结构和通信链路的类型。按地理环境衡量耦合度,分布式系统可以分为机体内系统、建筑物内系统、建筑物间系统和不同地理范围的区域系统等,它们的耦合度依次由高到低按应用领域的性质决定耦合度,可以分成三类:
1)是面向计算任务的分布并行计算机系统和分布式多用户计算机系统,它们要求尽可能高的耦合度,以便发展成为能分担大型计算机和分时计算机系统所完成的工作。

2)是面向管理信息的分布式数据处理系统。耦合度可以适当降低。

3)是面向过程控制的分布式计算机控制系统。耦合度要求适中,当然对于某些实时应用,其耦合度的要求可能很高。

分布式系统是多个处理机通过通信线路互联而构成的松散耦合的系统。从系统中某台处理机来看,其余的处理机和相应的资源都是远程的,只有它自己的资源才是本地的。至今,对分布式系统的定义尚未形成统一的见解。一般认为,分布式系统应具有以下四个特征:
1)分布性。分布式系统由多台计算机组成,它们在地域上是分散的,可以散布在一个单位、一个城市、一个国家,甚至全球范围内。整个系统的功能是分散在各个节点上实现的,因而分布式系统具有数据处理的分布性。

2)自治性。分布式系统中的各个节点都包含自己的处理机和内存,各自具有独立的处理数据的功能。通常,彼此在地位上是平等的,无主次之分,既能自治地进行工作,又能利用共享的通信线路来传送信息,协调任务处理。

3)并行性。一个大的任务可以划分为若干个子任务,分别在不同的主机上执行。

4)全局性。分布式系统中必须存在一个单一的、全局的进程通信机制,使得任何一个进程都能与其他进程通信,并且不区分本地通信与远程通信。同时,还应当有全局的保护机制。系统中所有机器上有统一的系统调用集合,它们必须适应分布式的环境。在所有CPU上运行同样的内核,使协调工作更加容易。


优缺点

优点
1)资源共享。若干不同的节点通过通信网络彼此互联,一个节点上的用户可以使用其他节点上的资源,如分布式系统允许设备共享,使众多用户共享昂贵的外部设备,如彩色打印机;允许数据共享,使众多用户访问共用的数据库;可以共享远程文件,使用远程特有的硬件设备如高速阵列处理器,以及执行其他操作。

2)加快计算速度。如果一个特定的计算任务可以划分为若干个并行运行的子任务,则可把这些子任务分散到不同的节点上,使它们同时在这些节点上运行,从而加快计算速度。另外,分布式系统具有计算迁移功能,如果某个节点上的负载太重,则可把其中一些作业移到其他节点去执行,从而减轻该节点的负载。这种作业迁移称为负载平衡。

3)可靠性高。分布式系统具有高可靠性。如果其中某个节点失效了,则其余的节点可以继续操作,整个系统不会因为一个或少数几个节点的故障而全体崩溃。因此,分布式系统有很好的容错性能。系统必须能够检测节点的故障,采取适当的手段,使它从故障中恢复过来。系统确定故障所在的节点后,就不再利用它来提供服务,直至其恢复正常工作。如果失效节点的功能可由其他节点完成,则系统必须保证功能转移的正确实施。当失效节点被恢复或者修复时,系统必须把它平滑地集成到系统中。

4)通信方便、快捷。分布式系统中各个节点通过一个通信网络互联在一起。通信网络由通信线路、调制解调器和通信处理器等组成,不同节点的用户可以方便地交换信息。在低层,系统之间利用传递消息的方式进行通信,这类似于单CPU系统中的消息机制。单CPU系统中所有高层的消息传递功能都可以在分布式系统中实现,如文件传递、登录、邮件、Web浏览和远程过程调用( Remote Procedure call,RPC)。

分布式系统实现了节点之间的远距离通信,为人与人之间的信息交流提供了很大方便不同地区的用户可以共同完成一个项目,通过传送项目文件,远程登录进入对方系统来运行程序,如发送电子邮件等,协调彼此的工作。

缺点
尽管分布式系统具备众多优势,但它也有自身的缺点,主要是可用软件不足,系统软件、编程语言、应用程序以及开发工具都相对很少。此外,还存在通信网络饱和或信息丢失和网络安全问题,方便的数据共享同时意味着机密数据容易被窃取。虽然分布式系统存在这些潜在的问题,但其优点远大于缺点,而且这些缺点也正得到克服。因此,分布式系统仍是人们研究、开发和应用的方向。


设计难点

虽然分布式系统具有很多优点,然而由于分布式系统自身的特点及应用环境的复杂性,分布式系统设计有如下的很多难题需要解决:

1.部分失效问题
由于分布式系统通常由若干部分组成,各个部分由于各种原因可能发生故障,如硬件故障、软件错误及错误操作等。如果一个分布式系统不对这些故障进行有效的处理,系统某一组成部分的故障可能导致整个系统的瘫痪。

2.性能和可靠性过分依赖于网络
由于分布式系统是建立在网络之上的,而网络本身是不可靠的,可能经常发生故障,网络故障可能导致系统服务的终止。另外,网络超负荷会导致性能的降低,增加系统的响应时间。

3.缺乏统一控制
一个分布式系统的控制通常是一个典型的分散控制,没有统一的中心控制。因此,分布式系统通常需要相应的同步机制宋协调系统中各个部分的工作。设计与实现一个对用户来说是透明的且具有容错能力的分布式系统是一项具有挑战性的工作,而且所需的机制和策略尚未成熟。因此什么样的程序设计模型、什么样的控制机制最适合分布式系统仍是需要继续研究的课题。两个或多个机器尝试执行特定任务,实际上只需在任意给定时间由单个机器完成。两个或多个操作等待彼此无限期完成。

4.难以合理设计资源分配策略
在集中式系统中,所有的资源都由操作系统管理和分配,但在分布式系统中,资源属于各节点,所以调度的灵活性不如集中式系统,资源的物理分布可能与用户请求的分布不匹配,某些资源可能空闲,而另一些资源可能超载。

5.安全保密性问题
开放性使得分布式系统中的许多软件接口都提供给用户,这样的开放式结构对于开发人员非常有价值,但同时也为破坏者打开了方便之门。针对分布式系统存在的上述难点,要保证一个分布式系统的正常运行,就必须对系统资源进行有效的管理,对计算机之间的通信、故障、安全等问题提供有效的处理手段和支持机制。用户对分布式系统的要求是透明性、安全性、灵活性、简单性、可靠性,也要求方便在局部失效时重构系统,以及集成不均匀子系统的能力。资源的分布性、缺乏全局状态信息及传输延迟,意味着集中式操作系统的某些方法和技术不能应用于分布式系统中。即使集中式系统中的某些技术满足上面的要求,其实现通常也是要付出很大代价的。


开源界的分布式系统协调

分布式是解决众多问题的一个主要手段,随着越来越多的分布式的服务,如何在分布式的系统中对这些服务做协调变成了一个很棘手的问题。今天我们就来看看使用开源分布式应用做服务做协调,在对分布式的应用做协调的时候,主要会碰到以下的应用场景:

业务发现service discovery:找到分布式系统中存在那些可用的服务和节点

名字服务name service:通过给定的名字知道到对应的资源

配置管理configuration management:如何在分布式的节点中共享配置文件,保证一致性

故障发现和故障转移failure detection and failover:当某一个节点出故障的时候,如何检测到并通知其它节点, 或者把想用的服务转移到其它的可用节点

领导选举leader election:如何在众多的节点中选举一个领导者,来协调所有的节点

分布式的锁distributed exclusive lock:如何通过锁在分布式的服务中进行同步

消息和通知服务message queue and notification:如何在分布式的服务中传递消息,以通知的形式对事件作出主动的响应

有许多的开源软件试图解决以上的全部或者部分问题,例如ZooKeeper,Consul,Etcd等等,现在就看看它们是如何做的。

Etcd

etcd是另一个用GO开发的分布式协调应用,它提供一个分布式的Key/Value存储来进行共享的配置管理和服务发现。它对节点的操作和ZooKeeper类似,不过etcd不支持ZooKeeper的ephemeral Node的概念,要监控服务的状态似乎比较麻烦。

Consul

Consul是用Go开发的分布式服务协调管理的工具,它提供了服务发现,健康检查,Key/Value存储等功能,并且支持跨数据中心的功能。Consul提供ZooKeeper类似的功能,它的基于HTTP的API可以方便的和各种语言进行绑定。与Zookeeper有所差异的是Consul通过基于Client/Server架构的Agent部署来支持跨Data Center的功能。



Consul在Cluster上的每一个节点都运行一个Agent,这个Agent可以使Server或者Client模式。Client负责到Server的高效通信,相对为无状态的。Server负责包括选举领导节点,维护cluster的状态,对所有的查询做响应,跨数据中心的通信等等。类似于Zookeeper,Consul支持对KV的增删查改的操作。服务发现Service Discovery和健康检查Health Check方面:Consul的另一个主要的功能是用于对分布式的服务做管理,用户可以注册一个服务,同时还提供对服务做健康检测的功能。当用户注册了一个服务后,就可以通过Consul来查询该服务,获得该服务的状态。

Consul支持三种Check的模式:
调用一个外部脚本Script,在该模式下,consul定时会调用一个外部脚本,通过脚本的返回内容获得对应服务的健康状态。

调用HTTP,在该模式下,consul定时会调用一个HTTP请求,返回2XX,则为健康;429 Too many request是警告。其它均为不健康

主动上报,在该模式下,服务需要主动调用一个consul提供的HTTP PUT请求,上报健康状态。

Consul的Health Check和Zookeeper的Failure Detection略有不同,ZooKeeper可以利用ephemeral Node来检测服务的状态,Consul的Health Check,通过调用脚本,HTTP或者主动上报的方式检查服务的状态,更为灵活,可以获得等多的信息,但是也需要做更多的工作。

故障检测(Failure Detection):Consul提供Session的概念,利用Session可以检查服务是否存活。对每一个服务我们都可以创建一个session对象,注意这里我们设置了ttl,consul会以ttl的数值为间隔时间,持续的对session的存活做检查。对应的在服务中,我们需要持续的renew,保证session是合法的。


ZooKeeper

ZooKeeper是使用最广泛,也是最有名的解决分布式服务的协调问题的开源软件了,它最早和Hadoop一起开发,后来成为了Apache的顶级项目,很多开源的项目都在使用ZooKeeper,例如大名鼎鼎的Kafka。Zookeeper本身是一个分布式的应用,通过对共享的数据的管理来实现对分布式应用的协调。使用一个树形目录作为数据模型,这个目录和文件目录类似,目录上的每一个节点被称作ZNodes。



ZooKeeper提供基本的API来操纵和控制Znodes,包括对节点的创建,删除,设置和获取数据,获得子节点等。除了这些基本的操作,ZooKeeper还提供了一些配方Recipe,其实就是一些常见的用例,例如锁,两阶段提交,领导选举等等。其本身是用Java开发的,所以对Java的支持是最自然的。它同时还提供了C语言的绑定。

通过对ZNode的操作,我们可以完成一些分布式服务协调的基本需求,包括名字服务,配置服务,分组等等。

故障检测(Failure Detection):在分布式系统中,一个最基本的需求就是当某一个服务出问题的时候,能够通知其它的节点或者某个管理节点。ZooKeeper提供ephemeral Node的概念,当创建该Node的服务退出或者异常中止的时候,该Node会被删除,所以我们就可以利用这种行为来监控服务运行状态。

ZooKeeper提供了监视Watch的功能,当节点的数据被修改的时候,监控的function会被调用。可以利用这一点进行配置文件的同步,发消息,或其他需要通知的功能,还计数,租约,队列等等。


分布式应用的个人概述小结

我们是否知道什么是分布式,分布式会遇到什么问题,有哪些理论支撑,有哪些经典的应对方案,业界是如何设计并保证分布式系统的高可用呢?

1. 架构设计

将从一些经典的开源系统架构设计出发,来看一下,如何设计一个高质量的分布式系统;而一般的设计出发点,无外乎:
冗余:简单理解为找个备胎,现任挂掉之后,备胎顶上
拆分:不能让一个人承担所有的重任,拆分下,每个人负担一部分,压力均摊

1.1 主备架构

给现有的服务搭建一个备用的服务,两者功能完全一致,区别在于平时只有主应用对外提供服务能力;而备应用则只需要保证与主应用能力一致,随时待机即可,并不用对外提供服务;当主应用出现故障之后,将备应用切换为主应用,原主应用下线;迅速的主备切换可以有效的缩短故障时间。

基于上面的描述,主备架构特点比较清晰
采用冗余的方案,加一台备用服务
缺点就是资源浪费

其次就是这个架构模型最需要考虑的则是如何实现主备切换:
人工
VIP (虚拟 ip) + keepalived 机制

1.2 主从架构

主从一般又叫做读写分离,主提供读写能力,而从则只提供读能力。鉴于当下的互联网应用,绝大多数都是读多写少的场景;读更容易成为性能瓶颈,所以采用读写分离,可以有效的提高整个集群的响应能力。主从架构可以区分为:一主多从 + 一主一从再多从,以 mysql 的主从架构模型为例进行说明:


MySql主从

主从模式的主要特点在于
添加从,源头依然是数据冗余的思想
读写分离:主负责读写,从只负责读,可以视为负载均衡策略
从需要向主同步数据,所若有的从都同步与主,对主的压力依然可能很大;所以就有了主从从的模式

关键问题则在于
主从延迟
主的写瓶颈
主挂之后如何选主

1.3 多主多从架构

一主多从面临单主节点的瓶颈问题,那就考虑多主多从的策略,同样是主负责提供读写,从提供读;但是这里有一个核心点在于多主之间的数据同步,如何保证数据的一致性是这个架构模型的重点。如 MySql 的双主双从可以说是一个典型的应用场景,在实际使用的时候除了上面的一致性之外,还需要考虑主键 id 冲突的问题。

1.4 普通集群模式

无主节点,集群中所有的应用职能对等,没有主次之分(当下绝大多数的业务服务都属于这种),一个请求可以被集群中任意一个服务响应;这种也可以叫做去中心化的设计模式,如 redis 的集群模式,eureka 注册中心,以可用性为首要目标。对于普通集群模式而言,重点需要考虑的点在于
资源竞争:如何确保一个资源在同一时刻只能被一个业务操作
如现在同时来了申请退款和货物出库的请求,如果不对这个订单进行加锁,两个请求同时响应,将会导致发货又退款了,导致财货两失

数据一致性:如何确保所有的实例数据都是一致的,或者最终是一致的
如应用服务使用 jvm 缓存,那么如何确保所有实例的 jvm 缓存一致
如 Eureka 的分区导致不同的分区的注册信息表不一致

1.5 数据分片架构

这个分片模型的描述可能并不准确,大家看的时候重点理解一下这个思想。前面几个的架构中,采用的是数据冗余的方式,即所有的实例都有一个全量的数据,而这里的数据分片,则从数据拆分的思路来处理,将全量的数据,通过一定规则拆分到多个系统中,每个系统包含部分的数据,减小单个节点的压力,主要用于解决数据量大的场景。比如 redis 的集群方式,通过 hash 槽的方式进行分区,如 es 的索引分片存储。

1.6 小结

这一节主要从架构设计层面对当前的分布式系统所采用的方案进行了一个简单的归类与小结,并不一定全面,欢迎各位大佬留言指正,基于冗余的思想:
主备
主从
多主多从
无中心集群

基于拆分的思想:
数据分片

对于拆分这一块,常说的分库分表也体现的是这一思想。

2. 理论基础

这一小节将介绍分布式系统中的经典理论,如广为流程的 CAP/BASE 理论,一致性理论基础 paxios,raft,信息交换的 Gossip 协议,两阶段、三阶段等。

2.1 CAP 定理

CAP 定理指出,分布式系统 不可能 同时提供下面三个要求:

Consistency:一致性
操作更新完成并返回客户端之后,所有节点数据完全一致
Availability:可用性
服务一直可用
Partition tolerance:分区容错性
分布式系统在遇到某节点或网络分区故障的时候,仍然能够对外提供满足一致性和可用性的服务

通常来讲 P 很难不保证,当服务部署到多台实例上时,节点异常、网络故障属于常态,根据不同业务场景进行选择

对于服务有限的应用而言,首选 AP,保证高可用,即使部分机器异常,也不会导致整个服务不可用;如绝大多数的前台应用都是这种

对于数据一致性要求高的场景,如涉及到钱的支付结算,CP 可能更重要了。对于 CAP 的三种组合说明如下
选择 说明
CA 放弃分区容错性,加强一致性和可用性,其实就是传统的单机场景
AP 放弃一致性(这里说的一致性是强一致性),追求分区容错性和可用性,这是很多分布式系统设计时的选择,例如很多NoSQL系统就是如此
CP 放弃可用性,追求一致性和分区容错性,基本不会选择,网络问题会直接让整个系统不可用


2.2 BASE 理论

Base 理论作为 cap 的延伸,其核心特点在于放弃强一致性,追求最终一致性

Basically Available: 基本可用
指分布式系统在出现故障的时候,允许损失部分可用性,即保证核心可用
如大促时降级策略

Soft State:软状态
允许系统存在中间状态,而该中间状态不会影响系统整体可用性
MySql 异步方式的主从同步,可能导致的主从数据不一致

Eventual Consistency:最终一致性
最终一致性是指系统中的所有数据副本经过一定时间后,最终能够达到一致的状态

基于上面的描述,可以看到 BASE 理论适用于大型高可用可扩展的分布式系统

注意其不同于 ACID 的强一致性模型,而是通过牺牲强一致性 来获得可用性,并允许数据在一段时间内是不一致的,但最终达到一致状态

2.3 PACELEC 定理

这个真没听说过,以下内容来自:
Distributed System Design Patterns | by Nishant | Medium

如果有一个分区('P'),分布式系统可以在可用性和一致性(即 'A' 和 'C')之间进行权衡;
否则('E'),当系统在没有分区的情况下正常运行时,系统可以在延迟('L')和一致性('C')之间进行权衡。


定理(PAC)的第一部分与 CAP 定理相同,ELC 是扩展。整个论点假设我们通过复制来保持高可用性。因此,当失败时,CAP 定理占上风。但如果没有,我们仍然必须考虑复制系统的一致性和延迟之间的权衡。

2.4 Paxos 共识算法

Paxos 算法解决的问题是分布式共识性问题,即一个分布式系统中的各个进程如何就某个值(决议)通过共识达成一致

基于上面这个描述,可以看出它非常适用于选举;其工作流程:一个或多个提议进程 (Proposer) 可以发起提案 (Proposal),Paxos 算法使所有提案中的某一个提案,在所有进程中达成一致。 系统中的多数派同时认可该提案,即达成了一致

角色划分:
Proposer: 提出提案 Proposal,包含编号 + value
Acceptor: 参与决策,回应 Proposers 的提案;当一个提案,被半数以上的 Acceptor 接受,则该提案被批准
每个 acceptor 只能批准一个提案
Learner: 不参与决策,获取最新的提案 value

2.5 Raft 算法

为了解决 paxos 的复杂性,raft 算法提供了一套更易理解的算法基础,其核心流程在于:
leader 接受请求,并转发给 follow,当大部分 follow 响应之后,leader 通知所有的 follow 提交请求、同时自己也提交请求并告诉调用方 ok

角色划分:
Leader:领导者,接受客户端请求,并向 Follower 同步请求,当数据同步到大多数节点上后告诉 Follower 提交日志
Follow: 接受并持久化 Leader 同步的数据,在 Leader 告之日志可以提交之后,提交
Candidate:Leader 选举过程中的临时角色,向其他节点拉选票,得到多数的晋升为 leader,选举完成之后不存在这个角色


raft共识流程

2.6 ZAB 协议

ZAB (Zookeeper Atomic Broadcast) 协议是为分布式协调服务 ZooKeeper 专门设计的一种支持崩溃恢复的一致性协议,基于该协议,ZooKeeper 实现了一种 主从模式的系统架构来保持集群中各个副本之间的数据一致性。

主要用于 zk 的数据一致性场景,其核心思想是 Leader 再接受到事务请求之后,通过给 Follower,当半数以上的 Follower 返回 ACK 之后,Leader 提交提案,并向 Follower 发送 commit 信息

角色划分

Leader: 负责整个 Zookeeper 集群工作机制中的核心
事务请求的唯一调度和处理者,保证集群事务处理的顺序性
集群内部各服务器的调度者
Follower:Leader 的追随者
处理客户端的非实物请求,转发事务请求给 Leader 服务器
参与事务请求 Proposal 的投票
参与 Leader 选举投票
Observer:是 zookeeper 自 3.3.0 开始引入的一个角色,
它不参与事务请求 Proposal 的投票,
也不参与 Leader 选举投票
只提供非事务的服务(查询),通常在不影响集群事务处理能力的前提下提升集群的非事务处理能力。


ZAB消息广播

2.7 2PC 协议

two-phase commit protocol,两阶段提交协议,主要是为了解决强一致性,中心化的强一致性协议

角色划分:
协调节点 (coordinator):中心化
参与者节点 (partcipant):多个

执行流程:协调节点接收请求,然后向参与者节点提交 precommit,当所有的参与者都回复 ok 之后,协调节点再给所有的参与者节点提交 commit,所有的都返回 ok 之后,才表明这个数据确认提交。当第一个阶段,有一个参与者失败,则所有的参与者节点都回滚。


2pc流程

特点
优点在于实现简单
缺点也很明显
协调节点的单点故障
第一阶段全部 ack 正常,第二阶段存在部分参与者节点异常时,可能出现不一致问题

2.8 3PC 协议

在两阶段的基础上进行扩展,将第一阶段划分两部,cancommit + precommit,第三阶段则为 docommit

第一阶段 cancommit
该阶段协调者会去询问各个参与者是否能够正常执行事务,参与者根据自身情况回复一个预估值,相对于真正的执行事务,这个过程是轻量的

第二阶段 precommit
本阶段协调者会根据第一阶段的询盘结果采取相应操作,若所有参与者都返回 ok,则协调者向参与者提交事务执行 (单不提交) 通知;否则通知参与者 abort 回滚

第三阶段 docommit
如果第二阶段事务未中断,那么本阶段协调者将会依据事务执行返回的结果来决定提交或回滚事务,若所有参与者正常执行,则提交;否则协调者 + 参与者回滚

在本阶段如果因为协调者或网络问题,导致参与者迟迟不能收到来自协调者的 commit 或 rollback 请求,那么参与者将不会如两阶段提交中那样陷入阻塞,而是等待超时后继续 commit,相对于两阶段提交虽然降低了同步阻塞,但仍然无法完全避免数据的不一致

特点

降低了阻塞与单点故障:
参与者返回 CanCommit 请求的响应后,等待第二阶段指令,若等待超时 / 协调者宕机,则自动 abort,降低了阻塞;
参与者返回 PreCommit 请求的响应后,等待第三阶段指令,若等待超时 / 协调者宕机,则自动 commit 事务,也降低了阻塞;

数据不一致问题依然存在
比如第三阶段协调者发出了 abort 请求,然后有些参与者没有收到 abort,那么就会自动 commit,造成数据不一致

2.9 Gossip 协议

Gossip 协议,顾名思义,就像流言蜚语一样,利用一种随机、带有传染性的方式,将信息传播到整个网络中,并在一定时间内,使得系统内的所有节点数据一致。Gossip 协议通过上面的特性,可以保证系统能在极端情况下(比如集群中只有一个节点在运行)也能运行。主要用在分布式数据库系统中各个副本节点同步数据之用,这种场景的一个最大特点就是组成的网络的节点都是对等节点,是非结构化网络。

工作流程

周期性的传播消息,通常周期时间为 1s
被感染的节点,随机选择 n 个相邻节点,传播消息
每次传播消息都选择还没有发送过的节点进行传播
收单消息的节点,不会传播给向它发送消息的节点


Gossip传播示意图

特点

扩展性:允许节点动态增加、减少,新增的节点状态最终会与其他节点一致
容错:网络中任意一个节点宕机重启都不会影响消息传播
去中心化:不要求中心节点,所有节点对等,任何一个节点无需知道整个网络状况,只要网络连通,则一个节点的消息最终会散播到整个网络
一致性收敛:协议中的消息会以一传十、十传百一样的指数级速度在网络中快速传播,因此系统状态的不一致可以在很快的时间内收敛到一致。消息传播速度达到了 logN
简单:Gossip 协议的过程极其简单,实现起来几乎没有太多复杂性

缺点

消息延迟:节点只会随机向少数几个节点发送消息,消息最终是通过多个轮次的散播而到达全网的,因此使用 Gossip 协议会造成不可避免的消息延迟
消息冗余:节点会定期随机选择周围节点发送消息,而收到消息的节点也会重复该步骤,导致消息的冗余

2.10 小结

本节主要介绍的是分布式系统设计中的一些常见的理论基石,如分布式中如何保障一致性,如何对一个提案达成共识:
BASE,CAP,PACELEC 理论:构建稳定的分布式系统应该考虑的方向
paxos,raft 共识算法
zab 一致性协议
gossip 消息同步协议

3. 算法

这一节将主要介绍下分布式系统中的经典的算法,比如常用于分区的一致性 hash 算法,适用于一致性的 Quorum NWR 算法,PBFT 拜占庭容错算法,区块链中大量使用的工作量证明 PoW 算法等。

3.1 一致性 hash 算法

一致性 hash 算法,主要应用于数据分片场景下,有效降低服务的新增、删除对数据复制的影响。通过对数据项的键进行哈希处理映射其在环上的位置,然后顺时针遍历环以查找位置大于该项位置的第一个节点,将每个由键标识的数据分配给 hash 环中的一个节点

一致性hash算法

一致散列的主要优点是增量稳定性;节点添加删除,对整个集群而言,仅影响其直接邻居,其他节点不受影响。

注意:redis 集群实现了一套 hash 槽机制,其核心思想与一致性 hash 比较相似

3.2 Quorum NWR 算法

用来保证数据冗余和最终一致性的投票算法,其主要数学思想来源于鸽巢原理

N 表示副本数,又叫做复制因子(Replication Factor)。也就是说,N 表示集群中同一份数据有多少个副本
W,又称写一致性级别(Write Consistency Level),表示成功完成 W 个副本更新写入,才会视为本次写操作成功
R 又称读一致性级别(Read Consistency Level),表示读取一个数据对象时需要读 R 个副本,才会视为本次读操作成功

Quorum NWR 算法要求每个数据拷贝对象 都可以投 1 票,而每一个操作的执行则需要获取最小的读票数,写票数;通常来讲写票数 W 一般需要超过 N/2,即我们通常说的得到半数以上的票才表示数据写入成功。

事实上当 W=N、R=1 时,即所谓的 WARO (Write All Read One)。就是 CAP 理论中 CP 模型的场景。

3.3 PBFT 拜占庭算法

拜占庭算法主要针对的是分布式场景下无响应,或者响应不可信的情况下的容错问题,其核心分三段流程,如下:


拜占庭算法

假设集群节点数为 N,f 个故障节点 (无响应) 和 f 个问题节点 (无响应或错误响应),f+1 个正常节点,即 3f+1=n。

客户端向主节点发起请求,主节点接受请求之后,向其他节点广播 pre-prepare 消息;
节点接受 pre-prepare 消息之后,若同意请求,则向其他节点广播 prepare 消息;
当一个节点接受到 2f+1 个 prepare 新消息,则进入 commit 阶段,并广播 commit 消息;
当收到 2f+1 个 commit 消息后(包括自己),代表大多数节点已经进入 commit 阶段,这一阶段已经达成共识,于是节点就会执行请求,写入数据。

相比 Raft 算法完全不适应有人作恶的场景,PBFT 算法能容忍 (n 1)/3 个恶意节点 (也可以是故障节点)。另外,相比 PoW 算法,PBFT 的优点是不消耗算 力。PBFT 算法是 O (n ^ 2) 的消息复杂度的算法,所以以及随着消息数的增加,网络时延对系统运行的影响也会越大,这些都限制了运行 PBFT 算法的分布式系统 的规模,也决定了 PBFT 算法适用于中小型分布式系统。

3.4 PoW 算法

工作量证明 (Proof Of Work,简称 PoW),同样应用于分布式下的一致性场景,区别于前面的 raft, pbft, paxos 采用投票机制达成共识方案,pow 采用工作量证明。

客户端需要做一定难度的工作才能得出一个结果,验证方却很容易通过结果来检查出客户端是不是做了相应的工作,通过消耗一定工作浪,增加消息伪造的成本,PoW 以区块链中广泛应用而广为人知,下面以区块链来简单说一下 PoW 的算法应用场景。

以 BTC 的转账为例,A 转 n 个 btc 给 B,如何保证不会同时将这 n 个币转给 C:
A 转账给 B,交易信息记录在一个区块 1 中
A 转账给 C,交易信息被记录在另一个区块 2 中
当区块 1 被矿工成功提交到链上,并被大多数认可(通过校验区块链上的 hash 值验证是否准确,而这个 hash 值体现的是矿工的工作量),此时尚未提交的区块 2 则会被抛弃
若区块 1 被提交,区块 2 也被提交,各自有部分人认可,就会导致分叉,区块链中采用的是优选最长的链作为主链,丢弃分叉的部分

PoW 的算法,主要应用在上面的区块提交验证,通过 hash 值计算来消耗算力,以此证明矿工确实有付出,得到多数认可的可以达成共识。

3.5 小结

本节主要介绍了下当前分布式下常见的算法:
分区的一致性 hash 算法:基于 hash 环,减少节点动态增加减少对整个集群的影响;适用于数据分片的场景
适用于一致性的 Quorum NWR 算法:投票算法,定义如何就一个提案达成共识
PBFT 拜占庭容错算法:适用于集群中节点故障、或者不可信的场景
区块链中大量使用的工作量证明 PoW 算法:通过工作量证明,认可节点的提交

4. 技术思想

这一节的内容相对前面几个而言,并不太容易进行清晰的分类;主要包含一些高质量的分布式系统的实践中,值得推荐的设计思想、技术细节。

4.1 CQRS

Command Query Responsibility Segregation 即我们通俗理解的读写分离,其核心思想在于将两类不同操作进行分离,在独立的服务中实现。


cqrs

用途在于将领域模型与查询功能进行分离,让一些复杂的查询摆脱领域模型的限制,以更为简单的 DTO 形式展现查询结果。同时分离了不同的数据存储结构,让开发者按照查询的功能与要求更加自由的选择数据存储引擎。

4.2 复制负载平衡服务

复制负载平衡服务 (Replication Load Balancing Service, RLBS),可以简单理解为我们常说的负载均衡,多个相同的服务实例构建一个集群,每个服务都可以响应请求,负载均衡器负责请求的分发到不同的实例上,常见的负载算法如下:
算法 说明 特点
轮询 请求按照顺序依次分发给对应的服务器 优点简单,缺点在于未考虑不同服务器的实际性能情况
加权轮询 权重高的被分发更多的请求 优点:充分利用机器的性能
最少连接数 找连接数最少的服务器进行请求分发,若所有服务器相同的连接数,则找第一个选择的 目的是让优先让空闲的机器响应请求
少连接数慢启动时间 刚启动的服务器,在一个时间段内,连接数是有限制且缓慢增加 避免刚上线导致大量的请求分发过来而超载
加权最少连接 平衡服务性能 + 最少连接数  
基于代理的自适应负载均衡 载主机包含一个自适用逻辑用来定时监测服务器状态和该服务器的权重  
源地址哈希法 获取客户端的IP地址,通过哈希函映射到对应的服务器 相同的来源请求都转发到相同的服务器上
随机 随机算法选择一台服务器  
固定权重 最高权重只有在其他服务器的权重值都很低时才使用。然而,如果最高权重的服务器下降,则下一个最高优先级的服务器将为客户端服务 每个真实服务器的权重需要基于服务器优先级来配置
加权响应 服务器响应越小其权重越高,通常是基于心跳来判断机器的快慢 心跳的响应并不一定非常准确反应服务情况


4.3 心跳机制

在分布式环境里中,如何判断一个服务是否存活,当下最常见的方案就是心跳

比如 raft 算法中的 leader 向所有的 follow 发送心跳,表示自己还健在,避免发生新的选举;

比如 redis 的哨兵机制,也是通过 ping/pong 的心跳来判断节点是否下线,是否需要选新的主节点;

再比如我们日常的业务应用得健康监测,判断服务是否正常。

4.4 租约机制

租约就像一个锁,但即使客户端离开,它也能工作。客户端请求有限期限的租约,之后租约到期。如果客户端想要延长租约,它可以在租约到期之前续订租约。租约主要是了避免一个资源长久被某个对象持有,一旦对方挂了且不会主动释放的问题;在实际的场景中,有两个典型的应用。

case1 分布式锁

业务获取的分布式锁一般都有一个有效期,若有效期内没有主动释放,这个锁依然会被释放掉,其他业务也可以抢占到这把锁;因此对于持有锁的业务方而言,若发现在到期前,业务逻辑还没有处理完,则可以续约,让自己继续持有这把锁。典型的实现方式是 redisson 的看门狗机制

case2 raft 算法的任期

在 raft 算法中,每个 leader 都有一个任期,任期过后会重新选举,而 Leader 为了避免重新选举,一般会定时发送心跳到 Follower 进行续约。

4.5 Leader & Follow

这个比较好理解,上面很多系统都采用了这种方案,特别是在共识算法中,由领导者负责代表整个集群做出决策,并将决策传播到所有其他服务器;领导者选举在服务器启动时进行。每个服务器在启动时都会启动领导者选举,并尝试选举领导者。除非选出领导者,否则系统不接受任何客户端请求

4.6 Fencing

在领导者 - 追随者模式中,当领导者失败时,不可能确定领导者已停止工作,如慢速网络或网络分区可能会触发新的领导者选举,即使前一个领导者仍在运行并认为它仍然是活动的领导者。Fencint 是指在以前处于活动状态的领导者周围设置围栏,使其无法访问集群资源,从而停止为任何读/写请求提供服务:
资源屏蔽:系统会阻止以前处于活动状态的领导者访问执行基本任务所需的资源。
节点屏蔽:系统会阻止以前处于活动状态的领导者访问所有资源。执行此操作的常见方法是关闭节点电源或重置节点。

4.7 Quorum 法定人数

法定人数,常见于选举、共识算法中,当超过 Quorum 的节点数确认之后,才表示这个提案通过 (数据更新成功),通常这个法定人数为 = 半数节点 + 1。

4.8 High-Water mark 高水位线

高水位线,跟踪 Leader(领导者)上的最后一个日志条目,且该条目已成功复制到 > quorum(法定人数)的 Follow(跟谁者),即表示这个日志被整个集群接受。

日志中此条目的索引称为高水位线索引。领导者仅公开到高水位线索引的数据。如 Kafka:为了处理非可重复读取并确保数据一致性,Kafka broker 会跟踪高水位线,这是特定分区的最大偏移量。使用者只能看到高水位线之前的消息。

4.9 Phi 累计故障检测

Phi Accrual Failure Detection, 使用历史检测信号信息使阈值自适应。

通用的应计故障检测器不会判断服务器是否处于活动状态,而是输出有关服务器的可疑级别。

如 Cassandra(Facebook 开源的分布式 NoSql 数据库)使用 Phi 应计故障检测器算法来确定群集中节点的状态。

4.10 Write-ahead Log 预写日志

预写日志记录是解决操作系统中文件系统不一致的问题的高级解决方案,当我们提交写到操作系统的文件缓存,此时业务会认为已经提交成功;但是在文件缓存与实际写盘之间会有一个时间差,若此时机器宕机,会导致缓存中的数据丢失,从而导致完整性缺失。为了解决这个问题,如 mysql,es 等都采用了预写日志的机制来避免这个问题。

MySql:事务提交的流程中,先写 redolog precommit, 然后写 binlog,最后再 redolog commit;当 redolog 记录成功之后,才表示事务执行成功;因此当出现上面的宕机恢复时,则会加载 redologo,然后重放对应的命令,来恢复未持久化的数据

ElasticSearch:在内存中数据生成段写到操作系统文件缓存前,会先写事务日志,出现异常时,也是从事务日志进行恢复。

4.11 分段日志

将日志拆分为多个较小的文件,而不是单个大文件,以便于操作。

单个日志文件在启动时读取时可能会增长并成为性能瓶颈。较旧的日志会定期清理,并且很难对单个大文件执行清理操作。

单个日志拆分为多个段。日志文件在指定的大小限制后滚动。使用日志分段,需要有一种将逻辑日志偏移量(或日志序列号)映射到日志段文件的简单方法。

这个其实也非常常见,比如我们实际业务应用配置的 log,一般都是按天、固定大小进行拆分,并不会把所有的日志都放在一个日志文件中

再比如 es 的分段存储,一个段就是一个小的存储文件。

4.12 checksum 校验

在分布式系统中,在组件之间移动数据时,从节点获取的数据可能会损坏。

计算校验和并将其与数据一起存储。

要计算校验和,请使用 MD5、SHA-1、SHA-256 或 SHA-512 等加密哈希函数。哈希函数获取输入数据并生成固定长度的字符串(包含字母和数字); 此字符串称为校验和。

当系统存储某些数据时,它会计算数据的校验和,并将校验和与数据一起存储。当客户端检索数据时,它会验证从服务器接收的数据是否与存储的校验和匹配。如果没有,则客户端可以选择从另一个副本检索该数据。

HDFS 和 Chubby 将每个文件的校验和与数据一起存储。

4.13 小结

这一节很多内容来自下面这篇博文,推荐有兴趣的小伙伴查看原文:Distributed System Design Patterns | by Nishant | Medium

5. 分布式系统解决方案

最后再介绍一些常见的分布式业务场景及对应的解决方案,比如全局唯一的递增 ID - 雪花算法,分布式系统的资源抢占 - 分布式锁,分布式事务 - 2pc/3pc/tcc ,分布式缓存等。

5.1 缓存

缓存实际上并不是分布式独有的,这里把它加进来,主要是因为实在是应用得太广了,无论是应用服务、基础软件工具还是操作系统,大量都可以见到缓存的身影。缓存的核心思想在于: 借助更高效的 IO 方式,来替代代价昂贵的 IO 方式。如:
redis 的性能高于 mysql
如内存的读写,远高于磁盘 IO,文件 IO
磁盘顺序读写 > 随机读写

用好缓存可以有效提高应用性能,下面以一个普通的 java 前台应用为例说明:
JVM 缓存 -> 分布式缓存 (redis/memcache) -> mysql 缓存 -> 操作系统文件缓存 -> 磁盘文件

缓存面临的核心问题,则在于:
一致性问题:缓存与 db 的一致性如何保障(相信大家都听说过或者实际处理过这种问题)
数据完整性:比如常见的先写缓存,异步刷新到磁盘,那么缓存到磁盘刷新这段时间内,若宕机导致数据丢失怎么办?
TIP: 上面这个问题可以参考 mysql 的 redolog

5.2 全局唯一 ID

在传统的单体架构中,业务 id 基本上是依赖于数据库的自增 id 来处理;当我们进入分布式场景时,如我们常说的分库分表时,就需要我们来考虑如何实现全局唯一的业务 id 了,避免出现在分表中出现冲突。全局唯一 ID 解决方案:
uuid
数据库自增 id 表
redis 原子自增命令
雪花算法 (原生的,扩展的百度 UidGenerator、美团 Leaf 等)
Mist 薄雾算法

5.3 分布式锁

常用于分布式系统中资源控制,只有持有锁的才能继续操作,确保同一时刻只会有一个实例访问这个资源。常见的分布式锁有:
基于数据库实现分布式锁
Redis 实现分布式锁(应用篇) | 一灰灰 Learning
从 0 到 1 实现一个分布式锁 | 一灰灰 Learning
etcd 实现分布式锁
基于 consul 实现分布式锁

5.4 分布式事务

事务表示一组操作,要么全部成功,要么全部不成功;单机事务通常说的是数据库的事务;而分布式事务,则可以简单理解为多个数据库的操作,要么同时成功,要么全部不成功。更确切一点的说法,分布式事务主要是要求事务的参与方,可能涉及到多个系统、多个数据资源,要求它们的操作要么都成功,要么都回滚;一个简单的例子描述下分布式事务场景:

下单扣库存
用户下单,付钱
此时订单服务,会生成订单信息
支付网关,会记录付款信息,成功 or 失败
库存服务,扣减对应的库存

一个下单支付操作,涉及到三个系统,而分布式事务则是要求,若支付成功,则上面三个系统都应该更新成功;若有一个操作失败,如支付失败,则已经扣了库存的要回滚(还库存),生成的订单信息回滚(删掉 -- 注:现实中并不会去删除订单信息,这里只是用于说明分布式事务,请勿带入实际的实现方案)。

分布式事务实现方案:
2PC: 前面说的两阶段提交,就是实现分布式事务的一个经典解决方案
3PC: 三阶段提交
TCC:补偿事务,简单理解为应用层面的 2PC
SAGA 事务
本地消息表
MQ 事务方案

5.5 分布式任务

分布式任务相比于我们常说单机的定时任务而言,可以简单的理解为多台实例上的定时任务,从应用场景来说,可以区分两种:
互斥性的分布式任务
即同一时刻,集群内只能有一个实例执行这个任务

并存式的分布式任务
同一时刻,所有的实例都可以执行这个任务
续考虑如何避免多个任务操作相同的资源

分布式任务实现方案:
Quartz Cluster
XXL-Job
Elastic-Job
自研:
资源分片策略
分布式锁控制的唯一任务执行策略

5.6 分布式 Session

Session 一般叫做会话,Session 技术是 http 状态保持在服务端的解决方案,它是通过服务器来保持状态的。我们可以把客户端浏览器与服务器之间一系列交互的动作称为一个 Session。是服务器端为客户端所开辟的存储空间,在其中保存的信息就是用于保持状态。因此,session 是解决 http 协议无状态问题的服务端解决方案,它能让客户端和服务端一系列交互动作变成一个完整的事务。

单机基于 session/cookie 来实现用户认证,那么在分布式系统的多实例之间,如何验证用户身份呢?这个就是我们说的分布式 session,其实现方案:
session stick:客户端每次请求都转发到同一台服务器 (如基于 ip 的 hash 路由转发策略)
session 复制: session 生成之后,主动同步给其他服务器
session 集中保存:用户信息统一存储,每次需要时统一从这里取 (也就是常说的 redis 实现分布式 session 方案)
cookie: 使用客户端 cookie 存储 session 数据,每次请求时携带这个

5.7 分布式链路追踪

分布式链路追踪也可以叫做全链路追中,而它可以说是每个开发者的福音,通常指的是一次前端的请求,将这个请求过程中,所有涉及到的系统、链路都串联起来,可以清晰的知道这一次请求中,调用了哪些服务,有哪些 IO 交互,瓶颈点在哪里,什么地方抛出了异常。当前主流的全链路方案大多是基于 google 的 Dapper 论文实现的全链路实现方案:
zipkin
pinpoint
SkyWalking
CAT
jaeger

5.8 布隆过滤器

Bloom 过滤器是一种节省空间的概率数据结构,用于测试元素是否为某集合的成员。

布隆过滤器由一个长度为 m 比特的位数组(bit array)与 k 个哈希函数(hash function)组成的数据结构。

原理是当一个元素被加入集合时,通过 K 个散列函数将这个元素映射成一个位数组中的 K 个点,把它们置为 1。

检索时,我们只要看看这些点是不是都是 1 就大约知道集合中有没有它了,也就是说,如果这些点有任何一个 0 ,则被检元素一定不在;如果都是 1 ,则被检元素很可能在。关于布隆过滤器,请牢记一点:

判定命中的,不一定真的命中
判定没有命中的,则一定不在里面


布隆过滤器

常见的应用场景,如:
防止缓存穿透
爬虫时重复检测

5.9 小结

分布式系统的解决方案当然不局限于上面几种,比如分布式存储、分布式计算等也属于常见的场景,当然在我们实际的业务支持过程中,不太可能需要让我们自己来支撑这种大活;而上面提到的几个点,基本上或多或少会与我们日常工作相关,这里列出来当然是好为了后续的详情做铺垫。

6. 总结

6.1 综述

这是一篇概括性的综述类文章,可能并没有很多的干货,当然也限于个人的能力,上面的总结可能并不准确,如有发现,请不吝赐教。全文总结如下:

常见的分布式架构设计方案:主备,主从,多主多从,普通无中心集群,数据分片架构。

分布式系统中的理论基石:
CAP, BASE, PACELEC
共识算法:paxos, raft, zab
一致性协议:2pc, 3pc
数据同步:gossip

分布式系统中的算法:
分区的一致性 hash 算法:基于 hash 环,减少节点动态增加减少对整个集群的影响;适用于数据分片的场景
适用于一致性的 Quorum NWR 算法:投票算法,定义如何就一个提案达成共识
PBFT 拜占庭容错算法:适用于集群中节点故障、或者不可信的场景
区块链中大量使用的工作量证明 PoW 算法:通过工作量证明,认可节点的提交

分布式系统解决方案:
分布式缓存
全局唯一 ID
分布式锁
分布式事务
分布式任务
分布式会话
分布式链路追踪
布隆过滤器