简单分布式知识整理

简单分布式知识整理


简单分布式知识整理


前言

未整理完

一、基础理论

·分布式系统是一个硬件或软件组件分布在不同的网络计算机上,彼此之间仅仅通过消息传递进行通信和协调的系统。
·简单来说就是一群独立计算机集合共同对外提供服务,但是对于系统的用户来说,就像是一台计算机在提供服务一样。分布式意味着可以采用更多的普通计算机(相对于昂贵的大型机)组成分布式集群对外提供服务。计算机越多,CPU、内存、存储资源等也就越多,能够处理的并发访问量也就越大。

·分布式的种类

1、分布式应用和服务(微服务)

将应用和服务进行分层和分割,然后将应用和服务模块进行分布式部署。这样做不仅可以提高并发访问能力、减少数据库连接和资源消耗,还能使不同应用复用共同的服务,使业务易于扩展。

2、分布式数据和存储(分库分表)

大型网站常常需要处理海量数据,单台计算机往往无法提供足够的内存空间,可以对这些数据进行分布式存储。

3、分布式计算(云计算)

随着计算技术的发展,有些应用需要非常巨大的计算能力才能完成,如果采用集中式计算,需要耗费相当长的时间来完成。分布式计算将该应用分解成许多小的部分,分配给多台计算机进行处理。这样可以节约整体计算时间,大大提高计算效率。

·分布式的优点

1、可拓展性
又叫可伸缩性,主要强调“伸”;偶尔也强调“缩”。
在资源和用户数较大增长的情况下,系统性能仍能维持甚至提高。
通常表现为:
·利用硬件环境可以为更多的用户服务、而且响应更快
·通常通过增加更多/更快的处理器,能实现更可靠、更完善的服务
2、高性能
分布系统中的各个组成部分可以在并发的过程中被执行。这样一个复杂的问题可以拆解成多个简单的小问题,从而提升系统的吞吐量。
通常表现在:
·多个用户同时访问(和更新)资源
·多个服务进程同时运行,相互协作
3、高可用
一台服务器的系统崩溃不会影响到其他的服务器。从而可以保障系统的高可用。
通常表现在:
·服务模块化,订单系统、商品系统互不影响

·分布式的缺点

1、复杂度高
由于分布在多台服务器上,出现故障的时候排除和诊断问题难度较高。
2、成本高
在运维和硬件成本上成本增高,服务器之间需要通过网络通信,网络基础设置问题,包括传输、高负载、信息丢失问题。
3、服务的可用性要求高
由于是分布式,不在同一台机器上,我们需要实现分布式数据一致性和服务的可用性。而这个过程是通过网络传输的,而网络存在很复杂的状态,尤其是弱网或断网的情况。这样就导致了这个难度非常大,往往需要在数据一致性和可用性上做一个平衡。

·CAP理论(布鲁尔定理(Brewer’s theorem)

一个分布式系统最多只能同时满足一致性(Consistency)、可用性(Availability)和分区容错性(Partition tolerance)这三项中的两项。
在这里插入图片描述

·一致性(Consistency)
一致性指“all nodes see the same data at the same time”,即更新操作成功并返回客户端完成后,所有节点在同一时间的数据完全一致。
·可用性(Availability)
可用性指“Reads and writes always succeed”,即服务一直可用,而且是正常响应时间。
·分区容错性(Partition tolerance)
分区容错性指“the system continues to operate despite arbitrary message loss or failure of part of the system”,即分布式系统在遇到某节点或网络分区故障的时候,仍然能够对外提供满足一致性和可用性的服务。
·CAP权衡
·CA
这种情况在分布式系统中几乎是不存在的。首先在分布式环境下,网络分区是一个自然的事实。因为分区是必然的,所以如果舍弃P,意味着要舍弃分布式系统。那也就没有必要再讨论CAP理论了。
所以,对于一个分布式系统来说。P是一个基本要求,CAP三者中,只能在CA两者之间做权衡,并且要想尽办法提升P。
·CP
一旦发生网络故障或者消息丢失等情况,在不能保证数据一致性的情况下,系统就不可用。就要牺牲用户的体验,等待所有数据全部一致了之后再让用户访问系统。
那么什么情况下会使用CP模型呢?大家可以想象一下,不难想象一些数据库系统以及一些金融系统对数据的准确性要求很高。如Redis、HBase等,还有分布式系统中常用的Zookeeper也是在CAP三者之中选择优先保证CP的。
·AP
一旦网络问题发生,节点之间可能会失去联系。为了保证高可用,需要在用户访问时可以马上得到返回,则每个节点只能用本地数据提供服务,而这样会导致全局数据的出现不一致性的情况。
那么什么情况下会选择这种模型呢?其实很多系统在可用性方面会做很多事情来保证系统的全年可用性可以达到N个9,所以,对于很多业务系统来说,比如淘宝的购物,12306的买票等。都是在可用性和一致性之间舍弃了一致性而选择可用性。(这里的 N 个 9 就是对可用性的一个描述,叫做 SLA,即服务水平协议。)

·在分布式系统中,无法同时满足 CAP 定律中的“一致性”“可用性”和“分区容错性”三者。 对 CAP 的定义进行了更明确的声明:

·Consistency,一致性被称为原子对象,任何的读写都应该看起来是“原子”的,或串行的,写后面的读一定能读到前面写的内容,所有的读写请求都好像被全局排序;
·Availability,对任何非失败节点都应该在有限时间内给出请求的回应(请求的可终止性);
·Partition Tolerance,允许节点之间丢失任意多的消息,当网络分区发生时,节点之间的消息可能会完全丢失。

·Base 也是根据 AP 来扩展的。

·BASE理论

BASE介绍
BASE理论是对CAP理论的延伸,核心思想是即使无法做到强一致性(Strong Consistency,CAP的一致性就是强一致性),但应用可以采用适合的方式达到最终一致性(Eventual Consitency)。
CAP中提到的一致性是强一致性,所谓“牺牲一致性”指牺牲强一致性保证弱一致性。
· BASE是指基本可用(Basically Available)、软状态( Soft State)、最终一致性( Eventual Consistency)。
·基本可用(Basically Available)
基本可用是指分布式系统在出现故障的时候,允许损失部分可用性,即:保证核心可用。
如双11大促时,为了应对核心应用访问量激增,部分用户可能会被引导到降级页面,部分服务层也可能只提供降级服务,这就是损失部分可用性的体现。
·软状态( Soft State)
软状态是指允许系统存在中间状态,而该中间状态不会影响系统整体可用性。
软状态本质上是一种弱一致性,允许的软状态不能违背“基本可用”的要求。如分布式存储中一般一份数据至少会有三个副本,允许不同节点间副本同步的延时(某些时刻副本数低于3的中间状态)这就叫软状态。
·最终一致性( Eventual Consistency)
系统中的所有数据副本经过一定时间后,最终能够达到一致的状态。
通过上面软状态的终极目标是最终一致性。如,分布式存储的副本数最终会达到稳定状态。 DNS是一个典型的最终一致性系统。

·分布式系统解决了传统单体架构的单点问题和性能容量问题,另一方面也带来了很多新的问题,其中一个问题就是多节点的时间同步问题:不同机器上的物理时钟难以同步,导致无法区分在分布式系统中多个节点的事件时序。 没有全局时钟,绝对的内部一致性是没有意义的,一般来说,我们讨论的一致性都是外部一致性,而外部一致性主要指的是多并发访问时更新过的数据如何获取的问题。 和全局时钟相对的,是逻辑时钟,逻辑时钟描绘了分布式系统中事件发生的时序,是为了区分现实中的物理时钟提出来的概念。

一般情况下我们提到的时间都是指物理时间,但实际上很多应用中,只要所有机器有相同的时间就够了,这个时间不一定要跟实际时间相同。更进一步解释:如果两个节点之间不进行交互,那么它们的时间甚至都不需要同步。 因此问题的关键点在于节点间的交互要在事件的发生顺序上达成一致,而不是对于时间达成一致。 逻辑时钟的概念也被用来解决分布式一致性问题。

·在实际工程实践中,最终一致性存在以下五类主要变种。

最终一致表现形式

1.因果一致性:
因果一致性是指,如果进程A在更新完某个数据项后通知了进程B,那么进程B之后对该数据项的访问都应该能够获取到进程A更新后的最新值,并且如果进程B要对该数据项进行更新操作的话,务必基于进程A更新后的最新值,即不能发生丢失更新情况。与此同时,与进程A无因果关系的进程C的数据访问则没有这样的限制。
2.读己之所写:
读己之所写是指,进程A更新一个数据项之后,它自己总是能够访问到更新过的最新值,而不会看到旧值。也就是说,对于单个数据获取者而言,其读取到的数据一定不会比自己上次写入的值旧。因此,读己之所写也可以看作是一种特殊的因果一致性。
3.会话一致性:
会话一致性将对系统数据的访问过程框定在了一个会话当中:系统能保证在同一个有效的会话中实现“读己之所写”的一致性,也就是说,执行更新操作之后,客户端能够在同一个会话中始终读取到该数据项的最新值。就是在你的一次访问中,执行更新操作之后,客户端能够在同一个会话中始终读取到该数据项的最新值。 实际开发中有分布式的 Session 一致性问题,可以认为是会话一致性的一个应用。
4.单调读一致性:
单调读一致性是指如果一个进程从系统中读取出一个数据项的某个值后,那么系统对于该进程后续的任何数据访问都不应该返回更旧的值。
5.单调写一致性:
单调写一致性是指,一个系统需要能够保证来自同一个进程的写操作被顺序地执行。

·在实际系统实践中,可以将其中的若干个变种互相结合起来,以构建一个具有最终一致性的分布式系统。为了解决分布式的一致性问题,在长期的研究探索过程中,涌现出了一大批经典的一致性协议和算法,其中比较著名的有我们上文提到的2PC(二阶段提交协议),3PC(三阶段提交协议)、Paxos算法、ZAB协议、RAFT协议、Gossip协议等。

·总结

总的来说,BASE理论面向的是大型高可用可扩展的分布式系统,和传统事务的ACID特性是相反的,它完全不同于ACID的强一致性模型,而是提出通过牺牲强一致性来获得可用性,并允许数据在一段时间内是不一致的,但最终达到一致状态。但同时,在实际的分布式场景中,不同业务单元和组件对数据一致性的要求是不同的,因此在具体的分布式系统架构设计过程中,ACID特性与BASE理论往往又会结合在一起使用。ACID 是数据库事务完整性的理论,CAP 是分布式系统设计理论,BASE 是 CAP 理论中 AP 方案的延伸。

二、分布式算法

· 一致性算法的目的是保证在分布式系统中,多数据副本节点数据一致性。主要包含一致性Hash算法,Paxos算法,Raft算法,ZAB算法等。

2.1 一致性Hash算法

一致性Hash算法是个经典算法,Hash环的引入是为解决单调性(Monotonicity)的问题;虚拟节点的引入是为了解决平衡性(Balance)问题。在分布式集群中,对机器的添加删除,或者机器故障后自动脱离集群这些操作是分布式集群管理最基本的功能。如果采用常用的hash(object)%N算法,那么在有机器添加或者删除后,很多原有的数据就无法找到了,这样严重的违反了单调性原则。

·一致性hash算法提出了在动态变化的Cache环境中,判定哈希算法好坏的四个定义:
1)· 平衡性(Balance): 平衡性是指哈希的结果能够尽可能分布到所有的缓冲中去,这样可以使得所有的缓冲空间都得到利用。很多哈希算法都能够满足这一条件。
2)· 单调性(Monotonicity): 单调性是指如果已经有一些内容通过哈希分派到了相应的缓冲中,又有新的缓冲加入到系统中。哈希的结果应能够保证原有已分配的内容可以被映射到原有的或者新的缓冲中去,而不会被映射到旧的缓冲集合中的其他缓冲区。(去新不去旧)
3)· 分散性(Spread): 在分布式环境中,终端有可能看不到所有的缓冲,而是只能看到其中的一部分。当终端希望通过哈希过程将内容映射到缓冲上时,由于不同终端所见的缓冲范围有可能不同,从而导致哈希的结果不一致,最终的结果是相同的内容被不同的终端映射到不同的缓冲区中。这种情况显然是应该避免的,因为它导致相同内容被存储到不同缓冲中去,降低了系统存储的效率。分散性的定义就是上述情况发生的严重程度。好的哈希算法应能够尽量避免不一致的情况发生,也就是尽量降低分散性。
4)· 负载(Load): 负载问题实际上是从另一个角度看待分散性问题。既然不同的终端可能将相同的内容映射到不同的缓冲区中,那么对于一个特定的缓冲区而言,也可能被不同的用户映射为不同 的内容。与分散性一样,这种情况也是应当避免的,因此好的哈希算法应能够尽量降低缓冲的负荷。

