一、分布式架构介绍
1.1 什么是分布式系统?
分布式系统是一个硬件或软件组件分布在不同的网络计算机上,彼此之间仅仅通过消息传递进行通信和协调的系统。
1.2 分布式与集群的区别
集群: 多个服务器做同一个事情/分布式: 多个服务器做不同的事情
1.3 分布式系统特性
分布性
空间随机分布。这些计算机可以分布在不同的机房,不同的城市,甚至不同的国家。
对等性
分布式系统中的计算机没有主/从之分,组成分布式系统的所有节点都是对等的。
并发性
同一个分布式系统的多个节点,可能会并发地操作一些共享的资源,诸如数据库或分布式存储。
缺乏全局时钟
既然各个计算机之间是依赖于交换信息来进行相互通信,很难定义两件事件的先后顺序,缺乏全局始终控制序列。
故障总会发生
组成分布式的计算机,都有可能在某一时刻突然间崩掉。分的计算机越多,可能崩掉一个的几率就越大。如果再考虑到设计程序时的异常故障,也会加大故障的概率。
处理单点故障
单点SPoF(Single Point of Failure):某个角色或者功能只有某一台计算机在支撑,在这台计算机上出现的故障是单点故障。
1.4 分布式系统面临的问题
通信异常
网络分区
节点故障
三态:成功、失败、超时
重发
幂等:一次和多次请求某一个资源对于资源本身应该具有同样的结果
二、分布式理论
2.1 数据一致性
分布式数据一致性,指的是数据在多份副本中存储时,各副本中的数据是一致的。
一致性分类:
强一致性:写入了什么,读出来也是什么
弱一致性:不承诺立即读取到写入值
最终一致性:弱一致性的一种
【不一致时间窗口:系统无法保证强一致性的时间片段】
最终一致性变种:
因果一致性:(AB为因果关系)
读己之所写一致性
会话一致性
单调读一致性
单调写一致性
数据一致性模型图
2.2 CAP定理
一致性(Consistency)
所有节点访问时都是同一份最新的数据副本
可用性(Availability)
每次请求都能获取到非错的响应,但是不保证获取的数据为最新数据
分区容错性(Partition tolerance)
分布式系统在遇到任何网络分区故障的时候,仍然能够对外提供满足一致性和可用性的服务,除非整个网络环境都发生了故障
由于CAP不可能同时满足,而分区容错性对于分布式系统而言是必须的,即一般采用CP或AP
2.3 BASE定理
BASE:
Basically Available(基本可用)
Soft state(软状态)
Eventually consistent(最终一致性)
三、分布式一致性协议
3.1 2PC(2 Prepare Commit)
2PC流程图
优点:原理简单
缺点:
同步阻塞
在二阶段提交的执行过程中,所有参与该事务操作的逻辑都处于阻塞状态,即当参与者占有公共资源时,其他节点访问公共资源会处于阻塞状态
单点问题
若协调器出现问题,那么整个二阶段提交流程将无法运转,若协调者是在阶段二中出现问题时,那么其他参与者将会一直处于锁定事务资源的状态中,而无法继续完成事务操作
数据不一致
在阶段二中,执行事务提交的时候,当协调者向所有的参与者发送Commit请求之后,发生了局部网络异常或者是协调者在尚未发送完Commit请求之前自身发生了崩溃,导致最终只有部分参与者收到了Commit请求,于是会出现数据不一致的现象。
太过保守
在进行事务提交询问的过程中,参与者出现故障而导致协调者始终无法获取到所有参与者的响应信息的话,此时协调者只能依靠自身的超时机制来判断是否需要中断事务,这样的策略过于保守,即没有完善的容错机制,任意一个结点的失败都会导致整个事务的失败。
3.2 3PC(三阶段提交协议)
3PC流程图
首先对于协调者和参与者都设置了超时机制(在2PC中,只有协调者拥有超时机制,即如果在一定时间内没有收到参与者的消息则默认失败),主要是避免了参与者在长时间无法与协调者节点通讯(协调者挂掉了)的情况下,无法释放资源的问题,因为参与者自身拥有超时机制会在超时后,自动进行本地commit从而进行释放资源。而这种机制也侧面降低了整个事务的阻塞时间和范围。
通过CanCommit、PreCommit、DoCommit三个阶段的设计,相较于2PC而言,多设置了一个缓冲阶段保证了在最后提交阶段之前各参与节点的状态是一致的 。
PreCommit是一个缓冲,保证了在最后提交阶段之前各参与节点的状态是一致的。
注意:一旦进入阶段三,可能会出现 2 种故障:
协调者出现问题
协调者和参与者之间的网络故障
如果出现了任一一种情况,最终都会导致参与者无法收到 doCommit 请求或者 abort 请求,针对这种情况,参与者都会在等待超时之后,继续进行事务提交
问题:3PC协议并没有完全解决数据一致问题。
3.3 NWR协议
NWR是一种在分布式存储系统中用于控制一致性级别的一种策略。在亚马逊的云存储系统(Dynamo)中,就应用NWR来控制一致性。
N:在分布式存储系统中,有多少份备份数据
W:代表一次成功的更新操作要求至少有w份数据写入成功
R: 代表一次成功的读数据操作要求至少有R份数据成功读取
如何判断数据的新旧?
这需要向量时钟来配合,所以对于Dynamo来说,是通过NWR协议结合向量时钟来共同完成一致性保证的。
NWR示例图
3.4 Gossip协议
Gossip 是一种去中心化的分布式协议,数据通过节点像病毒一样逐个传播。
因为是指数级传播,整体传播速度非常快。
优点:
扩展性:允许节点的任意增加和减少,新增节点的状态 最终会与其他节点一致容错:任意节点的宕机和重启都不会影响 Gossip 消息的传播,具有天然的分布式系统容错特性
去中心化:无需中心节点,所有节点都是对等的,任意节点无需知道整个网络状况,只要网络连通,任意节点可把消息散播到全网
最终一致性:Gossip 协议实现信息指数级的快速传播,因此在有新信息需要传播时,消息可以快速地发送到全局节点,在有限的时间内能够做到所有节点都拥有最新的数据。
缺点:
消息延迟:节点随机向少数几个节点发送消息,消息最终是通过多个轮次的散播而到达全网;不可避免的造成消息延迟。
消息冗余:节点定期随机选择周围节点发送消息,而收到消息的节点也会重复该步骤;不可避免的引起同一节点消息多次接收,增加消息处理压力
适合于 AP 场景的数据一致性处理
3.5 Paxos算法(重点)
自Paxos问世以来就持续垄断了分布式一致性算法,Paxos这个名词几乎等同于分布式一致性。Google Chubby的作者Mike Burrows说过这个世界上只有一种一致性算法,那就是Paxos。
Paxos算法需要解决的问题就是如何在一个可能发生上述异常的分布式系统中,快速且正确地在集群内部对某个数据的值达成一致,并且保证不论发生以上任何异常,都不会破坏整个系统的一致性。
Basic Paxos
角色介绍
Client:客户端
客户端向分布式系统发出请求,并等待响应。例如,对分布式文件服务器中文件的写请求
Proposer:提案发起者
提案者提倡客户端请求,试图说服Acceptor对此达成一致,并在发生冲突时充当协调者以推动协议向前发展
Acceptor: 决策者,可以批准提案
Acceptor可以接受(accept)提案;并进行投票, 投票结果是否通过以多数派为准, 以如果某个提案被选定,那么该提案里的value就被选定了
Learner: 最终决策的学习者
学习者充当该协议的复制因素(不参与投票)
basic paxos[活锁问题:只需要在每个Proposer再去提案的时候随机加上一个等待时间即可]
Basic paxos流程一共分为4个步骤:
Prepare Proposer提出一个提案,编号为N, 此N大于这个Proposer之前提出所有提出的编号, 请求Accpetor的多数人接受这个提案
Promise 如果编号N大于此Accpetor之前接收的任提案编号则接收, 否则拒绝
Accept 如果达到多数派, Proposer会发出accept请求, 此请求包含提案编号和对应的内容
Accepted 如果此Accpetor在此期间没有接受到任何大于N的提案,则接收此提案内容, 否则忽略
Multi-Paxos
针对basic Paxos是存在一定得问题,首先就是流程复杂,实现及其困难,其次效率低(达成一致性需要2轮RPC调用)针对basic Paxos流程进行拆分为选举和复制的过程。
Multi-Paxos在实施的时候会将Proposer,Acceptor和Learner的角色合并统称为“服务器”。因此,最后只有“客户端”和“服务器”。
Multi-Paxos角色叠加流程图
3.6 Raft协议
Paxos算法的工程实现,引入主节点,通过竞选确定主节点。
节点状态:
Leader(主节点):接受 client 更新请求,写入本地后,然后同步到其他副本
Follower(从节点):从 Leader 中接受更新请求,然后写入本地日志文件。对客户端提供读请求
Candidate(候选节点):如果 follower 在一段时间内未收到 leader 心跳。则判断 leader可能故障,发起选主提议。节点状态从 Follower 变为 Candidate 状态,直到选主结束
竞选阶段动态流程示意
Raft完整版的动画演示:Raft
github提供的一个动画演示:Raft Consensus Algorithm
3.7 Lease机制
解决双主节点的问题,主要解决思路有四种:
设计能容忍双主的分布式协议
Raft协议-通过Term版本高的同步低的
用lease机制
涉及去中心化-Gossip协议
Lease的原理:引入中心节点负责下发Lease
lease时间长短一般取经验值1-10秒即可。太短网络压力大,太长则收回承诺时间过长影响可用性。
Lease机制
Lease的容错:
主节点宕机
lease机制天生即可容忍网络、lease接收方的出错,时间即Lease剩余过期时长
中心节点异常
颁发者宕机可能使得全部节点没有lease,系统处于不可用状态,解决的方法就是使用一个小集群而不是单一节点作为颁发者。
时差问题
中心节点与主节点之间的时钟可能也存在误差,只需要中心节点考虑时钟误差即可。
四、分布式系统设计策略
4.1 心跳检测机制
周期检测心跳机制
Server端每间隔 t 秒向Node集群发起监测请求,设定超时时间,如果超过超时时间,则判断“死亡”。
累计失效检测机制
在周期检测心跳机制的基础上,统计一定周期内节点的返回情况(包括超时及正确返回),以此计算节点的“死亡”概率。
4.2 高可用
高可用(High Availability)是系统架构设计中必须考虑的因素之一。通常是指,经过设计来减少系统不能提供服务的时间。
系统高可用性的常用设计模式包括三种:
主备(Master-SLave)
互备(Multi-Master)
集群(Cluster)模式
4.3 高可用HA下"脑裂问题"
在高可用(HA)系统中,当联系两个节点的"心跳线"断开时(即两个节点断开联系时),本来为一个整体、动作协调的HA系统,就分裂成为两个独立的节点(即两个独立的个体)。
脑裂预防方案
添加冗余的心跳线 [即冗余通信的方法]
同时用两条心跳线路 (即心跳线也HA),这样一条线路坏了,另一个还是好的,依然能传送心跳消息,尽量减少"脑裂"现象的发生几率。
仲裁机制
当两个节点出现分歧时,由第3方的仲裁者决定听谁的。这个仲裁者,可能是一个锁服务,一个共享盘或者其它什么东西
Lease机制
隔离(Fencing)机制
共享存储fencing:确保只有一个Master往共享存储中写数据;
客户端fencing:确保只有一个Master可以响应客户端的请求;
Slave fencing:确保只有一个Master可以向Slave下发命令。
4.4 容错性
容错的处理是保障分布式环境下相应系统的高可用或者健壮性。
4.5 负载均衡
硬件:F5
软件:Nginx
轮询:默认方式,每个请求会按时间顺序逐一分配到不同的后端服务器
weight:权重方式,在轮询策略的基础上指定轮询的几率,权重越大,接受请求越多
ip_hash:依据ip分配方式,相同的客户端的请求一直发送到相同的服务器,以保证
session会话
least_conn:最少连接方式,把请求转发给连接数较少的后端服务器
fair(第三方):响应时间方式,按照服务器端的响应时间来分配请求,响应时间短的优先分配
url_hash(第三方)
依据URL分配方式,按访问url的hash结果来分配请求,使每个url定向到同一个后端服务器
五、分布式架构服务调用
服务调用
HTTP 应用协议的通信框架
HttpURLConnection
Apache Common HttpClient
OKhttp3
RestTemplate
RPC 框架
Java RMI
Hessian
Dubbo
g’RPC
跨域调用(同源(协议, 域名, 端口号相同))
在分布式系统中,会有调用其他业务系统,导致出现跨域问题,跨域实质上是浏览器的一种保护处理。
常见的解决方案:
六、 分布式服务治理
6.1 服务协调(分布式锁是分布式协调技术实现的核心内容)
基于缓存(Redis等)实现分布式锁
获取锁的时候,使用setnx加锁,并使用expire命令为锁添加一个超时时间,超过该时间则自动释放锁,锁的value值为一个随机生成的UUID, 释放锁的时候进行判断。
获取锁的时候还设置一个获取的超时时间,若超过这个时间则放弃获取锁。
释放锁的时候,通过UUID判断是不是该锁,若是该锁,则执行delete进行锁释放。
基于ZooKeeper实现分布式锁的步骤如下:
创建一个目录mylock
线程A想获取锁就在mylock目录下创建临时顺序节点
获取mylock目录下所有的子节点,然后获取比自己小的兄弟节点,如果不存在,则说明当前线程顺序号最小,获得锁
线程B获取所有节点,判断自己不是最小节点,设置监听比自己次小的节点
线程A处理完,删除自己的节点,线程B监听到变更事件,判断自己是不是最小的节点,如果是则获得锁
6.2 服务削峰
削峰从本质上来说就是更多地延缓用户请求,以及层层过滤用户的访问需求,遵从“最后落地到数据库的请求数要尽量少”的原则
消息队列解决削峰
流量削峰漏斗:层层削峰
分层过滤的核心思想:
通过在不同的层次尽可能地过滤掉无效请求。
通过CDN过滤掉大量的图片,静态资源的请求。
再通过类似Redis这样的分布式缓存过滤请求。
分层过滤的基本原则
对写数据进行基于时间的合理分片,过滤掉过期的失效请求。
对写请求做限流保护,将超出系统承载能力的请求过滤掉。
涉及到的读数据不做强一致性校验,减少因为一致性校验产生瓶颈的问题。
对写数据进行强一致性校验,只保留最后有效的数据。
6.3 服务降级
当服务器压力剧增的情况下,根据实际业务情况及流量,对一些服务和页面有策略的不处理或换种简单的方式处理,从而释放服务器资源以保证核心服务正常运作或高效运作。
降级策略:
页面降级 —— 可视化界面禁用点击按钮、调整静态页面
延迟服务 —— 如定时任务延迟处理、消息入MQ后延迟处理
写降级 —— 直接禁止相关写操作的服务请求
读降级 —— 直接禁止相关读的服务请求
缓存降级 —— 使用缓存方式来降级部分读频繁的服务接口
针对后端代码层面的降级处理策略,则我们通常使用以下几种处理措施进行降级处理:
抛异常
返回NULL
调用Mock数据
调用Fallback处理逻辑
分级降级
6.4 服务限流
多维度限流
限流算法
限流算法-计数器(固定窗口)
计数器限制每一分钟或者每一秒钟内请求不能超过一定的次数,在下一秒钟计数器清零重新计算。
存在问题:
客户端在第一分钟的59秒请求100次,在第二分钟的第1秒又请求了100次, 2秒内后端会受到200次请求的压力,形成了流量突刺
限流算法-计数器(滑动窗口)
滑动窗口其实是细分后的计数器,它将每个时间窗口又细分成若干个时间片段,每过一个时间片段,整个时间窗口就会往右移动一格。时间窗口向右滑动一格,这时这个时间窗口其实已经打满了100次,客户端将被拒绝访问,时间窗口划分的越细,滑动窗口的滚动就越平滑,限流的效果就会越精确
限流算法-漏桶(削峰填谷)
漏桶算法类似一个限制出水速度的水桶,通过一个固定大小FIFO队列+定时取队列元素的方式实现,请求进入队列后会被匀速的取出处理(桶底部开口匀速出水),当队列被占满后后来的请求会直接拒绝(水倒的太快从桶中溢出来)
限流算法-令牌桶
令牌桶算法是以一个恒定的速度往桶里放置令牌(如果桶里的令牌满了就废弃),每进来一个请求去桶里找令牌,有的话就拿走令牌继续处理,没有就拒绝请求。
令牌桶的优点是可以应对突发流量
缺点就是相对漏桶一定程度上减小了对下游服务的保护
6.5 服务熔断(防止雪崩)
【熔断】, 熔断这一概念来源于电子工程中的断路器(Circuit Breaker)。在互联网系统中,当下游服务因访问压力过大而响应变慢或失败,上游服务为了保护系统整体的可用性,可以暂时切断对下游服务的调用。这种牺牲局部,保全整体的措施就叫做熔断。
Spring Cloud Hystrix
Sentinel 和 Hystrix 的原则是一致的: 当调用链路中某个资源出现不稳定,例如,表现为timeout,异常比例升高的时候,则对这个资源的调用进行限制,并让请求快速失败,防止避免影响到其它的资源,最终产生雪崩的效果。
Sentinel 熔断手段:
分布式链路追踪(Distributed Tracing)就是将一次分布式请求还原成调用链路。
链路跟踪具备的功能:
故障快速定位
各个调用环节的性能分析
数据分析
生成服务调用拓扑图
链路跟踪设计原则
设计目标:低侵入、低损耗、拓展性
埋点和生成日志
抓取和存储日志
分析和统计调用链路数据
计算和展示
链路跟踪Trace模型
Trace:一次完整的分布式调用跟踪链路
Span:跟踪服务调用基本结构,表示跨服务的一次调用; 多span形成树形结构,组合成一次Trace追踪记录
Annotation:
在span中的标注点,记录整个span时间段内发生的事件:
Cs CLIENT_SEND,客户端发起请求
Cr CLIENT_RECIEVE,客户端收到响应
Sr SERVER_RECIEVE,服务端收到请求
Ss SERVER_SEND,服务端发送结果
可以认为是特殊的Annotation,用户自定义事件:
Event 记录普通事件
Exception 记录异常事件
链路跟踪Trace模型