1. 分布式架构介绍
1.1 什么是分布式系统
分布式系统是一个硬件或软件组件分布在不同的网络计算机上,彼此之间仅仅通过消息传递进行通信和协调的系统。
通俗的理解,所谓分布式系统,就是一个业务拆分成多个子业务,分布在不同的服务器节点,共同构成的系统称为分布式系统,同一个分布式系统中的服务器节点在空间部署上是可以随意分布的,这些服务器可能放在不同的机柜中,也可能在不同的机房中,甚至分布在不同的城市。
1.2 分布式与集群的区别
布式和集群是不⼀样的,分布式⼀定是集群,但是集群不⼀定是分布式。
因为集群就是多个实例⼀起⼯作,分布式将⼀个系统拆分之后那就是多个实例;集群并不⼀定是分布式,因为复制型的集群不是拆分⽽是复制,没有体现出整个系统的功能是分散在各个节点上实现的。
1.3 分布式系统特性
- 分布性
空间中随机分布。这些计算机可以分布在不同的机房,不同的城市,甚至不同的国家。 - 对等性
分布式系统中的计算机没有主/从之分,组成分布式系统的所有节点都是对等的。 - 并发性
同一个分布式系统的多个节点,可能会并发地操作一些共享的资源,诸如数据库或分布式存储。 - 缺乏全局时钟
既然各个计算机之间是依赖于交换信息来进行相互通信,很难定义两件事件的先后顺序,缺乏全局始终控制序列 - 故障总会发生
组成分布式的计算机,都有可能在某一时刻突然间崩掉。分的计算机越多,可能崩掉一个的几率就越大。如果再考虑到设计程序时的异常故障,也会加大故障的概率。 - 处理单点故障
单点SPoF(Single Point of Failure):某个角色或者功能只有某一台计算机在支撑,在这台计算机上出现的故障是单点故障。
1.4 分布式系统面临的问题
-
通信异常
网络本身的不可靠性,因此每次网络通信都会伴随着网络不可用的风险(光纤、路由、DNS等硬件设备或系统的不可用),都会导致最终分布式系统无法顺利进行一次网络通信,另外,即使分布式系统各节点之间的网络通信能够正常执行,其延时也会大于单机操作,存在巨大的延时差别,也会影响消息的收发过程,因此消息丢失和消息延迟变的非常普遍。
-
网络分区
网络之间出现了网络不连通,但各个子网络的内部网络是正常的,从而导致整个系统的网络环境被切分成了若干个孤立的区域,分布式系统就会出现局部小集群,在极端情况下,这些小集群会独立完成原本需要整个分布式系统才能完成的功能,包括数据的事务处理,这就对分布式一致性提出非常大的挑战。
-
节点故障
节点故障是分布式系统下另一个比较常见的问题,指的是组成分布式系统的服务器节点出现的宕机或"僵死"现象,根据经验来说,每个节点都有可能出现故障,并且经常发生.
-
三态
分布式系统每一次请求与响应存在特有的“三态”概念,即成功、失败和超时。
-
重发
分布式系统在发生调用的时候可能会出现 失败 超时 的情况. 这个时候需要重新发起调用.
幂等
一次和多次请求某一个资源对于资源本身应该具有同样的结果(网络超时等问题除外)。也就是说,其任意多次执行对资源本身所产生的影响均与一次执行的影响相同
-
场景1
-
场景2
-
场景3
2. 分布式理论
2.1 数据一致性
2.1.1 什么是分布式数据一致性
分布式数据一致性,指的是数据在多份副本中存储时,各副本中的数据是一致的。
2.1.2 副本一致性
分布式系统当中,数据往往会有多个副本。多个副本就需要保证数据的一致性。这就带来了同步的问题,因为网络延迟等因素, 我们几乎没有办法保证可以同时更新所有机器当中的包括备份所有数据. 就会有数据不一致的情况
总得来说,我们无法找到一种能够满足分布式系统中数据一致性解决方案。因此,如何既保证数据的一致性,同时又不影响系统运行的性能,是每一个分布式系统都需要重点考虑和权衡的。于是,一致性级别由此诞生.
2.1.3 一致性分类
强一致性
这种一致性级别是最符合用户直觉的,它要求系统写入什么,读出来的也会是什么,用户体验好,但实现起来往往对系统的性能影响大。但是强一致性很难实现。弱一致性
这种一致性级别约束了系统在写入成功后,不承诺立即可以读到写入的值,也不承诺多久之后数据能够达到一致,但会尽可能地保证到某个时间级别(比如秒级别)后,数据能够达到一致状态。最终一致性
最终一致性也是弱一致性的一种,它无法保证数据更新后,所有后续的访问都能看到最新数值,而是需要一个时间,在这个时间之后可以保证这一点(就是在一段时间后,节点间的数据会最终达到一致状态),而在这个时间内,数据也许是不一致的,这个系统无法保证强一致性的时间片段被称为「不一致窗口」。不一致窗口的时间长短取决于很多因素,比如备份数据的个数、网络传输延迟速度、系统负载等。
最终一致性在实际应用中又有多种变种:
-
因果一致性
如果进程A通知进程B它已更新了一个数据项,那么进程B的后续访问将返回更新后的值。与进程A无因果关系的进程C的访问遵守一般的最终一致性规则
-
读己之所写一致性
当进程A自己更新一个数据项之后,它总是访问到更新过的值,绝不会看到旧值。这是因果一致性模型的一个特例。
-
会话一致性
它把访问存储系统的进程放到会话的上下文中。只要会话还存在,系统就保证“读己之所写”一致性。如果由于某些失败情形令会话终止,就要建立新的会话,而且系统的保证不会延续到新的会话。
-
单调读一致性
如果一个进程已经读取到一个特定值,那么该进程不会读取到该值以前的任何值。
-
单调写一致性
系统保证对同一个进程的写操作串行化。
-
一致性模型图
-
2.2 CAP定理
2.2.1 CAP定理介绍
CAP定理(CAP theorem),又被称作布鲁尔定理(Brewer's theorem),它指出对于一个分布式计算系统来说,不可能同时满足以下三点
-
一致性(C-Consistency)
这里指的是强一致性
在写操作完成后开始的任何读操作都必须返回该值,或者后续写操作的结果. 也就是说,在一致性系统中,一旦客户端将值写入任何一台服务器并获得响应,那么之后client从其他任何服务器读取的都是刚写入的数据
- 客户端向G1写入数据v1,并等待响应
- 此时,G1服务器的数据为v1,而G2服务器的数据为v0,两者不一致
- 接着,在返回响应给客户端之前,G2服务器会自动同步G1服务器的数据,使得G2服务器的数据也是v1
- 一致性保证了不管向哪台服务器(比如这边向G1)写入数据,其他的服务器(G2)能实时同步数据
- G2已经同步了G1的数据,会告诉G1,我已经同步了
- G1接收了所有同步服务器的已同步的报告,才将“写入成功”信息响应给client
- client再发起请求,读取G2的数据
- 此时得到的响应是v1,即使client读取数据到G2
可用性(A-Availability)
系统中非故障节点收到的每个请求都必须有响应. 在可用系统中,如果我们的客户端向服务器发送请求,并且服务器未崩溃,则服务器必须最终响应客户端,不允许服务器忽略客户的请求-
分区容错性(P-Partition tolerance)
允许网络丢失从一个节点发送到另一个节点的任意多条消息,即不同步. 也就是说,G1和G2发送给对方的任何消息都是可以放弃的,也就是说G1和G2可能因为各种意外情况,导致无法成功进行同步,分布式系统要能容忍这种情况。
2.2.2 CAP三者不可能同时满足论证
假设确实存在三者能同时满足的系统
-
那么我们要做的第一件事就是分区我们的系统,由于满足分区容错性,也就是说可能因为通信不佳等情况,G1和G2之间是没有同步
-
接下来,我们的客户端将v1写入G1,但G1和G2之间是不同步的,所以如下G1是v1数据,G2是v0数据。
-
由于要满足可用性,即一定要返回数据,所以G1必须在数据没有同步给G2的前提下返回数据给client,如下
4.接下去,client请求的是G2服务器,由于G2服务器的数据是v0,所以client得到的数据是v0
结论: 很明显,G1返回的是v1数据,G2返回的是v0数据,两者不一致。其余情况也有类似推导,也就是说 CAP三者不能同时出现。
2.2.3 CAP三者如何权衡
三选二利弊如何
CA (Consistency + Availability):关注一致性和可用性,它需要非常严格的全体一致的协议。CA 系统不能容忍网络错误或节点错误,一旦出现这样的问题,整个系统就会拒绝写请求,因为它并不知道对面的那个结点是否挂掉了,还是只是网络问题。唯一安全的做法就是把自己变成只读的。
CP (consistency + partition tolerance):关注一致性和分区容忍性。它关注的是系统里大多数人的一致性协议。这样的系统只需要保证大多数结点数据一致,而少数的结点会在没有同步到最新版本的数据时变成不可用的状态。这样能够提供一部分的可用性。
AP (availability + partition tolerance):这样的系统关心可用性和分区容忍性。因此,这样的系统不能达成一致性,需要给出数据冲突,给出数据冲突就需要维护数据版本。
如何进行三选二
放弃了一致性,满足分区容错,那么节点之间就有可能失去联系,为了高可用,每个节点只能用本地数据提供服务,而这样会容易导致全局数据不一致性。
对于互联网应用来说,机器数量庞大,节点分散,网络故障再正常不过了,那么此时就是保障AP,放弃C的场景,而从实际中理解,像网站这种偶尔没有一致性是能接受的,但不能访问问题就非常大了。
对于银行来说,就是必须保证强一致性,也就是说C必须存在,那么就只用CA和CP两种情况,当保障强一致性和可用性(CA),那么一旦出现通信故障,系统将完全不可用。另一方面,如果保障了强一致性和分区容错(CP),那么就具备了部分可用性。实际究竟应该选择什么,是需要通过业务场景进行权衡的(并不是所有情况都是CP好于CA,只能查看信息但不能更新信息有时候还不如直接拒绝服务)
2.3 BASE理论
上面我们讲到CAP 不可能同时满足,而分区容错性是对于分布式系统而言,是必须的。最后,我们说,如果系统能够同时实现 CAP 是再好不过的了,所以出现了 BASE 理论。
BASE:全称:Basically Available(基本可用),Soft state(软状态),和 Eventually consistent(最终一致性)三个短语的缩写 ,Base 理论是对 CAP 中一致性和可用性权衡的结果,其来源于对大型互联网分布式实践的总结,是基于 CAP 定理逐步演化而来的。其核心思想是: 既是无法做到强一致性(Strong consistency),但每个应用都可以根据自身的业务特点,采用适当的方式来使系统达到最终一致性(Eventual consistency)
- Basically Available(基本可用)
什么是基本可用呢?假设系统,出现了不可预知的故障,但还是能用,相比较正常的系统而言:
- 响应时间上的损失:正常情况下的搜索引擎 0.5 秒即返回给用户结果,而基本可用的搜索引擎可以在 1 秒返回结果。
-
功能上的损失:在一个电商网站上,正常情况下,用户可以顺利完成每一笔订单,但是到了大促期间,为了保护购物系统的稳定性,部分消费者可能会被引导到一个降级页面
- Soft state(软状态)
什么是软状态呢?相对于原子性而言,要求多个节点的数据副本都是一致的,这是一种 “硬状态”。
软状态指的是:允许系统中的数据存在中间状态,并认为该状态不会影响系统的整体可用性,即允许系统在多个不同节点的数据副本存在数据延时。 - Eventually consistent(最终一致性)
上面说软状态,然后不可能一直是软状态,必须有个时间期限。在期限过后,应当保证所有副本保持数据一致性。从而达到数据的最终一致性。这个时间期限取决于网络延时,系统负载,数据复制方案设计等等因素。
3. 分布式一致性协议
3.1 两阶段提交协议(2PC)
3.1.1两阶段提交协议
两阶段提交协议,简称2PC(2 Prepare Commit),是比较常用的解决分布式事务问题的方式,要么所有参与进程都提交事务,要么都取消事务,即实现ACID中的原子性(A)的常用手段。
分布式事务: 事务提供一种操作本地数据库的不可分割的一系列操作 “要么什么都不做,要么做全套(All or Nothing)”的机制,而分布式事务就是为了操作不同数据库的不可分割的一系列操作 “要么什么都不做,要么做全套(All or Nothing)”的机制
3.2 三阶段提交协议(3PC)
三阶段提交协议出现背景:一致性协议中设计出了二阶段提交协议(2PC),但是2PC设计中还存在缺陷,于是就有了三阶段提交协议,这便是3PC的诞生背景
3.2.1 三阶段提交协议
3PC,全称 “three phase commit”,是 2PC 的改进版,将 2PC 的 “提交事务请求” 过程一分为二,共形成了由CanCommit、PreCommit和doCommit三个阶段组成的事务处理协议。
3.3 NWR协议
3.3.1 什么是NWR协议
NWR是一种在分布式存储系统中用于控制一致性级别的一种策略。在亚马逊的云存储系统中,就应用NWR来控制一致性。
N:在分布式存储系统中,有多少份备份数据
W:代表一次成功的更新操作要求至少有w份数据写入成功
R: 代表一次成功的读数据操作要求至少有R份数据成功读取
3.3.2 原理
NWR值的不同组合会产生不同的一致性效果,当W+R>N的时候,整个系统对于客户端来讲能保证强一致性。
以常见的N=3、W=2、R=2为例:
- N=3,表示,任何一个对象都必须有三个副本
- W=2表示,对数据的修改操作只需要在3个副本中的2个上面完成就返回
- R=2表示,从三个对象中要读取到2个数据对象,才能返回
在分布式系统中,数据的单点是不允许存在的。即线上正常存在的备份数量N设置1的情况是非常危险的,因为一旦这个备份发生错误,就 可能发生数据的永久性错误。假如我们把N设置成为2,那么,只要有一个存储节点发生损坏,就会有单点的存在。所以N必须大于2。N越高,系统的维护和整体 成本就越高。工业界通常把N设置为3
- 当W是2、R是2的时候,W+R>N,这种情况对于客户端就是强一致性的。
在上图中,如果R+W>N,则读取操作和写入操作成功的数据一定会有交集(如图中的Node2),这样就可以保证一定能够读取到最新版本的更新数据,数据的强一致性得到了保证。在满足数据一致性协议的前提下,R或者W设置的越大,则系统延迟越大,因为这取决于最慢的那份备份数据的响应时间
-
当R+W<=N,无法保证数据的强一致性
因为成功写和成功读集合可能不存在交集,这样读操作无法读取到最新的更新数值,也就无法保证数据的强一致性。
3.4 Gossip 协议
3.4.1 什么是Gossip 协议
Gossip 协议也叫 Epidemic 协议 (流行病协议)。原本用于分布式数据库中节点同步数据使用,后被广泛用于数据库复制、信息扩散、集群成员身份确认、故障探测等。
从 gossip 单词就可以看到,其中文意思是八卦、流言等意思,我们可以想象下绯闻的传播(或者流行病的传播);gossip 协议的工作原理就类似于这个。gossip 协议利用一种随机的方式将信息传播到整个网络中,并在一定时间内使得系统内的所有节点数据一致。Gossip 其实是一种去中心化思路的分布式协议,解决状态在集群中的传播和状态一致性的保证两个问题。
3.4.2 Gossip原理
Gossip 协议的消息传播方式有两种:反熵传播 和 谣言传播
- 反熵传播
是以固定的概率传播所有的数据。所有参与节点只有两种状态:Suspective(病原)、
Infective(感染)。过程是种子节点会把所有的数据都跟其他节点共享,以便消除节点之间数据的任何不一致,它可以保证最终、完全的一致。缺点是消息数量非常庞大,且无限制;通常只用于新加入节点的数据初始化。 - 谣言传播
是以固定的概率仅传播新到达的数据。所有参与节点有三种状态:Suspective(病原)、Infective(感染)、Removed(愈除)。过程是消息只包含最新 update,谣言消息在某个时间点之后会被标记为 removed,并且不再被传播。缺点是系统有一定的概率会不一致,通常用于节点间数据增量同步。
3.5 Paxos协议
3.5.1 什么是Paxos
Paxos协议其实说的就是Paxos算法, Paxos算法是基于消息传递且具有高度容错特性的一致性算法,是目前公认的解决分布式一致性问题最有效的算法之一。
Paxos由 莱斯利·兰伯特(Leslie Lamport)于1998年在《The Part-Time Parliament》论文中首次公开,最初的描述使用希腊的一个小岛Paxos,描述了Paxos小岛中通过决议的流程,并以此命名这个算法,但是这个描述理解起来比较有挑战性。后来在2001年,莱斯利·兰伯特重新发表了朴实的算法描述版本《Paxos Made Simple》
自Paxos问世以来就持续垄断了分布式一致性算法,Paxos这个名词几乎等同于分布式一致性。Google的很多大型分布式系统都采用了Paxos算法来解决分布式一致性问题,如Chubby、Megastore以及Spanner等。开源的ZooKeeper,以及MySQL 5.7推出的用来取代传统的主从复制的MySQL GroupReplication等纷纷采用Paxos算法解决分布式一致性问题。然而,Paxos的最大特点就是难,不仅难以理解,更难以实现。
Google Chubby的作者Mike Burrows说过这个世界上只有一种一致性算法,那就是Paxos,其它的算法都是残次品。
3.5.2 Paxos 解决了什么问题
在常见的分布式系统中,总会发生诸如机器宕机或网络异常(包括消息的延迟、丢失、重复、乱序,还有网络分区)等情况。Paxos算法需要解决的问题就是如何在一个可能发生上述异常的分布式系统中,快速且正确地在集群内部对某个数据的值达成一致,并且保证不论发生以上任何异常,都不会破坏整个系统的一致性。
注:这里某个数据的值并不只是狭义上的某个数,它可以是一条日志,也可以是一条命令(command)。。。根据应用场景不同,某个数据的值有不同的含义。
在之前讲解2PC 和 3PC的时候在一定程度上是可以解决数据一致性问题的. 但是并没有完全解决就是协调者宕机的情况.
如何解决2PC和3PC的存在的问题呢?
-
步骤1-引入多个协调者
-
步骤-引入主协调者,以他的命令为基准
其实在引入多个协调者之后又引入主协调者.那么这个就是最简单的一种Paxos 算法.
Paxos的版本有: Basic Paxos , Multi Paxos, Fast-Paxos, 具体落地有Raft 和zk的ZAB协议
3.5.3 Basic Paxos相关概念
- 角色介绍
- Client:客户端
客户端向分布式系统发出请求,并等待响应。例如,对分布式文件服务器中文件的写请求。 - Proposer:提案发起者
提案者提倡客户端请求,试图说服Acceptor对此达成一致,并在发生冲突时充当协调者以推动协议向前发展 - Acceptor: 决策者,可以批准提案
Acceptor可以接受(accept)提案;并进行投票, 投票结果是否通过以多数派为准, 以如果某个提案被选定,那么该提案里的value就被选定了 - Learner: 最终决策的学习者
学习者充当该协议的复制因素(不参与投票)
-
决策模型
basic paxos流程
basic paxos流程一共分为4个步骤:
- Prepare
Proposer提出一个提案,编号为N, 此N大于这个Proposer之前提出所有提出的编号, 请求Accpetor的多数人接受这个提案 - Promise
如果编号N大于此Accpetor之前接收的任提案编号则接收, 否则拒绝 - Accept
如果达到多数派, Proposer会发出accept请求, 此请求包含提案编号和对应的内容 - Accepted
如果此Accpetor在此期间没有接受到任何大于N的提案,则接收此提案内容, 否则忽略
3.5.4 Basic Paxos流程图
-
无故障的basic Paxos
-
Acceptor失败时的basic Paxos
在下图中,多数派中的一个Acceptor发生故障,因此多数派大小变为2。在这种情况下,BasicPaxos协议仍然成功。
-
Proposer失败时的basic Paxos
Proposer在提出提案之后但在达成协议之前失败。具体来说,传递到Acceptor的时候失败了,这个时候需要选出新的Proposer(提案人),那么 Basic Paxos协议仍然成功
-
当多个提议者发生冲突时的basic Paxos
最复杂的情况是多个Proposer都进行提案,导致Paxos的活锁问题.
针对活锁问题解决起来非常简单: 只需要在每个Proposer再去提案的时候随机加上一个等待时间即可。
3.5.5 Multi-Paxos流程图
针对basic Paxos是存在一定得问题,首先就是流程复杂,实现及其困难, 其次效率低(达成一致性需要2轮RPC调用),针对basic Paxos流程进行拆分为选举和复制的过程.
-
第一次流程-确定Leader
-
第二次流程-直接由Leader确认
3.5.5 Multi-Paxos角色重叠流程图
Multi-Paxos在实施的时候会将Proposer,Acceptor和Learner的角色合并统称为“服务器”。因此,最后只有“客户端”和“服务器”。
3.6 Raft协议
3.6.1 什么是Raft协议
Paxos 是论证了一致性协议的可行性,但是论证的过程据说晦涩难懂,缺少必要的实现细节,而且工程实现难度比较高, 广为人知实现只有 zk 的实现 zab 协议。
Paxos协议的出现为分布式强一致性提供了很好的理论基础,但是Paxos协议理解起来较为困难,实现比较复杂。
然后斯坦福大学RamCloud项目中提出了易实现,易理解的分布式一致性复制协议 Raft。Java,C++,Go 等都有其对应的实现之后出现的Raft相对要简洁很多。引入主节点,通过竞选确定主节点。节点类型:Follower、Candidate 和 Leader
Leader 会周期性的发送心跳包给 Follower。每个 Follower 都设置了一个随机的竞选超时时间,一般为 150ms~300ms,如果在这个时间内没有收到Leader 的心跳包,就会变成 Candidate,进入竞选阶段, 通过竞选阶段的投票多的人成为Leader
3.6.2 Raft相关概念
- 节点状态
- Leader(主节点):接受 client 更新请求,写入本地后,然后同步到其他副本中
- Follower(从节点):从 Leader 中接受更新请求,然后写入本地日志文件。对客户端提供读
请求 - Candidate(候选节点):如果 follower 在一段时间内未收到 leader 心跳。则判断 leader可能故障,发起选主提议。节点状态从 Follower 变为 Candidate 状态,直到选主结束
termId:任期号,时间被划分成一个个任期,每次选举后都会产生一个新的 termId,一个任期内只有一个 leader。
RequestVote:请求投票,candidate 在选举过程中发起,收到多数派响应后,成为 leader。
3.6.3 竞选阶段流程
这个是Raft完整版http://thesecretlivesofdata.com/raft/动画演示
github也提供一个https://raft.github.io/动画演示地址 . 原理都是一样的.
单节点是不存在数据不一致问题的. 一个节点就很容易就该值达成一致性。
如果是多个节点如何达成一致性.Raft是用于实施分布式数据一致性协议的。
我们使用了3个不同的圆圈表示三种不同的状态
接下来开始完成整个竞选阶段流程:
-
下图表示一个分布式系统的最初阶段,此时只有 Follower,没有 Leader。Follower A 等待一个随机的竞选超时时间之后,没收到 Leader 发来的心跳包,因此进入竞选阶段。
-
此时 A 发送投票请求给其它所有节点。
-
其它节点会对请求进行回复,如果超过一半的节点回复了,那么该 Candidate 就会变成 Leader。
-
如果候选人获得大多数节点的投票,则该候选人成为领导者。
3.6.4 Leader节点宕机
Leader节点宕机后,重新选举
3.6.5 多个 Candidate 竞选
-
如果有多个 Follower 成为 Candidate,并且所获得票数相同,那么就需要重新开始投票,例如下图中 Candidate B 和 Candidate D 都获得两票,因此需要重新开始投票。
-
当重新开始投票时,由于每个节点设置的随机竞选超时时间不同,因此能下一次再次出现多个Candidate 并获得同样票数的概率很低
3.6.6 日志复制
-
来自客户端的修改都会被传入 Leader。注意该修改还未被提交,只是写入日志中。
-
Leader 会把修改复制到所有 Follower。
-
Leader 会等待大多数的 Follower 也进行了修改,然后才将修改提交。
-
此时 Leader 会通知的所有 Follower 让它们也提交修改,此时所有节点的值达成一致。
-
多次日志复制情况
3.6.7 网络分区
面对网络分区,Raft甚至可以保持一致。
-
最初始正常情况下状态,B节点会对其他4个节点发送心跳
-
当出现网络分区情况,, 但是出现网络分区的请求后,只能对A发送心跳,同时其他三个节点会再次选出一个leader节点
3.6.8 网络分区情况日志复制
网络分区情况日志复制工作也可以完成数据一致性
-
不同分区写入数据不同
-
最终E节点Termid最大成为Leader节点,同步节点数据,达成数据一致性
3.7 Lease机制
3.7.1 什么是Lease机制
Lease机制,翻译过来即是租约机制,是一种在分布式系统常用的协议,是维护分布式系统数据一致性的一种常用工具。
Lease机制有以下几个特点:
- Lease是颁发者对一段时间内数据一致性的承诺;
- 颁发者发出Lease后,不管是否被接收,只要Lease不过期,颁发者都会按照协议遵守承诺;
- Lease的持有者只能在Lease的有效期内使用承诺,一旦Lease超时,持有者需要放弃执行,重新申请Lease。
以租车为例:
3.7.2 Lease机制解决了什么问题
分布式系统中,如何确认一个节点是否工作正常?如果有5副本1-5。其中1号为主副本。
在分布式中最直观的处理方法是在每个副本与主副本维护一个心跳,期望通过心跳是否存在而判断对方是否依旧存活。
心跳方法其实根本无法解决分布式下节点是否正常的这个的这个问题。考虑如下场景:
-
在某个时刻Node1主节点突然出现网络抖动或者网络中断情况(注意:不是宕机),导致从节点无法接受到心跳.
-
会在剩下的副节点中选取一当主节点.
主要解决思路有四种:
- 设计能容忍双主的分布式协议
- Raft协议-通过Term版本高的同步低的.
- 用lease机制
- 涉及去中心化-Gossip协议
3.7.2 Lease的原理
-
引入中心节点负责下发Lease
-
出现网络问题
在01:05期间如果出现网络抖动导致其他节点申请Lease会申请失败, 因为中心节点在01:10之前都会承认有主节点,不允许其他节点在申请Lease
-
如果网络恢复
-
如果到01:10时间,主节点会进行续约操作,然后在下发新的Lease
-
如果主节点宕机,其他节点申请Lease也会失败,承认主节点存在
-
副节点申请Lease,申请成功. 因为Lease过期
3.7.3 lease的容错
- 主节点宕机
lease机制天生即可容忍网络、lease接收方的出错,时间即Lease剩余过期时长 - 中心节点异常
颁发者宕机可能使得全部节点没有lease,系统处于不可用状态,解决的方法就是使用一个小集群而不是单一节点作为颁发者。 - 时差问题
中心节点与主节点之间的时钟可能也存在误差,只需要中心节点考虑时钟误差即可。
lease时间长短一般取经验值1-10秒即可。太短网络压力大,太长则收回承诺时间过长影响可用性。
3.7.4 应用
- GFS(Google 文件系统)中,Master通过lease机制决定哪个是主副本,lease在给各节点的心跳响应消息中携带。收不到心跳时,则等待lease过期,再颁发给其他节点。
- chubby中,paxos选主后,从节点会给主颁发lease,在期限内不选其他节点为主。另一方面,主节点给每个client节点发送lease,用于判断client死活。
4. 分布式系统设计策略
在分布式环境下,有几个问题是普遍关心的.
- 如何检测当前节点还活着?
- 如何保障高可用?
- 容错处理
- 负载均衡
4.1 心跳检测
在分布式环境中,我们提及过存在非常多的节点(Node)。那么就有一个非常重要的问题,如何检测一个节点出现了故障乃至无法工作了?
通常解决这一问题是采用心跳检测的手段,如同通过仪器对病人进行一些检测诊断一样。
心跳顾名思义,就是以固定的频率向其他节点汇报当前节点状态的方式。收到心跳,一般可以认为一个节点和现在的网络是良好的。当然,心跳汇报时,一般也会携带一些附加的状态、元数据信息,以便管理
若Server没有收到Node3的心跳时,Server认为Node3失联。但是失联是失去联系,并不确定是否是Node3故障,有可能是Node3处于繁忙状态,导致调用检测超时;也有可能是Server与Node3之间链路出现故障或闪断。所以心跳不是万能的,收到心跳可以确认节点正常,但是收不到心跳也不能认为该节点就已经宣告“死亡”。此时,可以通过一些方法帮助Server做决定: 周期检测心跳机制、累计失效检测机制。
-
周期检测心跳机制
Server端每间隔 t 秒向Node集群发起监测请求,设定超时时间,如果超过超时时间,则判断“死 亡”。可以把该节点踢出集群
-
累计失效检测机制
在周期检测心跳机制的基础上,统计一定周期内节点的返回情况(包括超时及正确返回),以此计算节点的“死亡”概率。另外,对于宣告“濒临死亡”的节点可以发起有限次数的重试,以作进一步判断。如果超过次数则可以把该节点踢出集群
4.2 高可用
4.2.1 高可用HA设计
高可用(High Availability)是系统架构设计中必须考虑的因素之一,通常是指,经过设计来减少系统不能提供服务的时间 .
可用性 | 一年中可故障时长 | 一天中可故障时长 |
---|---|---|
90% | 36.5天 | 144分钟 |
99% | 3.6天 | 14.4分钟 |
99.9% | 8.8小时 | 86.4秒 |
99.99% | 52.6分钟 | 8.6秒 |
99.999% | 5.3分钟 | 860毫秒 |
99.9999% | 31.5秒 | 86毫秒 |
系统高可用性的常用设计模式包括三种:主备(Master-SLave)、互备(Active-Active)和集群(Cluster)模式。
主备模式
主备模式就是Active-Standby模式,当主机宕机时,备机接管主机的一切工作,待主机恢复正常后,按使用者的设定以自动(热备)或手动(冷备)方式将服务切换到主机上运行。在数据库部分,习惯称之为MS模式。MS模式即Master/Slave模式,这在数据库高可用性方案中比较常用,如MySQL、Redis等就采用MS模式实现主从复制。保证高可用。互备模式
互备模式指两台主机同时运行各自的服务工作且相互监测情况。在数据库高可用部分,常见的互备是MM模式。MM模式即Multi-Master模式,指一个系统存在多个master,每个master都具有read-write能力,会根据时间戳或业务逻辑合并版本。集群模式
集群模式是指有多个节点在运行,同时可以通过主控节点分担服务请求。集群模式需要解决主控节点本身的高可用问题,一般采用主备模式。
4.2.2 高可用HA下"脑裂问题"
- 什么是脑裂.
在高可用(HA)系统中,当联系两个节点的"心跳线"断开时(即两个节点断开联系时),本来为一个整体、动作协调的HA系统,就分裂成为两个独立的节点(即两个独立的个体)。由于相互失去了联系,都以为是对方出了故障,两个节点上的HA软件像"裂脑人"一样,"本能"地争抢"共享资源"、争起"应用服务"。就会发生严重后果:
- 共享资源被瓜分、两边"服务"都起不来了;
- 两边"服务"都起来了,但同时读写"共享存储",导致数据损坏(常见如数据库轮询着的联机日志出错)
两个节点相互争抢共享资源,结果会导致系统混乱,数据损坏。对于无状态服务的HA,无所谓脑裂不脑裂,但对有状态服务(比如MySQL)的HA,必须要严格防止脑裂
- 脑裂出现的原因
一般来说,裂脑的发生,有以下几种原因:
- 高可用服务器各节点之间心跳线链路发生故障,导致无法正常通信。
- 因网卡及相关驱动坏了,ip配置及冲突问题(网卡直连)。
- 因心跳线间连接的设备故障(网卡及交换机)。
- 因仲裁的机器出问题(采用仲裁的方案)。
- 高可用服务器上开启了iptables防火墙阻挡了心跳消息传输。
- 高可用服务器上心跳网卡地址等信息配置不正确,导致发送心跳失败。
- 其他服务配置不当等原因,如心跳方式不同,心跳广插冲突、软件Bug等。
- 脑裂预防方案
- 添加冗余的心跳线 [即冗余通信的方法]
同时用两条心跳线路 (即心跳线也HA),这样一条线路坏了,另一个还是好的,依然能传送心跳消息,尽量减少"脑裂"现象的发生几率。 - 仲裁机制
当两个节点出现分歧时,由第3方的仲裁者决定听谁的。这个仲裁者,可能是一个锁服务,一个共享盘或者其它什么东西 - Lease机制
- 隔离(Fencing)机制
- 共享存储fencing:确保只有一个Master往共享存储中写数据。
- 客户端fencing:确保只有一个Master可以响应客户端的请求。
- Slave fencing:确保只有一个Master可以向Slave下发命令
4.3 容错性
容错顾名思义就是IT系统对于错误包容的能力
容错的处理是保障分布式环境下相应系统的高可用或者健壮性,一个典型的案例就是对于缓存穿透 问题的解决方案。我们来具体看一下这个例子,如图所示
问题描述:
我们在项目中使用缓存通常都是先检查缓存中是否存在,如果存在直接返回缓存内容,如果不存在就直接查询数据库然后再缓存查询结果返回。这个时候如果我们查询的某一个数据在缓存中一直不存在,就会造成每一次请求都查询DB,这样缓存就失去了意义,在流量大时,或者有人恶意攻击如频繁发起为id为“-1”的条件进行查询,可能DB就挂掉了
那这种问题有什么好办法解决呢?
- 临时存放null值
-
使用布隆过滤器
4.4 负载均衡
负载均衡:其关键在于使用多台集群服务器共同分担计算任务,把网络请求及计算分配到集群可用的不同服务器节点上,从而达到高可用性及较好的用户操作体验。
如图,不同的用户client1、client2、client3访问应用,通过负载均衡器分配到不同的节点。
负载均衡器有硬件解决方案,也有软件解决方案。硬件解决方案有著名的F5,软件有LVS、HAProxy、Nginx等。
以Nginx为例,负载均衡有以下6种策略:
方式 | 说明 |
---|---|
轮询 | 默认方式,每个请求会按时间顺序逐一分配到不同的后端服务器 |
weight | 权重方式,在轮询策略的基础上指定轮询的几率,权重越大,接受请求越多 |
ip_hash | 依据ip分配方式,相同的客户端的请求一直发送到相同的服务器,以保证session会话 |
least_conn | 最少连接方式,把请求转发给连接数较少的后端服务器 |
fair(第三方) | 响应时间方式,按照服务器端的响应时间来分配请求,响应时间短的优先分配 |
url_hash(第三方) | 依据URL分配方式,按访问url的hash结果来分配请求,使每个url定向到同一个后端服务器 |
5. 分布式架构服务调用
5.1 服务调用
和传统的单体架构相比,分布式多了一个远程服务之间的通信,不管是 soa 还是微服务,他们本质上都是对于业务服务的提炼和复用。那么远程服务之间的调用才是实现分布式的关键因素。
5.2 实现方式
5.2.1 HTTP 应用协议的通信框架
- HttpURLConnection
java 原生 HttpURLConnection是基于http协议的,支持get,post,put,delete等各种请求方式,最常用的就是get和post - Apache Common HttpClient
HttpClient 是Apache Common 下的子项目,可以用来提供高效的、最新的、功能丰富的支持HTTP 协议的客户端编程工具包,并且它支持 HTTP 协议最新的版本。
- 实现了所有 HTTP 的方法(GET,POST,PUT,HEAD 等)
- 支持 HTTPS 协议
- 支持代理服务器等
- OKhttp3
OKHttp是一个当前主流的网络请求的开源框架, 用于替代HttpUrlConnection和Apache HttpClient
- 支持http2.0,对一台机器的请求共享一个socket。
- 采用连接池技术,可以有效的减少Http连接数量。
- 无缝集成GZIP压缩技术。
- 支持Response Cache,避免重复请求
- 域名多IP支持
- RestTemplate
Spring RestTemplate 是 Spring 提供的用于访问 Rest 服务的客户端,RestTemplate 提供了多种便捷访问远程Http服务的方法,能够大大提高客户端的编写效率,所以很多客户端比如 Android或者第三方服务商都是使用 RestTemplate 请求 restful 服务。
- 面向 URL 组件,必须依赖于主机 + 端口 + URI
- RestTemplate 不依赖于服务接口,仅关注 REST 响应内容
- Spring Cloud Feign
5.2.2 RPC 框架
RPC全称为remote procedure call,即远程过程调用。借助RPC可以做到像本地调用一样调用远程服务,是一种进程间的通信方式. 。常见的RPC框架有一下几种.
-
Java RMI
Java RMI(Romote Method Invocation)是一种基于Java的远程方法调用技术,是Java特有的一种RPC实现。
-
Hessian
Hessian是一个轻量级的remoting onhttp工具,使用简单的方法提供了RMI的功能. 相比WebService,Hessian更简单、快捷。采用的是二进制RPC协议,因为采用的是二进制协议,所以它很适合于发送二进制数据。
-
Dubbo
Dubbo是阿里巴巴公司开源的一个高性能优秀的服务框架,使得应用可通过高性能的 RPC 实现服务的输出和输入功能,可以和Spring框架无缝集成。Dubbo是一款高性能、轻量级的开源Java RPC框架,它提供了三大核心能力:面向接口的远程方法调用,智能容错和负载均衡,以及服务自动注册和发现。
-
gRPC
gRPC是由Google公司开源的一款高性能的远程过程调用(RPC)框架,可以在任何环境下运行。该框架提供了负载均衡,跟踪,智能监控,身份验证等功能,可以实现系统间的高效连接。
5.3 跨域调用
5.3.1 跨域
在分布式系统中, 会有调用其他业务系统,导致出现跨域问题,跨域实质上是浏览器的一种保护处理。如果产生了跨域,服务器在返回结果时就会被浏览器拦截(注意:此时请求是可以正常发起的,只是浏览器对其进行了拦截),导致响应的内容不可用. 产生跨域的几种情况有一下:
当前页面URL | 被请求页面URL | 是否跨域 | 原因 |
---|---|---|---|
http://www.lagou.com/ | http://www.lagou.com/index.html | 否 | 同源(协议, 域名, 端口号相同) |
http://www.lagou.com/ | https://www.lagou.com/index.html | 跨域 | 协议不同 |
(http/https)http://www.lagou.com/ | http://www.baidu.com/ | 跨域 | 主域名不同 |
(lagou/baidu)http://www.lagou.com/ | http://kaiwu.lagou.com/ | 跨域 | 子域名不同 |
(www/kaiwu)http://www.lagou.com:8080 | http://www.lagou.com:8090 | 跨域 | 端口号不同(8080/8090) |
5.3.2 常见的解决方案
- 使用jsonp解决网站跨域
缺点:不支持post请求,代码书写比较复杂 - 使用HttpClient内部转发
- 使用设置响应头允许跨域
response.setHeader(“Access-Control-Allow-Origin”, “*”); 设置响应头允许跨域 - 基于Nginx搭建企业级API接口网关
- 使用Zuul搭建微服务API接口网关
Zuul是spring cloud中的微服务网关。网关: 是一个网络整体系统中的前置门户入口。请求首先通过网关,进行路径的路由,定位到具体的服务节点上。可以使用zuul的过滤器的请求转发去解决跨域问题
6.分布式服务治理
6.1 服务协调
分布式协调技术主要用来解决分布式环境当中多个进程之间的同步控制,让他们有序的去访问某种临界资源,防止造成"脏数据"的后果。
分布式锁也就是我们分布式协调技术实现的核心内容。
分布式锁两种实现方式:
- 基于缓存(Redis等)实现分布式锁
- 获取锁的时候,使用setnx加锁,并使用expire命令为锁添加一个超时时间,超过该时间则自动释放锁,锁的value值为一个随机生成的UUID, 释放锁的时候进行判断。
- 获取锁的时候还设置一个获取的超时时间,若超过这个时间则放弃获取锁。
- 释放锁的时候,通过UUID判断是不是该锁,若是该锁,则执行delete进行锁释放。
SETNX :set一个key为value的字符串,返回1;若key存在,则什么都不做,返回0。
expire: 为key设置一个超时时间,单位为second,超过这个时间锁会自动释放,避免死锁。
delete :删除key
- ZooKeeper是一个为分布式应用提供一致性服务的开源组件,它内部是一个分层的文件系统目录树结构,规定同一个目录下只能有一个唯一文件名, 基于ZooKeeper实现分布式锁的步骤如下:
- 创建一个目录mylock
- 线程A想获取锁就在mylock目录下创建临时顺序节点
- 获取mylock目录下所有的子节点,然后获取比自己小的兄弟节点,如果不存在,则说明当前线程顺序号最小,获得锁
- 线程B获取所有节点,判断自己不是最小节点,设置监听比自己次小的节点
- 线程A处理完,删除自己的节点,线程B监听到变更事件,判断自己是不是最小的节点,如果是则获得锁
6.2 服务削峰
6.2.1 为什么要削峰
主要是还是来自于互联网的业务场景,例如,春节火车票抢购,大量的用户需要同一时间去抢购;以及大家熟知的阿里双11秒杀, 短时间上亿的用户涌入,瞬间流量巨大(高并发)
6.2.2 流量削峰方案
削峰从本质上来说就是更多地延缓用户请求,以及层层过滤用户的访问需求,遵从“最后落地到数据库的请求数要尽量少”的原则。
-
消息队列解决削峰
要对流量进行削峰,最容易想到的解决方案就是用消息队列来缓冲瞬时流量,把同步的直接调用转换成异步的间接推送,中间通过一个队列在一端承接瞬时的流量洪峰,在另一端平滑地将消息推送出去
消息队列中间件主要解决应用耦合,异步消息, 流量削锋等问题。常用消息队列系统:目前在生产环境,使用较多的消息队列有 ActiveMQ、RabbitMQ、 ZeroMQ、Kafka、RocketMQ 等。
在这里,消息队列就像“水库”一样,拦截上游的洪水,削减进入下游河道的洪峰流量,从而达到减免洪水灾害的目的。 -
流量削峰漏斗:层层削峰
分层过滤其实就是采用“漏斗”式设计来处理请求的,这样就像漏斗一样,尽量把数据量和请求量一层一层地过滤和减少了。如下图所示:
分层过滤的核心思想
- 通过在不同的层次尽可能地过滤掉无效请求。
- 通过CDN过滤掉大量的图片,静态资源的请求。
- 再通过类似Redis这样的分布式缓存过滤请求
分层过滤的基本原则
- 对写数据进行基于时间的合理分片,过滤掉过期的失效请求。
- 对写请求做限流保护,将超出系统承载能力的请求过滤掉。
- 涉及到的读数据不做强一致性校验,减少因为一致性校验产生瓶颈的问题。
- 对写数据进行强一致性校验,只保留最后有效的数据。
6.3 服务降级
6.3.1 什么是服务降级
当服务器压力剧增的情况下,根据实际业务情况及流量,对一些服务和页面有策略的不处理或换种简单的方式处理,从而释放服务器资源以保证核心服务正常运作或高效运作
整个架构整体的负载超出了预设的上限阈值或即将到来的流量预计将会超过预设的阈值时,为了保证重要或基本的服务能正常运行,我们可以将一些 不重要 或 不紧急 的服务或任务进行服务的 延迟使用或 暂停使用
6.3.2 降级策略
当触发服务降级后,新的交易再次到达时,我们该如何来处理这些请求呢?从分布式,微服务架构全局的视角来看,降级处理方案:
- 页面降级 —— 可视化界面禁用点击按钮、调整静态页面
- 延迟服务 —— 如定时任务延迟处理、消息入MQ后延迟处理
- 写降级 —— 直接禁止相关写操作的服务请求
- 读降级 —— 直接禁止相关读的服务请求
- 缓存降级 —— 使用缓存方式来降级部分读频繁的服务接口
针对后端代码层面的降级处理策略,则我们通常使用以下几种处理措施进行降级处理:
- 抛异常
- 返回NULL
- 调用Mock数据
- 调用Fallback处理逻辑
6.3.3 分级降级
结合服务能否降级的优先原则,并根据台风预警(都属于风暴预警)的等级进行参考设计,可将分布式服务架构的所有服务进行故障风暴等级划分为以下四种:
6.4 服务限流
6.4.1 什么是服务限流
限流并非新鲜事,在生活中亦无处不在,下面例举一二
- 博物馆:限制每天参观总人数以保护文物
- 地铁安检:有若干安检口,乘客依次排队,工作人员根据安检快慢决定是否放人进去。遇到节假日,可以增加安检口来提高处理能力,同时增加排队等待区长度。
- 水坝泄洪:水坝可以通过闸门控制泄洪速度。
以上"限流"例子,可以让服务提供者稳定的服务客户。
限流的目的是通过对并发访问请求进行限速或者一个时间窗口内的的请求数量进行限速来保护系统,一旦达到限制速率则可以拒绝服务、排队或等待
6.4.2 多维度限流
在请求到达目标服务接口的时候, 可以使用多维度的限流策略,这样就可以让系统平稳度过瞬间来临的并发
6.4.3 限流算法
-
限流算法-计数器(固定窗口)
计数器限制每一分钟或者每一秒钟内请求不能超过一定的次数,在下一秒钟计数器清零重新计算
存在问题:
客户端在第一分钟的59秒请求100次,在第二分钟的第1秒又请求了100次, 2秒内后端会受到200次请求的压力,形成了流量突刺
-
限流算法-计数器(滑动窗口)
滑动窗口其实是细分后的计数器,它将每个时间窗口又细分成若干个时间片段,每过一个时间片段,整个时间窗口就会往右移动一格
时间窗口向右滑动一格,这时这个时间窗口其实已经打满了100次,客户端将被拒绝访问,时间窗口划分的越细,滑动窗口的滚动就越平滑,限流的效果就会越精确
-
限流算法-漏桶
漏桶算法类似一个限制出水速度的水桶,通过一个固定大小FIFO队列+定时取队列元素的方式实现,请求进入队列后会被匀速的取出处理(桶底部开口匀速出水),当队列被占满后后来的请求会直接拒绝(水倒的太快从桶中溢出来)
优点是可以削峰填谷,不论请求多大多快,都只会匀速发给后端,不会出现突刺现象,保证下游服务正常运行 , 缺点就是在桶队列中的请求会排队,响应时间拉长
-
限流算法-令牌桶
令牌桶算法是以一个恒定的速度往桶里放置令牌(如果桶里的令牌满了就废弃),每进来一个请求去桶里找令牌,有的话就拿走令牌继续处理,没有就拒绝请求
令牌桶的优点是可以应对突发流量,当桶里有令牌时请求可以快速的响应,也不会产生漏桶队列中的等待时间, 缺点就是相对漏桶一定程度上减小了对下游服务的保护
6.5 服务熔断
6.5.1 什么是服务熔断
【熔断】, 熔断这一概念来源于电子工程中的断路器(Circuit Breaker)。在互联网系统中,当下游服务因访问压力过大而响应变慢或失败,上游服务为了保护系统整体的可用性,可以暂时切断对下游服务的调用。这种牺牲局部,保全整体的措施就叫做熔断。
如果不采取熔断措施,我们的系统会怎样呢?
举例说明:
当前系统中有A,B,C三个服务,服务A是上游,服务B是中游,服务C是下游. 它们的调用链如下:
一旦下游服务C因某些原因变得不可用,积压了大量请求,服务B的请求线程也随之阻塞。线程资源逐渐耗尽,使得服务B也变得不可用。紧接着,服务 A也变为不可用,整个调用链路被拖垮。
像这种调用链路的连锁故障,叫做雪崩。
6.5.2 熔断机制
在这种时候,就需要我们的熔断机制来挽救整个系统。
这里需要解释两点:
- 开启熔断
- 在固定时间窗口内,接口调用超时比率达到一个阈值,会开启熔断。
- 进入熔断状态后,后续对该服务接口的调用不再经过网络,直接执行本地的默认方法,达到服务降级的效果。
- 熔断恢复
- 熔断不可能是永久的。当经过了规定时间之后,服务将从熔断状态回复过来,再次接受调用方的远程调用
6.5.3 熔断机制实现
- Spring Cloud Hystrix
Spring Cloud Hystrix是基于Netflix的开源框架Hystrix实现,该框架实现了服务熔断、线程隔离等一系列服务保护功能。
对于熔断机制的实现,Hystrix设计了三种状态:
- 熔断关闭状态(Closed)
服务没有故障时,熔断器所处的状态,对调用方的调用不做任何限制。 - 熔断开启状态(Open)
在固定时间内(Hystrix默认是10秒),接口调用出错比率达到一个阈值(Hystrix默认为50%),会进入熔断开启状态。进入熔断状态后, 后续对该服务接口的调用不再经过网络,直接执行本地的fallback方法。 - 半熔断状态(Half-Open)
在进入熔断开启状态一段时间之后(Hystrix默认是5秒),熔断器会进入半熔断状态。所谓半熔断就是尝试恢复服务调用,允许有限的流量调用该服务,并监控调用成功率。如果成功率达到预期,则说明服务已恢复,进入熔断关闭状态;如果成功率仍旧很低,则重新进入熔断开启状态
三个状态的转化关系如下图:
- Sentinel
Sentinel 和 Hystrix 的原则是一致的: 当调用链路中某个资源出现不稳定,例如,表现为timeout,异常比例升高的时候,则对这个资源的调用进行限制,并让请求快速失败,防止避免影响到其它的资源,最终产生雪崩的效果。
Sentinel 熔断手段:
- 通过并发线程数进行限制
- 通过响应时间对资源进行降级
- 系统负载保护
6.6 服务链路追踪
6.6.1 什么是链路追踪
分布式微服务架构上通过业务来划分服务的,通过REST调用对外暴露的一个接口,可能需要很多个服务协同才能完成这个接口功能,如果链路上任何一个服务出现问题或者网络超时,都会形成导致接口调用失败。随着业务的不断扩张,服务之间互相调用会越来越复杂。
随着服务的越来越多,对调用链的分析会越来越复杂。它们之间的调用关系也许如下:
分布式链路追踪(Distributed Tracing),也叫 分布式链路跟踪,分布式跟踪,分布式追踪 等等. 其实就是将一次分布式请求还原成调用链路。显示的在后端查看一次分布式请求的调用情况,比如各个节点上的耗时、请求具体打到了哪台机器上、每个服务节点的请求状态等等。
6.6.2 链路跟踪具备的功能
-
故障快速定位
通过调用链跟踪,一次请求的逻辑轨迹可以用完整清晰的展示出来。开发中可以在业务日志中添加调用链ID,可以通过调用链结合业务日志快速定位错误信息。
-
各个调用环节的性能分析
在调用链的各个环节分别添加调用时延,可以分析系统的性能瓶颈,可以进行针对性的优化。通过分析各个环节的平均时延,QPS等信息,可以找到系统的薄弱环节,对一些模块做调整。
数据分析
调用链绑定业务后查看具体每条业务数据对应的链路问题,可以得到用户的行为路径,经过了哪些服务器上的哪个服务,汇总分析应用在很多业务场景。生成服务调用拓扑图
通过可视化分布式系统的模块和他们之间的相互联系来理解系统拓扑。点击某个节点会展示这个模块的详情,比如它当前的状态和请求数量。
6.6.3 链路跟踪设计原则
- 设计目标
- 低侵入性,应用透明
- 低损耗
- 大范围部署,扩展性
埋点和生成日志
埋点即系统在当前节点的上下文信息,可以分为客户端埋点、服务端埋点,以及客户端和服务端双向型埋点。埋点日志通常要包含以下内容:
TraceId、RPCId、调用的开始时间,调用类型,协议类型,调用方ip和端口,请求的服务名等信息;调用耗时,调用结果,异常信息,消息报文等抓取和存储日志
日志的采集和存储有许多开源的工具可以选择,一般来说,会使用离线+实时的方式去存储日志,主要是分布式日志采集的方式。典型的解决方案如Flume结合Kafka。分析和统计调用链数据
一条调用链的日志散落在调用经过的各个服务器上,首先需要按 TraceId 汇总日志,然后按照RpcId 对调用链进行顺序整理。调用链数据不要求百分之百准确,可以允许中间的部分日志丢失。计算和展示
汇总得到各个应用节点的调用链日志后,可以针对性的对各个业务线进行分析。需要对具体日志进行整理,进一步储存在HBase或者关系型数据库中,可以进行可视化的查询。
6.6.4 链路跟踪Trace模型
Trace调用模型,主要有以下概念:
术语 | 解释 |
---|---|
Trace | 一次完整的分布式调用跟踪链路 |
Span | 跟踪服务调用基本结构,表示跨服务的一次调用; 多span形成树形结构,组合成一次Trace追踪记录 |
Annotation | 在span中的标注点,记录整个span时间段内发生的事件: Cs CLIENT_SEND,客户端发起请求 Cr CLIENT_RECIEVE,客户端收到响应 Sr SERVER_RECIEVE,服务端收到请求 Ss SERVER_SEND,服务端发送结果 |
BinaryAnnotation | 可以认为是特殊的Annotation,用户自定义事件: Event 记录普通事件 Exception 记录异常事件 |
Client && Server:对于跨服务的一次调用,请求发起方为client,服务提供方为Server各术语在一次分布式调用中,关系如下图所示