·上面的简单的一致性hash的方案在某些情况下但依旧存在问题: 一个节点宕机之后,数据需要落到距离他最近的节点上,会导致下个节点的压力突然增大,可能导致雪崩,整个服务挂掉。
·虚拟节点解决数据不平衡
“虚拟节点”( virtual node )是实际节点(机器)在 hash 空间的复制品( replica ),一实际个节点(机器)对应了若干个“虚拟节点”,这个对应个数也成为“复制个数”,“虚拟节点”在 hash 空间中以hash值排列。这个就解决了某个节点宕机之后,存储及流量压力并没有全部转移到某台机器上,而是分散到了多台节点上。解决了节点宕机可能存在的雪崩问题。当物理节点多的时候,虚拟节点多,这个的雪崩可能就越小。

·一致性Hash的应用

最主要用的场景是redis中多节点集群的负载中。

如果像是图片等资源存的位置是自己管理的集群,可以使用该方式进行负载。一是避免集群中服务器数量发生变化的时候,会发生大量拉取图片的请求无法直接命中而引起的雪崩,导致整体系统压力过大而崩溃;二是如果使用的是其他的负载均衡方式,可能会导致几乎所有的图片资源位置发生变动,在此期间系统可用性会变差。

集群经常需要变动时负载均衡方式可以采用该种方式,动态扩容,宕机等。

·2.2 Paxos算法

一种基于消息传递的分布式一致性算法。自Paxos问世以来就持续垄断了分布式一致性算法,Paxos这个名词几乎等同于分布式一致性, 很多分布式一致性算法都由Paxos演变而来。Paxos 算法是 Leslie Lamport(莱斯利·兰伯特open in new window)在 1990 年提出了一种分布式系统 共识 算法。这也是第一个被证明完备的共识算法(前提是不存在拜占庭将军问题,也就是没有恶意节点)。

·共识算法的作用是让分布式系统中的多个节点之间对某个提案(Proposal)达成一致的看法。提案的含义在分布式系统中十分宽泛,像哪一个节点是 Leader 节点、多个事件发生的顺序等等都可以是一个提案。
·Basic Paxos 算法:描述的是多节点之间如何就某个值(提案 Value)达成共识。
·Multi-Paxos 思想:描述的是执行多个 Basic Paxos 实例,就一系列值达成共识。Multi-Paxos 说白了就是执行多次 Basic Paxos ,核心还是 Basic Paxos 。

·Basic Paxos 中存在 3 个重要的角色:
·提议者(Proposer):也可以叫做协调者(coordinator),提议者负责接受客户端的请求并发起提案。提案信息通常包括提案编号 (Proposal ID) 和提议的值 (Value)。
·接受者(Acceptor):也可以叫做投票员(voter),负责对提议者的提案进行投票,同时需要记住自己的投票历史;
·学习者(Learner):如果有超过半数接受者就某个提议达成了共识,那么学习者就需要接受这个提议,并就该提议作出运算,然后将运算结果返回给客户端。

为了减少实现该算法所需的节点数,一个节点可以身兼多个角色。并且,一个提案被选定需要被半数以上的 Acceptor 接受。这样的话,Basic Paxos 算法还具备容错性,在少于一半的节点出现故障时,集群仍能正常工作。

·Multi Paxos 思想
Basic Paxos 算法的仅能就单个值达成共识,为了能够对一系列的值达成共识,我们需要用到 Basic Paxos 思想。⚠️注意:Multi-Paxos 只是一种思想,这种思想的核心就是通过多个 Basic Paxos 实例就一系列值达成共识。也就是说,Basic Paxos 是 Multi-Paxos 思想的核心,Multi-Paxos 就是多执行几次 Basic Paxos。不过,我们并不需要自己实现基于 Multi-Paxos 思想的共识算法,业界已经有了比较出名的实现。像 Raft 算法就是 Multi-Paxos 的一个变种,其简化了 Multi-Paxos 的思想,变得更容易被理解以及工程实现,实际项目中可以优先考虑 Raft 算法。

2.3 Raft 算法详解

·共识是可容错系统中的一个基本问题:即使面对故障,服务器也可以在共享状态上达成一致。因此共识算法的工作就是保持复制日志的一致性。服务器上的共识模块从客户端接收命令并将它们添加到日志中。它与其他服务器上的共识模块通信,以确保即使某些服务器发生故障。每个日志最终包含相同顺序的请求。一旦命令被正确地复制,它们就被称为已提交。每个服务器的状态机按照日志顺序处理已提交的命令,并将输出返回给客户端,因此,这些服务器形成了一个单一的、高度可靠的状态机。

·适用于实际系统的共识算法通常具有以下特性:
·安全。确保在非拜占庭条件(也就是上文中提到的简易版拜占庭)下的安全性,包括网络延迟、分区、包丢失、复制和重新排序。
·高可用。只要大多数服务器都是可操作的,并且可以相互通信,也可以与客户端进行通信,那么这些服务器就可以看作完全功能可用的。因此,一个典型的由五台服务器组成的集群可以容忍任何两台服务器端故障。假设服务器因停止而发生故障;它们稍后可能会从稳定存储上的状态中恢复并重新加入集群。
·一致性不依赖时序。错误的时钟和极端的消息延迟,在最坏的情况下也只会造成可用性问题,而不会产生一致性问题。

·在集群中大多数服务器响应,命令就可以完成,不会被少数运行缓慢的服务器来影响整体系统性能。
· 节点类型
·一个 Raft 集群包括若干服务器,以典型的 5 服务器集群举例。在任意的时间,每个服务器一定会处于以下三个状态中的一个:
·Leader:负责发起心跳,响应客户端,创建日志,同步日志。
·Candidate:Leader 选举过程中的临时角色,由 Follower 转化而来,发起投票参与竞选。
·Follower:接受 Leader 的心跳和日志同步数据,投票给 Candidate。
·在正常的情况下,只有一个服务器是 Leader,剩下的服务器是 Follower。Follower 是被动的,它们不会发送任何请求,只是响应来自 Leader 和 Candidate 的请求。

· 任期
·raft 算法将时间划分为任意长度的任期(term),任期用连续的数字表示,看作当前 term 号。每一个任期的开始都是一次选举,在选举开始时,一个或多个 Candidate 会尝试成为 Leader。如果一个 Candidate 赢得了选举,它就会在该任期内担任 Leader。如果没有选出 Leader,将会开启另一个任期,并立刻开始下一次选举。raft 算法保证在给定的一个任期最少要有一个 Leader。每个节点都会存储当前的 term 号,当服务器之间进行通信时会交换当前的 term 号;如果有服务器发现自己的 term 号比其他人小,那么他会更新到较大的 term 值。如果一个 Candidate 或者 Leader 发现自己的 term 过期了,他会立即退回成 Follower。如果一台服务器收到的请求的 term 号是过期的,那么它会拒绝此次请求。

· 日志
·entry:每一个事件成为 entry,只有 Leader 可以创建 entry。entry 的内容为<term,index,cmd>其中 cmd 是可以应用到状态机的操作。
·log:由 entry 构成的数组,每一个 entry 都有一个表明自己在 log 中的 index。只有 Leader 才可以改变其他节点的 log。entry 总是先被 Leader 添加到自己的 log 数组中,然后再发起共识请求,获得同意后才会被 Leader 提交给状态机。Follower 只能从 Leader 获取新日志和当前的 commitIndex,然后把对应的 entry 应用到自己的状态机中。

· 领导人选举
·raft 使用心跳机制来触发 Leader 的选举。如果一台服务器能够收到来自 Leader 或者 Candidate 的有效信息,那么它会一直保持为 Follower 状态,并且刷新自己的 electionElapsed,重新计时。Leader 会向所有的 Follower 周期性发送心跳来保证自己的 Leader 地位。如果一个 Follower 在一个周期内没有收到心跳信息,就叫做选举超时,然后它就会认为此时没有可用的 Leader,并且开始进行一次选举以选出一个新的 Leader。为了开始新的选举,Follower 会自增自己的 term 号并且转换状态为 Candidate。然后他会向所有节点发起 RequestVoteRPC 请求, Candidate 的状态会持续到以下情况发生:
赢得选举;其他节点赢得选举;一轮选举结束,无人胜出。

·赢得选举的条件是:一个 Candidate 在一个任期内收到了来自集群内的多数选票(N/2+1),就可以成为 Leader。
·在 Candidate 等待选票的时候,它可能收到其他节点声明自己是 Leader 的心跳,此时有两种情况:
·该 Leader 的 term 号大于等于自己的 term 号,说明对方已经成为 Leader,则自己回退为 Follower。
·该 Leader 的 term 号小于自己的 term 号,那么会拒绝该请求并让该节点更新 term。
·由于可能同一时刻出现多个 Candidate,导致没有 Candidate 获得大多数选票,如果没有其他手段来重新分配选票的话,那么可能会无限重复下去。raft 使用了随机的选举超时时间来避免上述情况。每一个 Candidate 在发起选举后,都会随机化一个新的选举超时时间,这种机制使得各个服务器能够分散开来,在大多数情况下只有一个服务器会率先超时;它会在其他服务器超时之前赢得选举。

·日志复制
一旦选出了 Leader,它就开始接受客户端的请求。每一个客户端的请求都包含一条需要被复制状态机(Replicated State Machine)执行的命令。Leader 收到客户端请求后,会生成一个 entry,包含<index,term,cmd>,再将这个 entry 添加到自己的日志末尾后,向所有的节点广播该 entry,要求其他服务器复制这条 entry。如果 Follower 接受该 entry,则会将 entry 添加到自己的日志后面,同时返回给 Leader 同意。如果 Leader 收到了多数的成功响应,Leader 会将这个 entry 应用到自己的状态机中,之后可以成为这个 entry 是 committed 的,并且向客户端返回执行结果。

raft 保证以下两个性质:
1)·在两个日志里,有两个 entry 拥有相同的 index 和 term,那么它们一定有相同的 cmd
2)·在两个日志里,有两个 entry 拥有相同的 index 和 term,那么它们前面的 entry 也一定相同。

一旦选出了 Leader,它就开始接受客户端的请求。每一个客户端的请求都包含一条需要被复制状态机(Replicated State Machine)执行的命令。Leader 收到客户端请求后,会生成一个 entry,包含<index,term,cmd>,再将这个 entry 添加到自己的日志末尾后,向所有的节点广播该 entry,要求其他服务器复制这条 entry。如果 Follower 接受该 entry,则会将 entry 添加到自己的日志后面,同时返回给 Leader 同意。如果 Leader 收到了多数的成功响应,Leader 会将这个 entry 应用到自己的状态机中,之后可以成为这个 entry 是 committed 的,并且向客户端返回执行结果。raft 保证以下两个性质:在两个日志里,有两个 entry 拥有相同的 index 和 term,那么它们一定有相同的 cmd在两个日志里,有两个 entry 拥有相同的 index 和 term,那么它们前面的 entry 也一定相同。

· 安全性
·选举限制
Leader 需要保证自己存储全部已经提交的日志条目。这样才可以使日志条目只有一个流向:从 Leader 流向 Follower,Leader 永远不会覆盖已经存在的日志条目。每个 Candidate 发送 RequestVoteRPC 时,都会带上最后一个 entry 的信息。所有节点收到投票信息时,会对该 entry 进行比较,如果发现自己的更新,则拒绝投票给该 Candidate。判断日志新旧的方式:
如果两个日志的 term 不同,term 大的更新;如果 term 相同,更长的 index 更新。

