1. 分布式系统概论
分布式系统是一个内涵极度丰富的领域,单就应用层次而言就涉及分布式缓存、分布式存储、分布式文件系统、分布式锁、分布式事务、分布式调度任务、分布式调度计算、分布式消息、分布式采集等等
2. 分布式系统概念
2.1 进程、线程
进程是具有一定独立功能的程序关于某个数据集合上的一次运行活动,进程是进行资源分配和调度的一个独立单位。线程是进程的一个实体,是CPU调度和分派的基本单位,它是比进程更小的能独立运行的基本单位。线程基本上不拥有系统资源,只拥有一点在运行中必不可少的资源,如程序计数器,一组寄存器和栈。但是它可以与同属一个进程的其他线程共享进程所拥有的的全部资源
2.2 并发
并发: 当有多个线程在操作时,如果系统只有一个CPU,则它只能把CPU运行时间划分成若干个时间段,再将时间段分配给各个线程执行。在一个时间段的线程代码运行时,其他线程处于挂起状态
应用层的并发概念可以广推之,定义为单位时间内对于共享资源的访问。
2.3 锁
锁:用于保护临界区(操作系统:每个进程中,访问临界资源的那段代码被称作临界区)的一种机制
减少或规避锁争用的集中策略
- 分拆锁
- 分段锁
- 避免共享变量缓存
- 使用并发容器
- 使用Immutable数据和ThreadLocal中的数据
分离锁和分拆锁
分拆锁: 如果一个锁守护多个相互独立的变量,则可以通过分拆锁,使每一个锁守护不同的变量,从而改进可伸缩性
分离锁: 分拆锁的基础上,有时候可以被扩展,分成若干个加锁块的集合,并且它们归属于相互独立的对象,这样的情况就是分离锁。例如ConcurrentHashMap的分段锁实现
2.4 并行
当系统有一个以上的CPU时,则下半场各部分的才做有可能非并发。当一个CPU执行一个线程时,另一个CPU可以执行另一个线程,两个线程互不抢占CPU资源,可以同时进行,这种方式即为并行
2.5 集群
集群是一组相互独立的,通过高速网络互联的计算机,它们构成了一个组,并以单一系统的模式加以管理。一个客户与集群相互作用时,集群更像是一个独立的服务器。集群配置使用与提高可用性和可伸缩性。其次,分布式系统可以表达为很多机器组成的集群,靠彼此之间的网络通信,担当的角色可能不同,共同完成一件事情的系统。
可以划分为以下几种类型:
- 节点: 系统中按照协议完成计算工作的一个逻辑实体,可能是执行某些工作的进程或机器
- 网络: 系统的数据传输通道,用来彼此通信,通信是具有方向性的
- 存储: 系统中持久化数据的数据库或者文件存储
根据典型的集群体系结构,及群众涉及的关键技术可以归属于四个层次:
- 网络层: 网络互联结构,通信协议,信号技术等
- 节点机及操作系统层高性能客户机,分层或基于微内核的操作系统
- 集群系统管理层:资源管理、资源调度、负载平衡、并行IPO、安全等
- 应用层:并行程序开发环境、串行应用、并行应用等
2.6 状态特性
大部分应用中都提倡服务无状态,分布式环境中的任何节点也是无状态的。无状态是指不保存存储状态,则可以随意重启和替代,便于做扩展。比如负载均衡服务器Nginx是无状态的,应用服务绝大部分也是无状态的,在高压力访问下,撑不住了就加一些机器,很容易扩展
2.7 系统重发与幂等性
网络重发例子:用户访问一个应用,该应用需要调一个远程服务,如果app1访问service B 的链路出现网络异常,用户得到操作失败的反馈,为了减少失败率,httpClient的设计一般增加重发(retry)的机制
因此,我们还需要考虑系统幂等性设计
2.8 硬件异常
硬件异常就是硬件出现了问题,从而导致运行程序部分不可用或者全部不可用
(1) 服务器宕机
分布式环境下,采用低廉的PC Server代替高大上的服务器已是常态。我们把宕机时不能提供服务的节点称为不可用。服务器宕机时,节点将丢失所有内部信息,因此设计时需要考虑存储系统的持久化,在重启后,可以进行相关存储内容的恢复
(2) 网络异常
网络异常可能原因是消息丢失、网络报数据错误。设计容错系统的一个方案是,任何消息只有收到对方的回复才算发送成功
(3) 磁盘故障
磁盘故障是高概率事件。一般故障分为软件故障和硬件故障。而硬件故障又可以分为系统引起的,如电源不稳定,主板的IDE接口松动,硬件不兼容等。另一种就是磁盘损坏,磁盘损坏时,数据将丢失,虽然有一些专业的恢复策略,但是可靠性无法保障。因此,分布式环境中,需要把数据存储在多台服务器,一旦一台出现故障,也能从其他服务器恢复
(4) 机房级异常
当机房级异常如光纤出了问题,异地机房可以继续提供服务
3. 分布式系统理论
3.1 CAP理论
一致性(C): 在分布式系统中的所有数据备份,在同一时刻是否同样的值。(等同于所有节点访问同一份最新的数据副本)
可用性(A): 在集群中一部分节点故障后,集群整体是否还能相应客户端的读写请求。(对数据更新具备高可用性)
分区容忍性(P): 以实际效果而言,分区相当于对通信的时限要求。系统如果不能在一定时限内达成数据一致性,就意味着发生了分区的情况,必须就当前操作在 C 和 A 之间做出选择
高可用、数据一致性是很多系统设计的目标,但是分区又是不可避免的事情
,由此引出了一下几种选择:
(1)CA without P
如果不要求 P (不允许分区),则强一致性(C)和可用性(A)是可以保证的。但其实分区是肯定会存在的,因此CA的系统更多的是允许分期后各子系统依然保持CA,典型放弃分区容忍性的例子有 关系型数据库, LDAP等
(2)CP without A
如果不要求可用性(A),相当于每个请求都需要在Server之间强一致,而分区(P)会导致同步时间无限延长,如此CP也是可以保证的。很多传统的分布式事务都属于这种模式,分布式锁也是这种情况
(3)AP without C
要高可用并允许分区,则需要放弃一致性。一旦分区发生,节点之间可能会失去联系,为了高可用,每个节点只能用本地数据提供服务,而这样导致全局数据的不予执行。现在众多的NoSQL,DNS、Web缓存都属于此类
3.2 最终一致性
一种业内笔者较为认可的解释:
(1)给定足够长的一段时间,不再发送更新,则认为所有更新最终会传播到整个系统,且所有副本都会达到一致(也可以广泛理解为某一次更新最终会落实到每个节点副本)
(2)当存在持续更新时,一个被接受的更新要么到达副本,要么在到达副本的路上,比如网络闪断,有重试机制;为避免持续压力,可加大重试时间;超过重试次数,则引入手工决策或者第二套方案处理
3.3 Paxos
-
Paxos协议是一个解决分布式系统中,多个节点之间就某个值(提案)达成一致(协议)的通信协议。它能够处理在少数节点离线的情况下,剩余的多数节点仍能够达成一致
-
Paxos协议是一个两阶段协议,分为 Prepare 阶段和 Accept 阶段。涉及两个参与者角色,Proposer 和 Acceptor。其中Proposer是提议提案的服务器,而Acceptor是批准提案的服务器。二者在物理上可以是同一个服务器
3.4 2阶段提交协议(2PC)
在事务处理、关系型数据库及计算机网络中,2阶段提交协议(2PC)是一种典型的原子提交协议。它是一种由协调器来处理分布式原子参与者是提交或回滚事务的分布式算法。
该协议包括两个阶段:
(1)提交请求阶段或者叫投票阶段
该阶段的任务是确定相关参与者对事务处理是否准备就绪,Yes代表可以commit,No则反之
(2)提交阶段
基于投票结果,由协调器决定提交事务抑或是退出事务处理;各事务参与者遵循指示,对本地事务资源做需要的动作
协调器用coordinator表示,cohot1、cohot2分别表示事务参与者1、2
1. commit request phase(请求提交阶段)
提交请求阶段,cohot1执行 prepare(事务准备)动作,并返回给协调器,cohot2也是如此。如果均返回Yes,则进入下一个阶段:commit phase(提交阶段)。若果有一个事务参与者返回NO,则协调器决策不进入commit phase阶段
2. commit phase(提交阶段)
协调器向cohort1发出提交指令(commit),cohort1执行提交兵法确认信息给协调器;cohort2也是如此。如果某一个参与者comit失败/超时,则通知协调器,发起回滚(rollback)
2PC最大的不足是提交协议是阻塞协议,如果事务协调器宕机,某些参与者将无法解决他们的事务:一个参与者发送确认消息给协调器,因协调器无法工作而导致事务未处理完而处于悬挂状态
因此,在高并发网站中使用分布式事务的2PC协议要把握如下原则:
(1)能不用2PC的尽量不用,综上所述可以发现,2PC协议要有提交请求阶段、提交阶段,而每个阶段也有协调器分别与多个事务参与者的应答,复杂度高,性能也受到挑战。
(2)要获得事务强一致性,也要在性能和一致性上做折中,比如加上超时机制,阶段性补偿机制等。
3.5 3PC
3PC分为3次交互。第一阶段是投票,事务协调器询问参与者是否能提交(canCommit),都得到肯定回答后,继续第二阶段。第二阶段是预提交,都确认预提交后,进行第三阶段。第三阶段就是真实的提交,成功则完成事务;失败则继续重试。
3PC是在2PC的基础上增加了一次交互,也就是preCommit(又称预提交) 。只要预提交都成功了,则一定要保证doCommit提交成功,即使协调器在下一阶段不可用,或者调用超时。这是协议的基本思想,在工业环境中,一般是通过重试补偿的策略来保证doCommit提交成功的
3.6 Raft
Raft 和 Paxos 算法具有相同的功能和性能,但是它的算法结构和Paxos不同。为了提升可理解性,Raft将一致性算法分解成几个关键模块,例如领导人选举、日志复制和安全性。同事它通过实施一个更强的一致性来减少需要考虑的状态的数量。Raft算法还包括一个新的机制来允许集群成员的动态改变,利用重叠的大多数来保证安全性
Paxos和Raft都是为了实现一致性这个目标,这个过程如同选举一样,参选者需要说服大多数选民(服务器)投票给他,一旦选定后就跟随其操作。Paxos和Raft的区别在于选举的具体过程不同
在Raft中,任何时候一个服务器可以扮演下面角色之一:
领导者: 处理所有客户端交互、日支付至等动作,一般一次只有一个领导者
选民: 类似选民,完全被动的角色,这样的服务器等待被通知投票
候选人: 候选人就是在选举过程中提名自己的实体,一旦选举成功,则成为领导者
Raft算法分为两个阶段,首先是选举过程,然后在选举出来的领导人带领进行正常操作,比如日志复制等。
(1)任何一个服务器都可以成为一个候选者,它向其他服务器(选民)发出要求选举自己的请求
(2)其他服务器同意了,回复OK(同意),此时如果有一个Follower服务器宕机,没收到请求选举的要求,则只要达到半数以上的票数,候选人还是可以成为领导者的
(3)这样,这个候选者就成为领导者,它可以向选民们发出要执行具体操作动作的指令,比如进行日志复制
(4)如果一旦这个Leader宕机崩溃了,那么Follower中会有一个成为候选者,发出邀票选举
3.7 Lease机制
Lease英文含义是“租期”,“承诺”。在分布式环境中,该机制描述为:Lease是由授权者授予分布式环境一段时间内的承诺。
以下图为例,缓存服务器(Server)把数据分发给对应的节点NodeA,NodeB以及NodeC。其中节点A、B得到数据v01,有效期为12:00:00,而节点C受到数据v02,有效期为12:15:00。节点A可以被v01数据缓存在本地,在Lease时间范围内,放心使用,而Server也遵守承诺,在Lease过期时间内不修改数据
当时间到12:00:01 时候,此时v01的数据过期,则NodeA、NodeB会删除本地数据。而此时Server会阻塞一直到已发布的所有Lease都已经超时过期,再更新数据并发出新的Lease,如下图
Lease还存在以下优化点:
(1)已过期的Lease的读问题:NodeA的数据已经过期,这时Server还未更新发布。
(2)主动通知机制: 如果Server的数据通过配置后台,事件触发等在过期时间前主动修改数据,需要主动发起失效命令,如果所有失效成功,则可以直接更新数据,颁发新的Lease。如果不安全成功,则只能重试或者退回原始等待方案
(3)基于锁定资源的角度 ,如果一次更新动作的数据是分离的,则没有必要对所有的Lease等待过期
3.8 解决“脑裂”问题
主备是实现高可用的有效方式,但存在一个脑裂问题。脑裂是指在一个高可用(HA)系统中,当联系着的两个节点断开联系时,本来为一个整体的系统,分裂为两个独立节点,这时两个节点开始争抢共享资源 , 结果会导致系统混乱,数据损坏
当通过心跳检测做主备切换的时候,就存在不确定性。心跳检测的不确定性是发生脑裂问题的一个非常重要的原因,比如Slave提供服务了,但此前判死的Master又复活了,还在继续工作,则对应用程序逻辑带来未知因素,其中就包括抢夺资源
有一种解决做法是设置仲裁机制 ,例如设置第三方检测服务器(Monitor),当Slave确定准备接管Master时,让Monitor也ping一下Master,如果没有通讯,则判断其“死亡”;同时Master在对外提供服务时,每隔一段时间比如 10s 由Master服务器 ping Slave服务器和Monitor,如果均出现异常,则暂定业务操作,重试,重试多次之后则退出程序执行或者执行服务器重启操作
这会产生新的问题:如Monitor的高可用保障。通过Lease机制也可以进一步处理双主脑裂问题。如图,假设Slave已经在提供服务了,对应的Server服务器则获得Slave颁发的Lease。假设老Master仍在提供服务,则Lease必然是过期的,因此请求失败,老Master请求频繁失效的情况下,
3.9 Quorum NWR
NWR是一种分布式存储系统中用于控制一致性级别的一种策略,在Amazon的Dynamo云存储系统中,就应用 NWR 来控制一致性
N: 同一份数据的拷贝份数
W: 更新一个数据对象的时候需要确保成功更新的份数
R: 读取一个数据需要读取的拷贝的份数
具体策略通过两个公式计算:
这2个公式的意思是:
- 写操作要确保成功的份数应该高于同一份数据拷贝总份数的一半
- 同时,写操作加上读操作的总份数应高于同一份数据拷贝总份数
由表可得,N至少达到3,大于3则付出更高的成本。小于3无法保障高可用。一般采取 N=3、R=2、W=2的配置,W = 2 ,可以保障大多数写成功,而 R = 2,则能保障读到大多数一致的最新版本。 关于由于不同节点都在提供 W 和 R ,而 W 未必等于 N ,则一定存在数据不一直的情况。冲突解决策略一般有 Cassandra 使用的 client timestamp 和 Riak 的 Vector clock 等,如果没法解决,冲突可能会硬性覆盖或者推到业务代码。
Taobao File System,简称TFS,是淘宝针对海量非架构化数据存储设计的分布式系统。为外部提供高可扩展、高可用、高性能、面向互联网的服务。TFS采取了 N = 3、W = 3 的策略,为了取得写性能与高可用之间的平衡,在某个DataServer出现问题的时候,采取异步策略,由对应block元数据管理机制启动恢复流程,选择继续写到成功为止
TFS采用Block存储多份的方式来实现 DataServer 的容错。每一个 Block 会在TFS中存在多份,一般为3份,并且分布在不同网段的不同 DataServer 上。对于每一个写入请求,必须在所有的 Block 写入成功才算成功。当出现磁盘损坏DataServer宕机的时候,TFS 启动复制流程,把备份数未达到最小备份数的 Block 尽快复制到其他DataServer上去。TFS对每一个文件会记录校验 crc , 当客户端发现 crc 和文件内容不匹配时,会自动切换到一个好的 block 上读取。此后客户端将会实现自动修复单个文件损坏的情况
3.10 MVCC
MVCC,全称 Multiversion concurrency control,翻译为基于多版本并发控制 。人们一般把基于锁(比如行级锁)的并发控制机制称为悲观锁机制,而把 MVCC 称为乐观锁机制。由于 MVCC 是一种宽松的设计,读写互不阻塞,可以获得较好的并发性能
具体可看:
3.11 Gossip
对于分布式系统而言,由于状态分散在集群中的各个节点上,集群的状态同步面临着集中式系统所不具备的问题:
- 其中的每一个节点如何较快的得知集群状态全集的某些特征
- 如何避免多个节点就某个状态发生分歧,使得集群的状态实时或最终一致
分布式系统中的各个节点通过一定的交互方式(分布式协议)解决上述问题
Gossip就是一种去中心化思路的分布式协议,解决状态在集群中的传播和状态一致性的保证两个问题。因为其实现简单,具备较高的容错性和性能,成为分布式系统最广泛使用的状态同步协议之一
(1) 状态的传播
以Gossip协议同步状态的思路类似于流言的传播
A节点率先知道了某个流言(msg),它首先将此信息传播到集群中的部分节点(比如相邻的两个节点)B和C,后者再将其传递到它们所选择的“部分”节点,例如 B 选择了 D 和 E ,C 选择了将流言传播到 B 和 F。以此类推,最终来自于 A 的这条留言再3轮交互后被传播到了集群中的所有节点
在分布式系统的实践中,这个流言可能是:某个节点所感知到的其他节点是否宕机的认识;也可能是数据水平拆分的缓存集群中,关于哪些 hash 桶分布在哪些节点上的信息。每个节点起初只掌握部分状态信息,不断地从其他节点收到 Gossip 信息,每个节点逐渐地掌握到整个集群的状态信息。 因此解决了状态同步的而第一个问题:全集状态的获取。消息也能通过别的李静传播到整个集群,如下图:
(2) 状态的一致
状态同步的第二个问题:对于同一条状态信息,不同的节点可能掌握的值不同 ,也能通过基于Gossip 通信思路构建的协议包版本得到解决。例如水平拆分的 Redis 缓存集群,初始状态下 hash 桶在各个节点的分布如下所示
此时各个节点预先通过某种协议(如 Gossip)得知了集群的状态全集,此时新加入了节点D,如图所示
D分担了C的某个Hash桶,此时 C/D 和集群中其他节点就 C 所拥有的哪些 hash 这件事产生了分歧:A/B 认为 C 目前有6/7/8。此时通过为 Gossip 消息引入版本号,使得关于 C 的最新状态消息在全集群达到一致。如B收到来自 A 和 C 的 Gossip 消息时会将版本号最新的消息(来自 C 的 v2)更新到自己的本地副本中