·节点崩溃
如果 Leader 崩溃,集群中的节点在 electionTimeout 时间内没有收到 Leader 的心跳信息就会触发新一轮的选主,在选主期间整个集群对外是不可用的。如果 Follower 和 Candidate 崩溃,处理方式会简单很多。之后发送给它的 RequestVoteRPC 和 AppendEntriesRPC 会失败。由于 raft 的所有请求都是幂等的,所以失败的话会无限的重试。如果崩溃恢复后,就可以收到新的请求,然后选择追加或者拒绝 entry。

· 时间与可用性
raft 的要求之一就是安全性不依赖于时间:系统不能仅仅因为一些事件发生的比预想的快一些或者慢一些就产生错误。为了保证上述要求,最好能满足以下的时间条件:

broadcastTime << electionTimeout << MTBFbroadcastTime

·向其他节点并发发送消息的平均响应时间;

·electionTimeout:选举超时时间;
·MTBF(mean time between failures):单台机器的平均健康时间;

broadcastTime应该比electionTimeout小一个数量级,为的是使Leader能够持续发送心跳信息(heartbeat)来阻止Follower开始选举;electionTimeout也要比MTBF小几个数量级,为的是使得系统稳定运行。当Leader崩溃时,大约会在整个electionTimeout的时间内不可用;我们希望这种情况仅占全部时间的很小一部分。由于broadcastTime和MTBF是由系统决定的属性,因此需要决定electionTimeout的时间。一般来说,broadcastTime 一般为 0.5~20ms,electionTimeout 可以设置为 10~500ms,MTBF 一般为一两个月。

·Gossip 协议详解

在分布式系统中,不同的节点进行数据/信息共享是一个基本的需求。一种比较简单粗暴的方法就是 集中式发散消息,简单来说就是一个主节点同时共享最新信息给其他所有节点,比较适合中心化系统。这种方法的缺陷也很明显,节点多的时候不光同步消息的效率低,还太依赖与中心节点,存在单点风险问题。
于是,分散式发散消息 的 Gossip 协议 就诞生了。

·Gossip 协议介绍
也叫 Epidemic 协议(流行病协议)或者 Epidemic propagation 算法(疫情传播算法)。不过,这些名字的特点都具有 随机传播特性 ,这也正是 Gossip 协议最主要的特点。 Gossip 协议当时提出的主要应用是在分布式数据库系统中各个副本节点同步数据。
在 Gossip 协议下,没有所谓的中心节点,每个节点周期性地随机找一个节点互相同步彼此的信息,理论上来说,各个节点的状态最终会保持一致。

下面我们来对 Gossip 协议的定义做一个总结:Gossip 协议是一种允许在分布式系统中共享状态的去中心化通信协议,通过这种通信协议,我们可以将信息传播给网络或集群中的所有成员。
·NoSQL 数据库 Redis 和 Apache Cassandra、服务网格解决方案 Consul 等知名项目都用到了 Gossip 协议。
·Redis Cluster 中的各个节点基于 Gossip 协议 来进行通信共享信息,每个 Redis 节点都维护了一份集群的状态信息。
·Redis Cluster 的节点之间会相互发送多种 Gossip 消息:

·MEET:在 Redis Cluster 中的某个 Redis 节点上执行 CLUSTER MEET ip port 命令,可以向指定的 Redis 节点发送一条 MEET 信息,用于将其添加进 Redis Cluster 成为新的 Redis 节点。
·PING/PONG:Redis Cluster 中的节点都会定时地向其他节点发送 PING 消息,来交换各个节点状态信息,检查各个节点状态,包括在线状态、疑似下线状态 PFAIL 和已下线状态 FAIL。
·FAIL:Redis Cluster 中的节点 A 发现 B 节点 PFAIL ,并且在下线报告的有效期限内集群中半数以上的节点将 B 节点标记为 PFAIL,节点 A 就会向集群广播一条 FAIL 消息,通知其他节点将故障节点 B 标记为 FAIL 。

·有了 Redis Cluster 之后,不需要专门部署 Sentinel 集群服务了。Redis Cluster 相当于是内置了 Sentinel 机制,Redis Cluster 内部的各个 Redis 节点通过 Gossip 协议互相探测健康状态,在故障时可以自动切换。

·Gossip 协议消息传播模式

Gossip 设计了两种可能的消息传播模式:反熵(Anti-Entropy) 和 传谣(Rumor-Mongering)。
·1.反熵(Anti-entropy)
在这里,你可以把反熵中的熵理解为节点之间数据的混乱程度/差异性,反熵就是指消除不同节点中数据的差异,提升节点间数据的相似度,从而降低熵值。
具体是如何反熵的呢?集群中的节点,每隔段时间就随机选择某个其他节点,然后通过互相交换自己的所有数据来消除两者之间的差异,实现数据的最终一致性。

在实现反熵的时候,主要有推、拉和推拉三种方式:

1)推方式,就是将自己的所有副本数据,推给对方,修复对方副本中的熵。
2)拉方式,就是拉取对方的所有副本数据,修复自己副本中的熵。
3)推拉,就是同时修复自己副本和对方副本中的熵。

·在我们实际应用场景中,一般不会采用随机的节点进行反熵,而是需要可以的设计一个闭环。这样的话,我们能够在一个确定的时间范围内实现各个节点数据的最终一致性,而不是基于随机的概率。像 InfluxDB 就是这样来实现反熵的。
·虽然反熵很简单实用,但是,节点过多或者节点动态变化的话,反熵就不太适用了。这个时候,我们想要实现最终一致性就要靠 谣言传播(Rumor mongering) 。

·谣言传播(Rumor mongering)
谣言传播指的是分布式系统中的一个节点一旦有了新数据之后,就会变为活跃节点,活跃节点会周期性地联系其他节点向其发送新数据,直到所有的节点都存储了该新数据。
谣言传播比较适合节点数量比较多的情况,不过,这种模式下要尽量避免传播的信息包不能太大,避免网络消耗太大。

·总结
反熵(Anti-Entropy)会传播节点的所有数据,而谣言传播(Rumor-Mongering)只会传播节点新增的数据。我们一般会给反熵设计一个闭环。谣言传播(Rumor-Mongering)比较适合节点数量比较多或者节点动态变化的场景。

·Gossip 协议优势和缺陷

优势:

1、相比于其他分布式协议/算法来说,Gossip 协议理解起来非常简单。
2、能够容忍网络上节点的随意地增加或者减少,宕机或者重启,因为Gossip 协议下这些节点都是平等的,去中心化的。新增加或者重启的节点在理想情况下最终是一定会和其他节点的状态达到一致。
3、速度相对较快。节点数量比较多的情况下,扩散速度比一个主节点向其他节点传播信息要更快(多播)。

缺陷 :

1、消息需要通过多个传播的轮次才能传播到整个网络中,因此,必然会出现各节点状态不一致的情况。毕竟,Gossip协议强调的是最终一致,至于达到各个节点的状态一致需要多长时间,谁也无从得知。
2、由于拜占庭将军问题,不允许存在恶意节点。
3、可能会出现消息冗余的问题。由于消息传播的随机性,同一个节点可能会重复收到相同的消息。

·三、网关

·什么是网关?

微服务背景下,一个系统被拆分为多个服务,但是像安全认证,流量控制,日志,监控等功能是每个服务都需要的,没有网关的话,我们就需要在每个服务中单独实现,这使得我们做了很多重复的事情并且没有一个全局的视图来统一管理这些功能。微服务网关无需写接口映射,所有的外部请求都要通过微服务网关,其作用是整合整个微服务形成一套系统,实现日志统一记录、用户操作跟踪、限流操作和用户权限认证等。

一般情况下,网关可以为我们提供请求转发、安全认证(身份/权限认证)、流量控制、负载均衡、降级熔断、日志、监控、参数校验、协议转换等功能。实际上,网关主要做了两件事情:请求转发 + 请求过滤。

由于引入网关之后,会多一步网络转发,因此性能会有一点影响(几乎可以忽略不计,尤其是内网访问的情况下)。 另外,我们需要保障网关服务的高可用,避免单点风险。

加粗样式上图所示,网关服务外层通过 Nginx(其他负载均衡设备/软件也行) 进⾏负载转发以达到⾼可⽤。Nginx 在部署的时候,尽量也要考虑高可用,避免单点风险。

·绝大部分网关可以提供下面这些功能(有一些功能需要借助其他框架或者中间件):

·请求转发:将请求转发到目标微服务。 ·负载均衡:根据各个微服务实例的负载情况或者具体的负载均衡策略配置对请求实现动态的负载均衡。
·安全认证:对用户请求进行身份验证并仅允许可信客户端访问 API,并且还能够使用类似 RBAC 等方式来授权。
·参数校验:支持参数映射与校验逻辑。 ·日志记录:记录所有请求的行为日志供后续使用。 ·监控告警:从业务指标、机器指标、JVM指标等方面进行监控并提供配套的告警机制。
·流量控制:对请求的流量进行控制,也就是限制某一时刻内的请求数。
·熔断降级:实时监控请求的统计信息,达到配置的失败阈值后,自动熔断,返回默认值。
·响应缓存:当用户请求获取的是一些静态的或更新不频繁的数据时,一段时间内多次请求获取到的数据很可能是一样的。对于这种情况可以将响应缓存起来。这样用户请求可以直接在网关层得到响应数据,无需再去访问业务服务,减轻业务服务的负担。
·响应聚合:某些情况下用户请求要获取的响应内容可能会来自于多个业务服务。网关作为业务服务的调用方,可以把多个服务的响应整合起来,再一并返回给用户。
·灰度发布:将请求动态分流到不同的服务版本(最基本的一种灰度发布)。
·异常处理:对于业务服务返回的异常响应,可以在网关层在返回给用户之前做转换处理。这样可以把一些业务侧返回的异常细节隐藏,转换成用户友好的错误提示返回。
·API 文档: 如果计划将 API 暴露给组织以外的开发人员,那么必须考虑使用 API 文档,例如 Swagger 或 OpenAPI。
·协议转换:通过协议转换整合后台基于 REST、AMQP、Dubbo 等不同风格和实现技术的微服务,面向 Web Mobile、开放平台等特定客户端提供统一服务。
·证书管理:将 SSL 证书部署到 API网关,由一个统一的入口管理接口,降低了证书更换时的复杂度。

·有哪些常见的网关系统?

·Netflix Zuul

·Zuul 是 Netflix 开发的一款提供动态路由、监控、弹性、安全的网关服务,基于 Java 技术栈开发,可以和 Eureka、Ribbon、Hystrix 等组件配合使用。Zuul 主要通过过滤器(类似于 AOP)来过滤请求,从而实现网关必备的各种功能。Zuul 1.x 基于同步 IO,性能较差。Zuul 2.x 基于 Netty 实现了异步 IO,性能得到了大幅改进。

·Spring Cloud Gateway

SpringCloud Gateway 属于 Spring Cloud 生态系统中的网关,其诞生的目标是为了替代老牌网关 Zuul。准确点来说,应该是 Zuul 1.x。SpringCloud Gateway 起步要比 Zuul 2.x 更早。为了提升网关的性能,SpringCloud Gateway 基于 Spring WebFlux 。Spring WebFlux 使用 Reactor 库来实现响应式编程模型,底层基于 Netty 实现同步非阻塞的 I/O。

Spring Cloud Gateway 不仅提供统一的路由方式,并且基于 Filter 链的方式提供了网关基本的功能,例如:安全,监控/指标,限流。Spring Cloud Gateway 和 Zuul 2.x 的差别不大,也是通过过滤器来处理请求。不过,目前更加推荐使用 Spring Cloud Gateway 而非 Zuul,Spring Cloud 生态对其支持更加友好。

具体的流程分析:
客户端的请求先通过匹配规则找到合适的路由,就能映射到具体的服务。然后请求经过过滤器处理后转发给具体的服务,服务处理后,再次经过过滤器处理,最后返回给客户端。

·question: Spring Cloud Gateway 的断言是什么?

在 Gateway 中,如果客户端发送的请求满足了断言的条件,则映射到指定的路由器,就能转发到指定的服务上进行处理。

·question:Spring Cloud Gateway 的路由和断言是什么关系?

一对多:一个路由规则可以包含多个断言。如上图中路由 Route1 配置了三个断言 Predicate。
同时满足:如果一个路由规则中有多个断言,则需要同时满足才能匹配。
第一个匹配成功:如果一个请求可以匹配多个路由,则映射第一个匹配成功的路由。

·question:Spring Cloud Gateway 如何实现动态路由?

在使用 Spring Cloud Gateway 的时候,官方文档提供的方案总是基于配置文件或代码配置的方式。 Spring Cloud Gateway 作为微服务的入口,需要尽量避免重启,而现在配置更改需要重启服务不能满足实际生产过程中的动态刷新、实时变更的业务需求,所以我们需要在 Spring Cloud Gateway 运行时动态配置网关。

实现动态路由的方式有很多种,其中一种推荐的方式是基于 Nacos 注册中心来做。 Spring Cloud Gateway 可以从注册中心获取服务的元数据(例如服务名称、路径等),然后根据这些信息自动生成路由规则。这样,当你添加、移除或更新服务实例时,网关会自动感知并相应地调整路由规则,无需手动维护路由配置。

·question:Spring Cloud Gateway 的过滤器有哪些?

过滤器 Filter 按照请求和响应可以分为两种:

·Pre 类型:在请求被转发到微服务之前,对请求进行拦截和修改,例如参数校验、权限校验、流量监控、日志输出以及协议转换等操作。
·Post 类型:微服务处理完请求后,返回响应给网关,网关可以再次进行处理,例如修改响应内容或响应头、日志输出、流量监控等。

另外一种分类是按照过滤器 Filter 作用的范围进行划分:

·GatewayFilter:局部过滤器,应用在单个路由或一组路由上的过滤器。标红色表示比较常用的过滤器。
·GlobalFilter:全局过滤器,应用在所有路由上的过滤器。全局过滤器最常见的用法是进行负载均衡

·question:Spring Cloud Gateway 支持限流吗?

Spring Cloud Gateway 自带了限流过滤器,对应的接口是 RateLimiter,RateLimiter 接口只有一个实现类 RedisRateLimiter (基于 Redis + Lua 实现的限流),提供的限流功能比较简易且不易使用。从 Sentinel 1.6.0 版本开始,Sentinel 引入了 Spring Cloud Gateway 的适配模块,可以提供两种资源维度的限流:route 维度和自定义 API 维度。也就是说,Spring Cloud Gateway 可以结合 Sentinel 实现更强大的网关流量控制。

·question:Spring Cloud Gateway 如何自定义全局异常处理?

在 SpringBoot 项目中,我们捕获全局异常只需要在项目中配置 @RestControllerAdvice和 @ExceptionHandler就可以了。不过,这种方式在 Spring Cloud Gateway 下不适用。Spring Cloud Gateway 提供了多种全局处理的方式,比较常用的一种是实现ErrorWebExceptionHandler并重写其中的handle方法。

·OpenResty

OpenResty 是一个基于 Nginx 与 Lua 的高性能 Web 平台,其内部集成了大量精良的 Lua 库、第三方模块以及大多数的依赖项。用于方便地搭建能够处理超高并发、扩展性极高的动态 Web 应用、Web 服务和动态网关。

·Kong

是一款基于 OpenRestyopen in new window (Nginx + Lua)的高性能、云原生、可扩展、生态丰富的网关系统,主要由 3 个组件组成:

1)Kong Server:基于 Nginx 的服务器,用来接收 API 请求。 Apache
2)Cassandra/PostgreSQL:用来存储操作数据。 Kong Dashboard:官方推荐 UI 管理工具,当然,也可以使用
3)RESTful 方式 管理 Admin api。

·APISIX

APISIX 是一款基于 OpenResty 和 etcd 的高性能、云原生、可扩展的网关系统。APISIX 目前已经是 Apache 顶级开源项目。

·Shenyu

Shenyu 是一款基于 WebFlux 的可扩展、高性能、响应式网关,Apache 顶级开源项目。
·上面介绍的几个常见的网关系统,最常用的是 Spring Cloud Gateway、Kong、APISIX 这三个。
对于公司业务以 Java 为主要开发语言的情况下,Spring Cloud Gateway 通常是个不错的选择,其优点有:简单易用、成熟稳定、与 Spring Cloud 生态系统兼容、Spring 社区成熟等等。不过,Spring Cloud Gateway 也有一些局限性和不足之处, 一般还需要结合其他网关一起使用比如 OpenResty。并且,其性能相比较于 Kong 和 APISIX,还是差一些。如果对性能要求比较高的话,Spring Cloud Gateway 不是一个好的选择。

Kong 和 APISIX 功能更丰富,性能更强大,技术架构更贴合云原生。Kong 是开源 API 网关的鼻祖,生态丰富,用户群体庞大。APISIX 属于后来者,更优秀一些,根据 APISIX 官网介绍:“APISIX 已经生产可用,功能、性能、架构全面优于 Kong”。下面简单对比一下二者:
APISIX 基于 etcd 来做配置中心,不存在单点问题,云原生友好;而 Kong 基于 Apache Cassandra/PostgreSQL ,存在单点风险,需要额外的基础设施保障做高可用。
APISIX 支持热更新,并且实现了毫秒级别的热更新响应;而 Kong 不支持热更新。APISIX 的性能要优于 Kong 。

APISIX 支持的插件更多,功能更丰富。

·如何解决 高性能、低延迟 的问题

( http 协议显然不是一个理想的选择)。如果一个服务出现了 IP 漂移(IP 发生了变化),如何能够以最快且最优雅的方式完成IP的切换。对于上面的 第 1 个问题 的解决方案,微服务之间的通信不会使用传统的 http 协议,而是使用了更加高效的 grpc 协议。相比于传统放入 http 协议,grpc 协议 有更高性能的传输,grpc 的请求与响应的数据格式不同于传统的 http(文本格式),其使用的是二进制格式的请求与响应,并支持多路复用与流式传输。

对于上面 第 2 个问题 的解决方案,注册中心的服务注册与发现,在微服务项目中要求每个微服务必须向注册中心注册自己的服务名(可以理解为一个域名),注册中心会维护(心跳检测,即时刷新,负载均衡)一个服务名与 IP的注册表。
这就意味着,各个微服务之间的接口调用无需关注 IP,IP 是由各个微服务与注册中心进行的维护的,这样就解决的微服务的 IP 漂移 的问题。值得注意的是多个不同的微服务的服务名必须不同,相同服务名的微服务会被维护到一个分组中,并采用负载均衡的策略进行访问。

如上所属,结合 grpc(例如成熟的框架 feign)和注册中心,我们就能实现微服务之间的接口调用就像调用函数一样方便。

·注册中心

在微服务架构的项目中,注册中心 是其最核心的模块之一,注册中心 实现了各个微服务的服务注册与发现,从而实现了微服务之间的动态发现与连接,增强了微服之间的去中心化。
注册中心 往往是在分布式的应用体系下才会遇到的。对于分布式体系应用都是横向进行扩展。如下图User App这个服务,具有2台服务器。但是当用户从网关进来访问, 网关是如何知道这个 User App有几台服务及每台服务的网络地址是什么呢? 所以就需要有一个地方能收集到每台应用的地址及命名。

往往这个地方就被叫做 注册中心。分布式环境下的应用在启动时候都会向这个地方来注册自己的网络地址,及命名。

·常见的注册中心

·Zookeeper

可以用ZooKeeper来做:统一配置管理、统一命名服务、分布式锁、集群管理。

·ureka

Eureka是SpringBoot默认的注册中心组件。没有配置的能力

·Consul

Consul是用于服务发现和配置的工具。Consul是分布式的,高度可用的,并且具有极高的可伸缩性,而且开发使用都很简便。它提供了一个功能齐全的控制面板,主要特点是:服务发现、健康检查、键值存储、安全服务通信、多数据中心、ServiceMesh。Consul在设计上把很多分布式服务治理上要用到的功能都包含在内了。

· Nacos

Nacos致力于发现、配置和管理微服务。Nacos提供了一组简单易用的特性集,帮助您实现动态服务发现、服务配置管理、服务及流量管理。Nacos更敏捷和容易地构建、交付和管理微服务平台。 Nacos 是构建以“服务”为中心的现代应用架构(例如微服务范式、云原生范式)的服务基础设施。Nacos支持作为RPC注册中心,例如:支持Dubbo框架;也具备微服务注册中心的能力,例如:SpringCloud框架。

四、分布式ID

·分布式 ID 是分布式系统下的 ID。分布式 ID 不存在与现实生活中,属于计算机系统中的一个概念。
·在分库之后, 数据遍布在不同服务器上的数据库,数据库的自增主键已经没办法满足生成的主键唯一了。我们如何为不同的数据节点生成全局唯一主键呢?这个时候就需要生成分布式 ID了。
·一个最基本的分布式 ID 需要满足下面这些要求:

全局唯一:ID 的全局唯一性肯定是首先要满足的!
高性能:分布式 ID 的生成速度要快,对本地资源消耗要小。
高可用:生成分布式 ID的服务要保证可用性无限接近于 100%。
方便易用:拿来即用,使用方便,快速接入!

除了这些之外,一个比较好的分布式 ID 还应保证:

安全:ID 中不包含敏感信息。 有序递增:如果要把 ID 存放在数据库的话,ID 的有序性可以提升数据库写入速度。并且,很多时候,我们还很有可能会直接通过 ID 来进行排序。 有具体的业务含义:生成的 ID 如果能有具体的业务含义,可以让定位问题以及开发更透明化(通过ID 就能确定是哪个业务)。

独立部署:也就是分布式系统单独有一个发号器服务,专门用来生成分布式 ID。这样就生成 ID 的服务可以和业务相关的服务解耦。不过,这样同样带来了网络调用消耗增加的问题。总的来说,如果需要用到分布式 ID的场景比较多的话,独立部署的发号器服务还是很有必要的。

·分布式 ID 常见解决方案

1.数据库

·数据库主键自增
每次获取 ID 都要访问一次数据库
·数据库号段模式
如果我们可以批量获取,然后存在在内存里面,需要用到的时候,直接从内存里面拿就舒服了!这也就是我们说的 基于数据库的号段模式来生成分布式 ID。
·NoSQL
一般情况下,NoSQL 方案使用 Redis 多一些。我们通过 Redis 的 incr 命令即可实现对 id 原子顺序递增。为了提高可用性和并发,我们可以使用 Redis Cluster。Redis Cluster 是 Redis 官方提供的 Redis 集群解决方案(3.0+版本)。

2.算法

UUID
(Universally Unique Identifier(通用唯一标识符)), 包含 32 个 16 进制数字。

·Snowflake(雪花算法)
Snowflake 是 Twitter 开源的分布式 ID 生成算法。Snowflake 由 64 bit 的二进制数字组成,这 64bit 的二进制被分成了几部分,每一部分存储的数据都有特定的含义。在实际项目中,我们一般也会对 Snowflake 算法进行改造,最常见的就是在 Snowflake 算法生成的 ID 中加入业务类型信息。

3.开源框架

·UidGenerato
百度开源的一款基于 Snowflake(雪花算法)的唯一 ID 生成器。
·Leaf(美团)
Leaf 提供了 号段模式 和 Snowflake(雪花算法) 这两种模式来生成分布式 ID。并且,它支持双号段,还解决了雪花 ID 系统时钟回拨问题。不过,时钟问题的解决需要弱依赖于 Zookeeper(使用 Zookeeper 作为注册中心,通过在特定路径下读取和创建子节点来管理 workId) 。
·Tinyid(滴滴)
滴滴开源的一款基于数据库号段模式的唯一 ID 生成器。
·IdGenerator(个人)
和 UidGenerator、Leaf 一样,IdGenerator 也是一款基于 Snowflake(雪花算法)的唯一 ID 生成器。

五.分布式锁

·在多线程环境中,如果多个线程同时访问共享资源(例如商品库存、外卖订单),会发生数据竞争,可能会导致出现脏数据或者系统问题,威胁到程序的正常运行。

·对于单机多线程来说,在 Java 中,我们通常使用 ReetrantLock 类、synchronized 关键字这类 JDK 自带的 本地锁 来控制一个 JVM 进程内的多个线程对本地共享资源的访问。

·分布式系统下,不同的服务/客户端通常运行在独立的 JVM 进程上。如果多个 JVM 进程共享同一份资源的话,使用本地锁就没办法实现资源的互斥访问了。于是,分布式锁 就诞生了。
·一个最基本的分布式锁需要满足:

互斥:任意一个时刻,锁只能被一个线程持有。
高可用:锁服务是高可用的,当一个锁服务出现问题,能够自动切换到另外一个锁服务。并且,即使客户端的释放锁的代码逻辑出现问题,锁最终一定还是会被释放,不会影响其他线程对共享资源的访问。这一般是通过超时机制实现的。
可重入:一个节点获取了锁之后,还可以再次获取锁。 除了上面这三个基本条件之外,一个好的分布式锁还需要满足下面这些条件:
高性能:获取和释放锁的操作应该快速完成,并且不应该对整个系统的性能造成过大影响。
非阻塞:如果获取不到锁,不能无限期等待,避免对系统正常运行造成影响。

·分布式锁的常见实现方式:关系型数据库比如 MySQL、分布式协调服务 ZooKeeper、分布式键值存储系统比如 Redis 、Etcd 。

·在 Redis 中, SETNX 命令是可以帮助我们实现互斥。SETNX 即 SET if Not eXists (对应 Java 中的 setIfAbsent 方法),如果 key 不存在的话,才会设置 key 的值。如果 key 已经存在, SETNX 啥也不做。

· 释放锁的话,直接通过 DEL 命令删除对应的 key 即可。为了防止误删到其他的锁,这里我们建议使用 Lua 脚本通过 key 对应的 value(唯一值)来判断。
·这是一种最简易的 Redis 分布式锁实现,实现方式比较简单,性能也很高效。不过,这种方式实现分布式锁存在一些问题。就比如应用程序遇到一些问题比如释放锁的逻辑突然挂掉,可能会导致锁无法被释放,进而造成共享资源无法再被其他线程/进程访问。为什么要给锁设置一个过期时间?

为了避免锁无法被释放,我们可以想到的一个解决办法就是:给这个 key(也就是锁) 设置一个过期时间 。
·如果操作共享资源的时间大于过期时间,就会出现锁提前过期的问题,进而导致分布式锁直接失效。如果锁的超时时间设置过长,又会影响到性能。你或许在想:如果操作共享资源的操作还未完成,锁过期时间能够自己续期就好了!-》Redisson

·Redisson 中的分布式锁自带自动续期机制,使用起来非常简单,原理也比较简单,其提供了一个专门用来监控和续期锁的 Watch Dog( 看门狗),如果操作共享资源的线程还未执行完成的话,Watch Dog 会不断地延长锁的过期时间,进而保证锁不会因为超时而被释放。
·可重入分布式锁的实现核心思路是线程在获取锁的时候判断是否为自己的锁,如果是的话,就不用再重新获取了。为此,我们可以为每个锁关联一个可重入计数器和一个占有它的线程。当可重入计数器大于 0 时,则锁被占有,需要判断占有该锁的线程和请求获取锁的线程是否为同一个。

·Redis 如何解决集群情况下分布式锁的可靠性?

由于 Redis 集群数据同步到各个节点时是异步的,如果在 Redis 主节点获取到锁后,在没有同步到其他节点时,Redis 主节点宕机了,此时新的 Redis 主节点依然可以获取锁,所以多个应用服务就可以同时获取到锁。

针对这个问题,Redis 之父 antirez 设计了 Redlock 算法 来解决。
Redlock 算法的思想是让客户端向 Redis 集群中的多个独立的 Redis 实例依次请求申请加锁,如果客户端能够和半数以上的实例成功地完成加锁操作,那么我们就认为,客户端成功地获得分布式锁,否则加锁失败。Redlock 是直接操作 Redis 节点的,并不是通过 Redis 集群操作的,这样才可以避免 Redis 集群主从切换导致的锁丢失问题。
实际项目中不建议使用 Redlock 算法,成本和收益不成正比。

六.分布式事务

·分布式事务指事务的参与者、支持事务的服务器、资源服务器以及事务管理器分别位于不同的分布式系统的不同节点之上,且属于不同的应用,分布式事务需要保证这些操作要么全部成功,要么全部失败。本质上来说,分布式事务就是为了保证不同数据库的数据一致性。
·分布式事务架构
最早的分布式事务应用架构很简单,不涉及服务间的访问调用,仅仅是服务内操作涉及到对多个数据库资源的访问。

·分布式事务解决方案

1、通过消息中间件,将分布式事务转为本地事务(技术比较简单,业务比较复杂)
2、Seata:AT、TCC、XA、Saga

①基于XA协议的两阶段提交(2PC方案)

XA是一个分布式事务协议。XA中大致分为两部分:事务管理器和本地资源管理器。其中本地资源管理器往往由数据库实现,比如Oracle、DB2这些商业数据库都实现了XA接口,而事务管理器作为全局的调度者,负责各个本地资源的提交和回滚。
·分布式事务的6种解决方案

②三阶段

后面有详细介绍

③代码补偿事务(TCC)

TCC的作用主要是解决跨服务调用场景下的分布式事务问题。TCC是Try ( 尝试 ) — Confirm(确认) — Cancel ( 取消 ) 的简称。

TCC(Try Confirm Cancel)是应用层的两阶段提交,所以对代码的侵入性强,其核心思想是:针对每个操作,都要实现对应的确认和补偿操作,也就是业务逻辑的每个分支都需要实现 try、confirm、cancel 三个操作,第一阶段由业务代码编排来调用Try接口进行资源预留,当所有参与者的 Try 接口都成功了,事务协调者提交事务,并调用参与者的 confirm 接口真正提交业务操作,否则调用每个参与者的 cancel 接口回滚事务,并且由于 confirm 或者 cancel 有可能会重试,因此对应的部分需要支持幂等。

·TCC的执行流程可以分为两个阶段,分别如下:
·第一阶段:
Try,业务系统做检测并预留资源 (加锁,锁住资源),比如常见的下单,在try阶段,我们不是真正的减库存,而是把下单的库存给锁定住。
·第二阶段:根据第一阶段的结果决定是执行confirm还是cancel

Confirm:执行真正的业务(执行业务,释放锁)
Cancle:是对Try阶段预留资源的释放(出问题,释放锁)

Try阶段执行成功并开始执行 Confirm 阶段时,默认 Confirm 阶段是不会出错的,也就是说只要 Try 成功,Confirm 一定成功(TCC设计之初的定义)。Confirm 与 Cancel 如果失败,由TCC框架进行重试补偿,存在极低概率在CC环节彻底失败,则需要定时任务或人工介入。

·TCC的注意事项:
·1)允许空回滚:
空回滚出现的原因是 Try 超时或者丢包,导致 TCC 分布式事务二阶段的 回滚,触发 Cancel 操作,此时事务参与者未收到Try,但是却收到了Cancel 请求。所以 cancel 接口在实现时需要允许空回滚,也就是 Cancel 执行时如果发现没有对应的事务 xid 或主键时,需要返回回滚成功,让事务服务管理器认为已回滚。
·2)防悬挂控制:
悬挂指的是二阶段的 Cancel 比 一阶段的Try 操作先执行,出现该问题的原因是 Try 由于网络拥堵而超时,导致事务管理器生成回滚,触发 Cancel 接口,但之后拥堵在网络的 Try 操作又被资源管理器收到了,但是 Cancel 比 Try 先到。但按照前面允许空回滚的逻辑,回滚会返回成功,事务管理器认为事务已回滚成功,所以此时应该拒绝执行空回滚之后到来的 Try 操作,否则会产生数据不一致。因此我们可以在 Cancel 空回滚返回成功之前,先记录该条事务 xid 或业务主键,标识这条记录已经回滚过,Try 接口执行前先检查这条事务xid或业务主键是否已经标记为回滚成功,如果是则不执行 Try 的业务操作。
3)幂等控制
由于网络原因或者重试操作都有可能导致 Try - Confirm - Cancel 3个操作的重复执行,所以使用 TCC 时需要注意这三个操作的幂等控制,通常我们可以使用事务 xid 或业务主键判重来控制。

·TCC方案的优缺点:
TCC 事务机制相比于上面介绍的 XA 事务机制,有以下优点

1)性能提升:具体业务来实现,控制资源锁的粒度变小,不会锁定整个资源。 数据最终一致性:基于 Confirm 和 Cancel的幂等性,保证事务最终完成确认或者取消,保证数据的一致性。 2)可靠性:解决了 XA协议的协调者单点故障问题,由主业务方发起并控制整个业务活动,业务活动管理器也变成多点,引入集群。
3)跟2PC(很多第三方框架)比起来,实现以及流程相对简单了一些

缺点:

1)TCC 的 Try、Confirm 和 Cancel 操作功能要按具体业务来实现,业务耦合度较高,提高了开发成本。
2)数据的一致性比2PC也要差一些
3)在2,3步中都有可能失败。TCC属于应用层的一种补偿方式,所以需要程序员在实现的时候多写很多补偿的代码,在一些场景中,一些业务流程可能用TCC不太好定义及处理。

·④本地消息表(异步确保)

本地消息表与业务数据表处于同一个数据库中,这样就能利用本地事务来保证在对这两个表的操作满足事务特性,并且使用了消息队列保证最终一致性。本地消息表的核心思路就是将分布式事务拆分成本地事务进行处理,在该方案中主要有两种角色:事务主动方和事务被动方。事务主动发起方需要额外新建事务消息表,并在本地事务中完成业务处理和记录事务消息,并轮询事务消息表的数据发送事务消息,事务被动方基于消息中间件消费事务消息表中的事务。这样可以避免以下两种情况导致的数据不一致性:

业务处理成功、事务消息发送失败
业务处理失败、事务消息发送成功

·执行流程
① 事务主动方在同一个本地事务中处理业务和写消息表操作
② 事务主动方通过消息中间件,通知事务被动方处理事务消息。消息中间件可以基于 Kafka、RocketMQ 消息队列,事务主动方主动写消息到消息队列,事务消费方消费并处理消息队列中的消息。
③ 事务被动方通过消息中间件,通知事务主动方事务已处理的消息。
④ 事务主动方接收中间件的消息,更新消息表的状态为已处理。
本地消息表的优缺点
优点:

1)从应用设计开发的角度实现了消息数据的可靠性,消息数据的可靠性不依赖于消息中间件,弱化了对 MQ 中间件特性的依赖。
2)方案轻量,容易实现。

缺点:

1)与具体的业务场景绑定,耦合性强,不可公用
’2)消息数据与业务数据同库,占用业务系统资源
3)业务系统在使用关系型数据库的情况下,消息服务性能会受到关系型数据库并发性能的局限。

·⑤MQ事务消息:

执行流程
基于MQ的分布式事务方案本质上是对本地消息表的封装,整体流程与本地消息表一致,唯一不同的就是将本地消息表存在了MQ内部,而不是业务数据库中。

(1)先给Broker发送事务消息即半消息(对消费者来说不可见,然后发送成功后发送方再执行本地事务)
(2)再根据本地事务的结果向Broker发送Commit或者RollBack命令
(3)并且RocketMQ的发送方会提供一个反查事务状态接口,如果一段时间内办消息没有收到任何操作请求,那么Broker会通过反查接口得知发送方事务是否执行成功,然后执行Commit或者RollBack命令
(4)如果是Commit那么订阅方就能收到这个消息,然后在做对应的操作,做完之后再消费这条消息
(5)如果是RollBack那么订阅方收不到这条消息,等于事务没执行过

·⑥最大努力通知

最大努力通知也称为定期校对,是对MQ事务方案的进一步优化。它在事务主动方增加了消息校对的接口,如果事务被动方没有接收到主动方发送的消息,此时可以调用事务主动方提供的消息校对的接口主动获取。
1、有一定的消息重复通知机制:因为接收通知方可能没有接收到通知,此时要有一定的机制对消息重复通知。
2、消息校对机制:如果尽最大努力也没有通知到接收方,或者接收方消费消息后要再次消费,此时可由接收方主动向通知方查询消息信息来满足需求。

·总结:

·2PC/3PC:
依赖于数据库,能够很好的提供强一致性和强事务性,但延迟比较高,比较适合传统的单体应用,在同一个方法中存在跨库操作的情况,不适合高并发和高性能要求的场景。
·TCC:
适用于执行时间确定且较短,实时性要求高,对数据一致性要求高,比如互联网金融企业最核心的三个服务:交易、支付、账务。
·本地消息表/MQ 事务:
适用于事务中参与方支持操作幂等,对一致性要求不高,业务上能容忍数据不一致到一个人工检查周期,事务涉及的参与方、参与环节较少,业务上有对账/校验系统兜底。
· 可以看出 2PC 和 3PC 是一种强一致性事务,不过还是有数据不一致,阻塞等风险,而且只能用在数据库层面。
  ·而 TCC 是一种补偿性事务思想,适用的范围更广,在业务层面实现,因此对业务的侵入性较大,每一个操作都需要实现对应的三个方法。

·本地消息、事务消息和最大努力通知其实都是最终一致性事务,因此适用于一些对时间不敏感的业务

·Seata(理想方式)

·seata是阿里开源的一个分布式事务框架,能够让大家在操作分布式事务时,像操作本地事务一样简单。一个注解搞定分布式事务。
·解决分布式事务问题,有两个设计初衷
对业务无侵入:即减少技术架构上的微服务化所带来的分布式事务问题对业务的侵入高性能:
减少分布式事务解决方案所带来的性能消耗

·seata中有两种分布式事务实现方案,AT及TCC
·AT模式主要关注多 DB 访问的数据一致性,当然也包括多服务下的多 DB 数据访问一致性问题 2PC-改进。TCC 模式主要关注业务拆分,在按照业务横向扩展资源时,解决微服务间调用的一致性问题。

七.RPC

·RPC(Remote Procedure Call) 即远程过程调用,通过名字我们就能看出 RPC 关注的是远程调用而非本地调用。
**·为什么要 RPC ? **
因为,两个不同的服务器上的服务提供的方法不在一个内存空间,所以,需要通过网络编程才能传递方法调用所需要的参数。并且,方法调用的结果也需要通过网络编程来接收。但是,如果我们自己手动网络编程来实现这个调用过程的话工作量是非常大的,因为,我们需要考虑底层传输方式(TCP 还是 UDP)、序列化方式等等方面。

·RPC 能帮助我们做什么呢?
简单来说,通过 RPC 可以帮助我们调用远程计算机上某个服务的方法,这个过程就像调用本地方法一样简单。并且!我们不需要了解底层网络编程的具体细节。
·我们可以将整个 RPC 的 核心功能看作是下面 👇5 个部分实现的:
·①客户端(服务消费端):调用远程方法的一端。
·②客户端 Stub(桩):这其实就是一代理类。代理类主要做的事情很简单,就是把你调用方法、类、方法参数等信息传递到服务端。
·③网络传输:网络传输就是你要把你调用的方法的信息比如说参数啊这些东西传输到服务端,然后服务端执行完之后再把返回结果通过网络传输给你传输回来。网络传输的实现方式有很多种比如最近基本的 Socket 或者性能以及封装更加优秀的 Netty(推荐)。
·④服务端 Stub(桩):这个桩就不是代理类了。我觉得理解为桩实际不太好,大家注意一下就好。这里的服务端 Stub 实际指的就是接收到客户端执行方法的请求后,去执行对应的方法然后返回结果给客户端的类。
·⑤服务端(服务提供端):提供远程方法的一端。

1.服务消费端(client)以本地调用的方式调用远程服务;
2.客户端 Stub(client stub) 接收到调用后负责将方法、参数等组装成能够进行网络传输的消息体(序列化):RpcRequest;
3.客户端 Stub(client stub) 找到远程服务的地址,并将消息发送到服务提供端;
4.服务端 Stub(桩)收到消息将消息反序列化为 Java 对象: RpcRequest;
5.服务端 Stub(桩)根据RpcRequest中的类、方法、方法参数等信息调用本地的方法;
6.服务端 Stub(桩)得到方法执行结果并将组装成能够进行网络传输的消息体:RpcResponse(序列化)发送至消费方;
7.客户端 Stub(client stub)接收到消息并将消息反序列化为 Java 对象:RpcResponse ,这样也就得到了最终结果。over!

·我们这里说的 RPC 框架指的是可以让客户端直接调用服务端方法,就像调用本地方法一样简单的框架,比如我下面介绍的 Dubbo、Motan、gRPC 这些。 如果需要和 HTTP 协议打交道,解析和封装 HTTP 请求和响应。这类框架并不能算是“RPC 框架”,比如 Feign。

·TCP 是有三个特点,面向连接、可靠、基于字节流。于是基于 TCP,就衍生了非常多的协议,比如 HTTP 和 RPC。

·TCP 是传输层的协议 ,而基于 TCP 造出来的 HTTP 和各类 RPC 协议,它们都只是定义了不同消息格式的 应用层协议 而已。

·HTTP(Hyper Text Transfer Protocol)协议又叫做 超文本传输协议 。我们用的比较多,平时上网在浏览器上敲个网址就能访问网页,这里用到的就是 HTTP 协议。

·而 RPC(Remote Procedure Call)又叫做 远程过程调用,它本身并不是一个具体的协议,而是一种 调用方式 。

·值得注意的是,虽然大部分 RPC 协议底层使用 TCP,但实际上 它们不一定非得使用 TCP,改用 UDP 或者 HTTP,其实也可以做到类似的功能。
·也就是说在多年以前,HTTP 主要用于 B/S 架构,而 RPC 更多用于 C/S 架构。但现在其实已经没分那么清了,B/S 和 C/S 在慢慢融合。 果通信协议都用 HTTP 的话,那服务器只用同一套就够了。而 RPC 就开始退居幕后,一般用于公司内部集群里,各个微服务之间的通讯。

·HTTP和RPC比较:
·服务发现:首先要向某个服务器发起请求,你得先建立连接,而建立连接的前提是,你得知道 IP 地址和端口 。这个找到服务对应的 IP 端口的过程,其实就是 服务发现。
在 HTTP 中,你知道服务的域名,就可以通过 DNS 服务 去解析得到它背后的 IP 地址,默认 80 端口。而 RPC 的话,就有些区别,一般会有专门的中间服务去保存服务名和 IP 信息,比如 Consul、Etcd、Nacos、ZooKeeper,甚至是 Redis。想要访问某个服务,就去这些中间服务去获得 IP 和端口信息。由于 DNS 也是服务发现的一种,所以也有基于 DNS 去做服务发现的组件,比如 CoreDNS。

·底层连接形式

以主流的 HTTP1.1 协议为例,其默认在建立底层 TCP 连接之后会一直保持这个连接(keep alive),之后的请求和响应都会复用这条连接。

而 RPC 协议,也跟 HTTP 类似,也是通过建立 TCP 长链接进行数据交互,但不同的地方在于,RPC 协议一般还会再建个 连接池,在请求量大的时候,建立多条连接放在池内,要发数据的时候就从池里取一条连接出来,用完放回去,下次再复用,可以说非常环保。

·传输的内容
而 RPC,因为它定制化程度更高,可以采用体积更小的 Protobuf 或其他序列化协议去保存结构体数据,同时也不需要像 HTTP 那样考虑各种浏览器行为,比如 302 重定向跳转啥的。因此性能也会更好一些,这也是在公司内部微服务中抛弃 HTTP,选择使用 RPC 的最主要原因。

总结:
·纯裸 TCP 是能收发数据,但它是个无边界的数据流,上层需要定义消息格式用于定义 消息边界 。于是就有了各种协议,HTTP 和各类 RPC 协议就是在 TCP 之上定义的应用层协议。

·RPC 本质上不算是协议,而是一种调用方式,而像 gRPC 和 Thrift 这样的具体实现,才是协议,它们是实现了 RPC 调用的协议。目的是希望程序员能像调用本地方法那样去调用远端的服务方法。同时 RPC 有很多种实现方式,不一定非得基于 TCP 协议。

·从发展历史来说,HTTP 主要用于 B/S 架构,而 RPC 更多用于 C/S 架构。但现在其实已经没分那么清了,B/S 和 C/S 在慢慢融合。 很多软件同时支持多端,所以对外一般用 HTTP 协议,而内部集群的微服务之间则采用 RPC 协议进行通讯。

·RPC 其实比 HTTP 出现的要早,且比目前主流的 HTTP1.1 性能要更好,所以大部分公司内部都还在使用 RPC。

·HTTP2.0 在 HTTP1.1 的基础上做了优化,性能可能比很多 RPC 协议都要好,但由于是这几年才出来的,所以也不太可能取代掉 RPC。

八、分布式系统事务处理:

·二阶段提交

在分布式系统中,多个节点需要协同工作以完成各种任务。以一个电子商务网站为例,该网站的服务分布在多个数据中心。用户在网站上下订单,订单服务在一个数据中心,库存服务在另一个数据中心。在没有一个协调机制(例如二阶段提交)的情况下,可能会出现以下问题:订单服务已经创建了订单,但在减少库存时,由于网络问题,库存服务没有收到请求,导致库存数量错误,用户可能会购买到实际上已经售罄的商品。

没有一个合适的分布式事务处理机制,可能会导致数据的不一致、系统的不可靠和用户的购物体验受损。为了解决这类问题,我们需要引入像二阶段提交这样的协议,确保分布式系统中的事务能够安全、完整地执行。

随着分布式系统的普及,如何在多个节点间保证数据的一致性成为了一个重要的挑战。二阶段提交(2PC)是解决分布式事务问题的经典算法之一。

·二阶段提交(2PC)
是一种在分布式系统中实现事务原子性的协议。在分布式系统中,一个事务可能涉及到多个节点,因此需要一种机制来保证所有节点要么都提交(commit)事务,要么都回滚(rollback)事务,这就是二阶段提交协议的作用。

·2PC主要涉及两个角色:协调者(Coordinator)和参与者(Participant)。
·第一阶段(准备阶段)
投票请求:协调者向所有参与者发送事务请求,询问它们是否可以提交事务。
投票:每个参与者对事务进行预执行,并根据其本地情况决定投票。如果可以执行,它就记录事务日志,响应"YES";否则响应"NO"。
第二阶段(提交/回滚阶段)
所有投票为YES:如果所有参与者都响应"YES",协调者向所有参与者发出"提交"命令。
有投票为NO:如果有任何一个参与者响应"NO",或者在规定时间内没有响应,协调者向所有参与者发出"回滚"命令。每个参与者在接到"提交"或"回滚"命令后,执行相应的操作,并向协调者发出确认。

优点:
·确保了分布式环境中的事务原子性:二阶段提交协议通过两个阶段的操作,确保了在分布式环境中的事务原子性。在第一阶段,所有的参与者都会对事务进行预执行,并根据其本地情况决定是否可以提交事务。在第二阶段,如果所有参与者都同意提交事务,那么事务就会被提交,否则就会被回滚。这样,无论事务是否被提交,都能保证所有的参与者的状态是一致的,从而实现了事务的原子性。
·结构相对简单,容易理解:二阶段提交协议的流程和结构都相对简单。它只包含两个阶段:准备阶段和提交/回滚阶段。在准备阶段,协调者向所有的参与者发送事务请求,参与者根据本地情况进行投票。在提交/回滚阶段,协调者根据投票结果决定是提交事务还是回滚事务。这个流程很直观,容易理解,也便于实现。
缺点:
·性能开销:二阶段提交协议涉及多次网络通信,包括协调者向参与者发送事务请求,参与者向协调者返回投票结果,以及协调者向参与者发送提交或回滚命令。这些通信过程会增加系统的延迟,尤其在网络环境不佳的情况下,性能开销可能会更大。
·单点故障:在二阶段提交协议中,协调者扮演着关键的角色。如果协调者出现故障或宕机,会导致参与者处于不确定状态,不知道应该提交事务还是回滚事务。在这种情况下,参与者必须等待协调者恢复后才能继续执行,这可能会导致系统的整体性能下降。可以使用多个协调者来避免单点故障。例如,可以使用主备模式,当主协调者出现故障时,备用协调者可以接管。此外,可以使用心跳检测和超时机制来检测和处理协调者的故障。
· 阻塞问题:在二阶段提交协议的第二阶段,如果有参与者没有返回投票结果,或者返回的结果是"NO",那么协调者会向所有参与者发送回滚命令。但是,如果有参与者在这个阶段出现故障或者延迟,那么其他的参与者就必须等待这个参与者恢复后才能继续执行,这就可能导致阻塞问题。在某些场景下,这种阻塞可能会持续很长时间,严重影响系统的性能和可用性。引入超时机制可以解决阻塞问题。如果参与者在规定的时间内没有响应,协调者可以决定回滚事务,避免其他参与者长时间等待。
·数据不一致:假设协调者发出了事务 Commit 的通知,但是由于网络问题该通知仅被一部分参与者所收到并执行 Commit,其余的参与者没有收到通知,一直处于阻塞状态,那么,这段时间就产生了数据的不一致性。

·三阶段提交协议

3PC在2PC的基础上增加了一个超时机制和一个预提交阶段,解决了二阶段提交协议中的同步阻塞和单点故障问题。三阶段提交协议在协调者和参与者中都引入超时机制,并且把两阶段提交协议的第一个阶段拆分成了两步:询问,然后再锁资源,最后真正提交。

第一阶段(CanCommit阶段)
提交询问:协调者向所有参与者发送提交询问,询问它们是否可以进入下一个阶段,即提交事务。
参与者决策:每个参与者根据自己的状态判断是否可以提交,然后响应“可以”或“不可以”。
第二阶段(PreCommit阶段)
所有参与者同意:如果所有的参与者都答复“可以”,那么协调者决定继续提交并进入该阶段,向所有参与者发送“预提交”消息。
参与者预执行:参与者接收到“预提交”消息后,执行事务操作但并不提交,然后向协调者发送“已准备好提交”的消息。
第三阶段(DoCommit阶段)
所有参与者准备好:协调者收到所有参与者的“已准备好提交”消息后,向它们发送“提交”消息。
参与者提交:参与者收到“提交”消息后正式提交其事务。
·事务中止:如果在此过程中,有任何参与者或协调者发送“中止”消息或没有响应,那么事务会被中止。

优点:
·更好地解决单点故障和阻塞问题:三阶段提交协议在二阶段提交的基础上增加了一个预提交阶段和超时机制,这使得它能更好地解决单点故障和阻塞问题。在预提交阶段,参与者执行事务操作但并不提交,这为协调者提供了更多的灵活性来处理可能的故障和阻塞。
·增强了系统的可用性和健壮性:通过引入超时机制,三阶段提交协议可以在参与者没有响应或者响应"NO"的情况下,避免其他参与者长时间等待,从而增强了系统的可用性和健壮性。
·提高了事务的执行效率:在三阶段提交协议中,参与者在预提交阶段就开始执行事务操作,这可以提高事务的执行效率,尤其是在事务操作耗时较长的情况下。

缺点及解决方案:
·更高的消息开销:三阶段提交协议比二阶段提交协议增加了一个阶段,这意味着更多的消息交换,从而导致更高的消息开销。这可能会影响系统的性能,尤其是在网络环境不佳的情况下。解决方案是优化网络环境和消息传输机制,减少消息开销。
·复杂性增加:引入了一个新的预提交阶段和超时机制,使得协议的复杂性增加。这可能会增加实现和维护的难度。解决方案是使用更高级的协议,如Paxos或Raft,它们在处理分布式事务时,可以更好地处理故障和阻塞问题,同时也能提供更高的性能。
·依然存在阻塞问题:尽管三阶段提交协议通过引入超时机制来解决阻塞问题,但在某些情况下,例如网络分区或者协调者和参与者同时失败等情况,依然可能出现阻塞。解决方案是引入故障恢复机制,例如日志记录,当协调者或参与者失败时,可以通过日志来恢复状态,避免阻塞。

·在阶段三中,如果参与者接收到PreCommit消息后,与协调者无法正常通信,参与者仍然会进行事务的提交,导致数据不一致性。

·数据库分表

单表数据量太大,会极大影响 sql 执行的性能
1)·根据数值取模
·优点:
将一个数据表的数据分成多个表后,数据相对比较均匀,减轻来高并发访问带来的数据库压力。
缺点:

1.后期如果扩容时,需要迁移旧的数据重新计算。
2.跨分表查询复杂性增加。比如上例中,如果频繁用到的查询条件中不带cusno时,将会导致无法定位数据库,从而需要同时向4个库发起查询,再在内存中合并数据,取最小集返回给应用,分库反而成为拖累。

2)·根据数值范围
为了解决后期集群扩容需要迁移旧数据的问题,可以使用按日期或者ID来进行分表。某种意义上,某些系统中使用的"冷热数据分离",将一些使用较少的历史数据迁移到其他库中,业务功能上只提供热点数据的查询,也是类似的实践。
·优点:

1.单表大小可控
2.天然便于水平扩展,后期如果想对整个分片集群扩容时,只需要添加节点即可,无需对其他分片的数据进行迁移
3.使用分片字段进行范围查找时,连续分片可快速定位分片进行快速查询,有效避免跨分片查询的问题。

·缺点:
热点数据成为性能瓶颈。连续分片可能存在数据热点,例如按时间字段分片,有些分片存储最近时间段内的数据,可能会被频繁的读写,而有些分片存储的历史数据,则很少被查询。

·分库

将一个库的数据拆分到多个库中

·垂直拆分

垂直拆分意思就是处理数据库的列,列和对应的业务有关系,意思就是就是根据业务耦合性,将关联度低的不同表存储在不同的数据库。需要解决的问题:跨数据库的事务、jion查询等问题。

·水平拆分

按照规则划分,一般水平分库是在垂直分库之后的。比如每天处理的订单数量是海量的,可以按照一定的规则水平划分。比如某张表太大,单个数据库存储不下或访问性能有压力,把一张表拆成多张,每张表存放部分记录,保存在不同的数据库里,水平分库需要对系统做大的改造。
1)Scale up,升级数据库所在的物理机,提升内存/存储/IO性能,但这种升级费用昂贵,并且只能满足短期需要。
2)Scale out,把订单库拆分为多个库,分散到多台机器进行存储和访问,这种做法支持水平扩展,可以满足长远需要。
要解决的问题:数据路由、组装。

·读写分离

对于时效性不高的数据,可以通过读写分离缓解数据库压力。
需要解决的问题:在业务上区分哪些业务上是允许一定时间延迟的,以及数据同步问题。
垂直分库–>水平分库–>读写分离

·拆分后面临新的问题的解决方案
常用的解决方案:目前分库分表已经有一些较为成熟的开源解决方案。

·选用第三方的数据库中间件(Atlas,Mycat,TDDL,DRDS),同时业务系统需要配合数据存储的升级。综合考虑,现在其实建议考量的,就是 Sharding-jdbc 和 Mycat,这两个都可以去考虑使用。

·Sharding-jdbc 这种 client 层方案的优点在于不用部署,运维成本低,不需要代理层的二次转发请求,性能很高,但是如果遇到升级啥的需要各个系统都重新升级版本再发布,各个系统都需要耦合 Sharding-jdbc 的依赖;

·Mycat 这种 proxy 层方案的缺点在于需要部署,自己运维一套中间件,运维成本高,但是好处在于对于各个项目是透明的,如果遇到升级之类的都是自己中间件那里搞就行了。

·通常来说,这两个方案其实都可以选用,建议中小型公司选用 Sharding-jdbc,client 层方案轻便,而且维护成本低,不需要额外增派人手,而且中小型公司系统复杂度会低一些,项目也没那么多;但是中大型公司最好还是选用 Mycat 这类 proxy 层方案,因为可能大公司系统和项目非常多,团队很大,人员充足,那么最好是专门弄个人来研究和维护 Mycat,然后大量项目直接透明使用即可。

·消息中间件

·消息队列

消息队列(Message queue,简称MQ),是指利用高效可靠的消息传递机制进行与平台无关的数据交流,并基于数据通信来进行分布式系统的集成。从字面理解就是一个保存消息的一个容器。那么我们为何需要这样一个容器呢?其实就是为了解耦各个系统。当不需要立即获得结果,但是并发量又需要进行控制的时候,差不多就是需要使用消息队列的时候。

·当前使用较多的消息队列有RabbitMQ、RocketMQ、ActiveMQ、Kafka、ZeroMQ、MetaMQ等,而部分数据库如Redis、MySQL以及phxsql也可实现消息队列的功能。

·消息队列的组成

Broker:消息服务器,作为server提供消息核心服务。 ·
Producer:消息生产者,业务的发起方,负责生产消息传输给broker。 ·
Consumer:消息消费者,业务的处理方,负责从broker获取消息并进行业务逻辑处理。 ·
Topic:主题,发布订阅模式下的消息统一汇集地,不同生产者向topic发送消息,由MQ服务器分发到不同的订阅者,实现消息的广播。 ·
Queue:队列,PTP模式下,特定生产者向特定queue发送消息,消费者订阅特定的queue完成指定消息的接收。 ·
Message:消息体,根据不同通信协议定义的固定格式进行编码的数据包,来封装业务数据,实现消息的传输。

·消息队列的特点
1、先进先出:消息队列的顺序在入队的时候就基本已经确定了,一般是不需人工干预的。
2、发布订阅:发布订阅是一种很高效的处理方式,如果不发生阻塞,基本可以当成是同步操作。
3、持久化:持久化确保消息队列的使用不只是一个部分场景的辅助工具,而是让消息队列能像数据库一样存储核心的数据。
4、分布式:在现在大流量、大数据的使用场景下,支持分布式的部署,才能被广泛使用。消息队列的定位就是一个高性能的中间件。

·消息队列的主要特点是异步处理,主要目的是减少请求响应时间,实现非核心流程异步化,提高系统响应性能。

· 消息队列模式分类

·点对点
PTP点对点:使用queue作为通信载体
·发布/订阅
Pub/Sub发布订阅(广播):使用topic作为通信载体。消息生产者(发布)将消息发布到topic中,同时有多个消息消费者(订阅)消费该消息。和点对点方式不同,发布到topic的消息会被所有订阅者消费。

·. 消息中间件的优势

1、系统解耦
交互系统之间没有直接的调用关系,只是通过消息传输,故系统侵入性不强,耦合度低。
2、提高系统响应时间
例如原来的一套逻辑,完成支付可能涉及先修改订单状态、计算会员积分、通知物流配送几个逻辑才能完成;通过MQ架构设计,就可将紧急重要(需要立刻响应)的业务放到该调用方法中,响应要求不高的使用消息队列,放到MQ队列中,供消费者处理。
3、为大数据处理架构提供服务
通过消息作为整合,大数据的背景下,消息队列还与实时处理架构整合,为数据处理提供性能支持。
4、Java消息服务——JMS
Java消息服务(Java Message Service,JMS)应用程序接口是一个Java平台中关于面向消息中间件(MOM)的API,用于在两个应用程序之间,或分布式系统中发送消息,进行异步通信。
JMS中的P2P和Pub/Sub消息模式:点对点(point to point, queue)与发布订阅(publish/subscribe,topic)最初是由JMS定义的。这两种模式主要区别或解决的问题就是发送到队列的消息能否重复消费(多订阅)。

·消息中间件应用场景
·异步
·解耦
·削峰
流量削峰在秒杀系统中应用非常广泛。如果在秒杀活动中每秒上万的请求全部落到数据库,那么必将会导致数据库挂掉,从而导致订单系统崩溃。引入消息中间件后,将用户的请求写入消息队列,如果消息队列的长度超过最大值,那么直接抛弃用户的请求并返回商品秒杀结束的信息给用户。消息中间件集群中积攒的几万条消息可被订单系统慢慢的消费处理掉。瞬间上万并发的压力由消息中间件来抗,订单系统只需要从消息中间件中慢慢拉取消息进行消费,不会对数据库造成影响。
·冗余
·扩展性
·过载保护
·可恢复性
·顺序保证
·缓冲
·数据流处理

· 消息中间件常用协议

9.1 AMQP协议
AMQP即Advanced Message Queuing Protocol,一个提供统一消息服务的应用层标准高级消息队列协议,是应用层协议的一个开放标准,为面向消息的中间件设计。基于此协议的客户端与消息中间件可传递消息,并不受客户端/中间件不同产品,不同开发语言等条件的限制。

优点:可靠、通用

9.2 MQTT协议
MQTT(Message Queuing Telemetry Transport,消息队列遥测传输)是IBM开发的一个即时通讯协议,有可能成为物联网的重要组成部分。该协议支持所有平台,几乎可以把所有联网物品和外部连接起来,被用来当做传感器和致动器(比如通过Twitter让房屋联网)的通信协议。

优点:格式简洁、占用带宽小、移动端通信、PUSH、嵌入式系统

9.3 STOMP协议
STOMP(Streaming Text Orientated Message Protocol)是流文本定向消息协议,是一种为MOM(Message Oriented Middleware,面向消息的中间件)设计的简单文本协议。STOMP提供一个可互操作的连接格式,允许客户端与任意STOMP消息代理(Broker)进行交互。

优点:命令模式(非topic\queue模式)

9.4 XMPP协议
XMPP(可扩展消息处理现场协议,Extensible Messaging and Presence Protocol)是基于可扩展标记语言(XML)的协议,多用于即时消息(IM)以及在线现场探测。适用于服务器之间的准即时操作。核心是基于XML流传输,这个协议可能最终允许因特网用户向因特网上的其他任何人发送即时消息,即使其操作系统和浏览器不同。

优点:通用公开、兼容性强、可扩展、安全性高,但XML编码格式占用带宽大;

9.5 其他基于TCP/IP自定义的协议
有些特殊框架(如:redis、kafka、zeroMq等)根据自身需要未严格遵循MQ规范,而是基于TCP\IP自行封装了一套协议,通过网络socket接口进行传输,实现了MQ的功能。

·常见消息队列介绍
·RocketMQ
阿里系下开源的一款分布式、队列模型的消息中间件,原名Metaq,3.0版本名称改为RocketMQ,
·RabbitMQ
·ActiveMQ
·Kafka
·Redis
·ZeroMQ

·消息中间件的优势

1系统解耦
交互系统之间没有直接的调用关系,只是通过消息传输,故系统侵入性不强,耦合度低。

2提高系统响应时间
例如原来的一套逻辑,完成支付可能涉及先修改订单状态、计算会员积分、通知物流配送几个逻辑才能完成;通过MQ架构设计,就可将紧急重要(需要立刻响应)的业务放到该调用方法中,响应要求不高的使用消息队列,放到MQ队列中,供消费者处理。

3为大数据处理架构提供服务
通过消息作为整合,大数据的背景下,消息队列还与实时处理架构整合,为数据处理提供性能支持。

4Java消息服务——JMS
Java消息服务(Java Message Service,JMS)应用程序接口是一个Java平台中关于面向消息中间件(MOM)的API,用于在两个应用程序之间,或分布式系统中发送消息,进行异步通信。

JMS中的P2P和Pub/Sub消息模式:点对点(point to point, queue)与发布订阅(publish/subscribe,topic)最初是由JMS定义的。这两种模式主要区别或解决的问题就是发送到队列的消息能否重复消费(多订阅)。

·数据库路由

MySQL Router是MySQL官方提供的一个轻量级中间件,是InnoDB Cluster的一部分,可在应用程序和后端MySQL服务器之间提供透明路由。主要用以解决MySQL主从库集群的高可用、负载均衡、易扩展等问题。Router作为一个流量转发层,位于应用与MySQL服务器之间,其功能类似于LVS。

主要作用是为数据库集群提供一个虚拟IP作为应用程序单一连接点,通过这个单一的连接点实现负载均衡,读写分离,故障转移等数据库高可用方案。

是分布式数据库中的一个重要功能,它是在分布式架构下,实现快速访问数据的利器。使用数据库路由是在业务体量很大,数据量增长过快,所以要把用户的数据拆分到不同的库表中,以此来减轻单机的压力。数据库路由是用于分库分表操作,有两种方式。
1)垂直拆分
根据业务分类,将不同的业务表分布在不同的数据库中。可以将数据的压力分担到不同的数据库中,专库专用。
2)水平拆分

当垂直拆分遇到单机瓶颈的时候,就可以将一张数据表拆分多张表,放在不同的数据库中。
·如果使用分库分表方式,存在三个技术通用需求需要实现。
1、SQL组合:因为我们关联的表名是动态的,所以我们需要根据逻辑组装动态的SQL。
2、数据库路由:因为数据库名也是动态的,所以我们需要根据不同的逻辑使用不同的数据库。
3、执行结果合并:有些需求需要通过多个分库执行,再合并归集使用。

而市面上能解决以上问题的中间件分为2类:Proxy模式、Client模式。
·主要作用是为数据库集群提供一个虚拟IP作为应用程序单一连接点,通过这个单一的连接点实现负载均衡,读写分离,故障转移等数据库高可用方案。

·Remote Procedure Call

RPC 是一种计算机通信协议,它允许程序在网络上请求服务而不需要了解底层网络细节。RPC 的基本思想:本地计算机上的客户端程序调用远程服务器上的过程或子程序,就像调用本地过程一样,而不用关心底层网络通信的细节。

·RPC 工作原理
客户端调用:客户端通过本地调用远程过程的方式,就像调用本地方法一样
数据序列化:客户端将调用的参数序列化为可以在网络上传输的格式,如二进制流或 JSON
网络传输:序列化后的数据通过网络传输到远程服务器
数据反序列化:服务器接收到数据后,将其反序列化为原始参数
服务端执行:服务器执行相应的过程,并将结果返回
结果序列化:服务器将执行结果序列化为可以在网络上传输的格式
网络传输:序列化后的结果通过网络传输到客户端
结果反序列化:客户端接收到结果后,将其反序列化为最终的返回值
客户端接收结果:客户端获得最终的执行结果,完成整个过程

·RPC 适用场景
分布式系统场景:当系统的各个模块分布在不同的服务器上时,RPC 提供了方便的远程调用机制,使得模块间的通信更加简便
微服务架构场景:在微服务架构中,各个微服务可能运行在不同的进程或主机上,RPC 提供了一种有效的通信方式,支持微服务之间的远程调用
性能要求高场景:RPC 框架通常采用二进制协议和高效的序列化方式,因此在性能要求较高的场景中表现比较优越
多语言支持场景:RPC 框架通常支持多种编程语言,使得不同语言编写的模块可以方便地进行远程调用
服务治理场景:RPC 框架通常提供服务注册、负载均衡、服务发现等功能,有助于构建健壮的分布式系统
接口规范明确场景:RPC 框架通常使用 IDL 或者其他接口描述语言,接口规范明确,便于团队协作和版本管理
总之,RPC 在需要高效、可靠的远程调用和分布式系统通信的场景中具有较强的适用性。

·常见 RPC 框架

·gRPC

gRPC 是 Google 开发的高性能远程服务调用框架,特别适用于构建跨语音、高性能、多服务的分布式系统,尤其在微服务架构中表现优异。
特点:
跨语言支持:支持多种编程语言,使得不同语言的服务可以无缝协同工作
IDL:使用 Protobuf 作为接口定义语言,IDL 可以定义服务和消息格式,提供强大的代码生成工具,使得跨语言的通信变得更加简单
HTTP/2 协议:基于 HTTP/2 的传输协议,提供了双向流、头部压缩、多路复用等特性,显著提升了性能和效率
多种序列化支持:支持多种序列化格式,包括 Protobuf、JSON、XML 等,可以让开发根据需求选择适当的序列化方式
双向流和流控制:支持双向流式通信,可以在同一个连接上同时进行多个请求和响应,同时提供了流控制机制,确保通信的平滑进行
服务治理和负载均衡:集成了服务治理和负载均衡的功能,可以通过服务注册发现、负载均衡策略等方式优化服务通信
拦截器和中间件:提供了强大的拦截器和中间件机制,允许开发者在请求和响应的生命周期中添加自定义的逻辑,扩展框架功能
高度集成的监控和追踪:集成了常见的监控和跟踪工具,如 Prometheus、Zipkin 等,方便开发者对系统进行监测和调试

适用场景:
跨语言通信:因支持多种编程语言,适用于构建跨语言的分布式系统,特别是在大规模多语言的项目中
高性能和效率要求:因基于 HTTP/2 的传输协议和 Protobuf 的序列化格式,使得在性能和效率方面表现出色,适用于对通信性能有高要求的场景
微服务架构:因提供了服务治理和负载均衡,使其成为构建可伸缩和可靠的微服务架构的理想选择
多语言团队协作:适用于多语言团队协作的场景,可以让不同语言的服务协同工作,提高团队的开发效率
流式通信需求:因支持双向流和流控制,适用于需要实时或大规模数据传输的场景,如实时通信、视频流等

·Dubbo

Dubbo 是一个强大而灵活的分布式服务框架,适用于构建复杂的分布式系统,尤其在微服务和异构系统继承的场景中表现出色。Alibaba 是主要贡献者。
特点
分布式服务治理:提供了全面的分布式服务治理功能,包括服务注册发现、负载均衡、路由、容错、集群管理等,以支持大规模分布式系统
多协议支持:支持多种通信协议,包括 Dubbo 协议、HTTP、WebService 等,使得服务可以以不同的方式进行通信,方便集成到不同的系统中
多语言支持:Dubbo 用 Java 实现,但它支持多语言的客户端和服务端实现,允许不同语言的服务协同工作
高性能和低延迟:Dubbo 在设计上追求高性能和低延迟,采用了多种优化策略,包括基于 Netty 的通信、序列化协议等,以满足对性能要求较高的应用场景
可扩展性:框架提供了丰富的扩展点,开发者可以通过自定义扩展点实现特定的需求,使得 Dubbo 具有较强的可扩展性
智能负载均衡:支持多种负载均衡策略,包括随机、轮询、一致性哈希等,通过智能的负载均衡机制,优化服务的性能和稳定性
服务降级和容错:提供了多种容错机制,如服务降级、失败重试、容错策略等,以提高系统的可用性和鲁棒性
适用场景
分布式架构:适用于构建大规模分布式系统,提供完善的服务治理功能,使得服务的注册、发现和管理更加方便
微服务架构:Dubbo 被广泛应用于微服务架构中,支持微服务之间的通信协作,通过治理功能优化微服务架构的整体性能
异构系统集成:Dubbo 支持多语言,可以用语整合不同语言编写的系统,实现异构系统的集成和协同工作
高性能和低延迟要求场景:适用于对性能要求较高、对延迟有严格要求的应用场景,如金融、电商等
服务治理场景:对服务治理功能有较高需求的场景,如需求灵活的负载均衡、容错机制等

·Finagle

Finagle 是 Twitter 开发的高性能 RPC 框架,特别适用于构建高性能、可伸缩、可靠的分布式系统,尤其是在微服务架构中。
·Thrift
Thrift 是由 Apache 开发的一种跨语言的远程服务调用框架,具有高性能、多语言支持、动态扩展性的特点,适用于构建复杂、异构、性能要求较高的分布式系统。
·BRPC
BRPC 是由 Baidu 开发的高性能、通用的 RPC 框架,他注重性能、灵活性和可扩展性,适用于 Baidu 内部大规模服务的场景
·Motan1/2
Motan1/2 是由 Sina 微博开发的一款高性能、易扩展的分布式远程服务调用框架。


总结

未完待续
部分内容参考https://javaguide.c

  • 21
    点赞
  • 29
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值