1、分布式系统的设计理念

 

1.1 分布式系统的目标与要素

分布式系统的目标是提升系统的整体性能和吞吐量另外还要尽量保证分布式系统的容错性(假如增加10台服务器才达到单机运行效果2倍左右的性能,那么这个分布式系统就根本没有存在的意义)。

即使采用了分布式系统,我们也要尽力运用并发编程、高性能网络框架等等手段提升单机上的程序性能。

 

1.2 分布式系统的设计两大思路:去中心化和中心化

1、中心化设计:

两个角色: 中心化的设计思想很简单,分布式集群中的节点机器按照角色分工,大体上分为两种角色: “领导” 和 “干活的”(主从节点)

角色职责: “领导”通常负责分发任务并监督“干活的”,发现谁太闲了,就想发设法地给其安排新任务,确保没有一个“干活的”能够偷懒,如果“领导”发现某个“干活的”因为劳累过度而病倒了,则是不会考虑先尝试“医治”他的,而是一脚踢出去,然后把他的任务分给其他人。其中微服务架构 Kubernetes 就恰好采用了这一设计思路。

中心化设计的问题:

中心化的设计存在的最大问题是“领导”的安危问题,如果“领导”出了问题,则群龙无首,整个集群就奔溃了。但我们难以同时安排两个“领导”以避免单点问题。

中心化设计还存在另外一个潜在的问题,既“领导”的能力问题:可以领导10个人高效工作并不意味着可以领导100个人高效工作,所以如果系统设计和实现得不好,问题就会卡在“领导”身上。

领导安危问题的解决办法: 大多数中心化系统都采用了主备两个“领导”的设计方案,可以是热备或者冷备,也可以是自动切换或者手动切换,而且越来越多的新系统都开始具备自动选举切换“领导”的能力,以提升系统的可用性。

 

2、去中心化设计思路

众生地位平等: 在去中心化的设计里,通常没有“领导”和“干活的”这两种角色的区分,大家的角色都是一样的,地位是平等的,全球互联网就是一个典型的去中心化的分布式系统,联网的任意节点设备宕机,都只会影响很小范围的功能。

“去中心化”不是不要中心,而是由节点来自由选择中心。 (集群的成员会自发的举行“会议”选举新的“领导”主持工作。最典型的案例就是ZooKeeper及Go语言实现的Etcd)

去中心化设计的问题: 去中心化设计里最难解决的一个问题是 “脑裂”问题 ,这种情况的发声概率很低,但影响很大。脑裂问题,这种情况的发生概率很低,但影响很大。脑裂指一个集群由于网络的故障,被分为至少两个彼此无法通信的单独集群,此时如果两个集群都各自工作,则可能会产生严重的数据冲突和错误。一般的设计思路是,当集群判断发生了脑裂问题时,规模较小的集群就“自杀”或者拒绝服务。

 

1.3 分布式与集群的区别

分布式: 一个业务分拆多个子业务,部署在不同的服务器上

集群:同一个业务,部署在多个服务器上。比如之前做电商网站搭的redis集群以及solr集群都是属于将redis服务器提供的缓存服务以及solr服务器提供的搜索服务部署在多个服务器上以提高系统性能、并发量解决海量存储问题。

 

1.4、分布式系统的指标

从分布式技术的起源可以看出,分布式系统的出现就是为了用廉价的、普通的机器解决单个计算机处理复杂、大规模数据和任务时存在的性能问题、资源瓶颈问题,以及可用性和可扩展性问题。换句话说,分布式的目的是用更多的机器,处理更多的数据和更复杂的任务。

由此可以看出,性能、资源、可用性和可扩展性是分布式系统的重要指标。

 

 

2、CAP定理

在理论计算机科学中,CAP定理指的是对于一个分布式计算系统来说,不可能同时满足以下三点:

 

一致性(Consistence) :所有节点访问同一份最新的数据副本 

可用性(Availability):每次请求都能获取到非错的响应——但是不保证获取的数据为最新数据

分区容错性(Partition tolerance) : 分布式系统在遇到某节点或网络分区故障的时候,仍然能够对外提供满足一致性和可用性的服务。

 

CAP仅适用于原子读写的NOSQL场景中,并不适合数据库系统。现在的分布式系统具有更多特性比如扩展性、可用性等等,在进行系统设计和开发时,我们不应该仅仅局限在CAP问题上。注意:不是所谓的3选2(不要被网上大多数文章误导了):

 

大部分人解释这一定律时,常常简单的表述为:“一致性、可用性、分区容忍性三者你只能同时达到其中两个,不可能同时达到”。实际上这是一个非常具有误导性质的说法,而且在CAP理论诞生12年之后,CAP之父也在2012年重写了之前的论文。

 

当发生网络分区的时候,如果我们要继续服务,那么强一致性和可用性只能2选1。也就是说当网络分区之后P是前提,决定了P之后才有C和A的选择。也就是说分区容错性(Partition tolerance)我们是必须要实现的。

 

因为分布式系统中系统肯定部署在多台机器上,无法保证网络做到100%的可靠,所以网络分区一定存在,即P一定存在;在出现网络分区后,就出现了可用性和一致性的问题,我们必须要在这两者之间进行取舍,因此就有了两种架构:CP架构,AP架构;

 

2.1、CP架构

当网络分区出现后,为了保证一致性,就必须拒接请求,否则无法保证一致性

  • 当没有出网络分区时,系统A与系统B的数据一致,X=1
  • 将系统A的X修改为2,X=2
  • 当出现网络分区后,系统A与系统B之间的数据同步数据失败,系统B的X=1
  • 当客户端请求系统B时,为了保证一致性,此时系统B应拒绝服务请求,返回错误码或错误信息

 

上面这种方式就违背了可用性的要求(此时系统B应拒绝服务请求,返回错误码或错误信息),只满足一致性和分区容错,即CP

 

CAP理论是忽略网络延迟,从系统A同步数据到系统B的网络延迟是忽略的

 

CP架构保证了客户端在获取数据时一定是最近的写操作,或者获取到异常信息,绝不会出现数据不一致的情况

 

2.2、AP架构

当网络分区出现后,为了保证可用性,系统B可以返回旧值,保证系统的可用性

  1. 当没有出网络分区时,系统A与系统B的数据一致,X=1
  2. 将系统A的X修改为2,X=2
  3. 当出现网络分区后,系统A与系统B之间的数据同步数据失败,系统B的X=1
  4. 当客户端请求系统B时,为了保证可用性,此时系统B应返回旧值,X=1

上面这种方式就违背了一致性的要求(此时系统B应返回旧值,X=1),只满足可用性和分区容错,即AP

 

CP架构保证了客户端在获取数据时无论返回的是最新值还是旧值,系统一定是可用的

 

CAP理论关注粒度是数据,而不是整体系统设计的策略

 

3、BASE理论核心思想

 

BASE理论由eBay架构师Dan Pritchett提出,BASE 是 Basically Available(基本可用) 、Soft-state(软状态) 和 Eventually Consistent(最终一致性) 三个短语的缩写。BASE理论是对CAP中一致性和可用性权衡的结果,其来源于对大规模互联网系统分布式实践的总结,是基于CAP定理逐步演化而来的,它大大降低了我们对系统的要求。即使无法做到强一致性,但每个应用都可以根据自身业务特点,采用适当的方式来使系统达到最终一致性。也就是牺牲数据的一致性来满足系统的高可用性,系统中一部分数据不可用或者不一致时,仍需要保持系统整体“主要可用”。

 

针对数据库领域,BASE思想的主要实现是对业务数据进行拆分,让不同的数据分布在不同的机器上,以提升系统的可用性,当前主要有以下两种做法:

  • 按功能划分数据库
  • 分片(如开源的Mycat、Amoeba等)。

由于拆分后会涉及分布式事务问题,所以eBay在该BASE论文中提到了如何用最终一致性的思路来实现高性能的分布式事务。

BASE理论的三要素:

1. 基本可用

基本可用是指分布式系统在出现不可预知故障的时候,允许损失部分可用性。但是,这绝不等价于系统不可用。

比如:响应时间上的损失:正常情况下,一个在线搜索引擎需要在0.5秒之内返回给用户相应的查询结果,但由于出现故障,查询结果的响应时间增加了1~2秒

系统功能上的损失:正常情况下,在一个电子商务网站上进行购物的时候,消费者几乎能够顺利完成每一笔订单,但是在一些节日大促购物高峰的时候,由于消费者的购物行为激增,为了保护购物系统的稳定性,部分消费者可能会被引导到一个降级页面

 

2. 软状态  软状态指允许系统中的数据存在中间状态,并认为该中间状态的存在不会影响系统的整体可用性,即允许系统在不同节点的数据副本之间进行数据同步的过程存在延时

 

3. 最终一致性   最终一致性强调的是系统中所有的数据副本,在经过一段时间的同步后,最终能够达到一个一致的状态。因此,最终一致性的本质是需要系统保证最终数据能够达到一致,而不需要实时保证系统数据的强一致性。

 

4、分布式协调与同步

 

4.1、分布式互斥

对于同一共享资源,一个程序正在使用的时候也不希望被其他程序打扰。这,就要求同一时刻只能有一个程序能够访问这种资源。

在分布式系统里,这种排他性的资源访问方式,叫作分布式互斥(Distributed Mutual Exclusion),而这种被互斥访问的共享资源就叫作临界资源(Critical Resource)。

 

集中式算法

引入一个协调者程序,得到一个分布式互斥算法。每个程序在需要访问临界资源时,先给协调者发送一个请求。如果当前没有程序使用这个资源,协调者直接授权请求程序访问;否则,按照先来后到的顺序为请求程序“排一个号”。如果有程序使用完资源,则通知协调者,协调者从“排号”的队列里取出排在最前面的请求,并给它发送授权消息。拿到授权消息的程序,可以直接去访问临界资源。这个互斥算法,就是我们所说的集中式算法,也可以叫做中央服务器算法。之所以这么称呼,是因为协调者代表着集中程序或中央服务器。

 

分布式算法

既然引入协调者会带来一些问题,这时你可能就会想,不用协调者是否可以实现对临界资源的互斥访问呢?想象一下,当你需要使用自助咖啡机的时候,是不是可以先去征求其他人的意见,在确认其他人都没在使用也暂时不会使用咖啡机时,你就可以放心大胆地去泡制自己的咖啡了呢?

 

同理,我们可以把这套算法用于分布式系统。当一个程序要访问临界资源时,先向系统中的其他程序发送一条请求消息,在接收到所有程序返回的同意消息后,才可以访问临界资源。其中,请求消息需要包含所请求的资源、请求者的 ID,以及发起请求的时间。这,就是民主协商法。在分布式领域中,我们称之为分布式算法,或者使用组播和逻辑时钟的算法。

 

如图所示,程序 1、2、3 需要访问共享资源 A。在时间戳为 8 的时刻,程序 1 想要使用资源 A,于是向程序 2 和 3 发起使用资源 A 的申请,希望得到它们的同意。在时间戳为 12的时刻,程序 3 想要使用资源 A,于是向程序 1 和 2 发起访问资源 A 的请求。

分布式项目架构 分布式项目的设计思想_分布式项目架构

分布式算法是一个“先到先得”和“投票全票通过”的公平访问机制,但通信成本较高,可用性也比集中式算法低,适用于临界资源使用频度较低,且系统规模较小的场景。

 

令牌环算法

 

类似华为的轮值 CEO 。在华为的轮值 CEO 体系里,CEO 就是临界资源,同时只能有一个人担任,由多名高管轮流出任CEO。

 

类似地,程序访问临界资源问题也可按照轮值 CEO 的思路实现。 如下图所示,所有程序构成一个环结构,令牌按照顺时针(或逆时针)方向在程序之间传递,收到令牌的程序有权访问临界资源,访问完成后将令牌传送到下一个程序;若该程序不需要访问临界资源,则直接把令牌传送给下一个程序。

 

在分布式领域,这个算法叫作令牌环算法,也可以叫作基于环的算法。

分布式项目架构 分布式项目的设计思想_服务器_02

因为在使用临界资源前,不需要像分布式算法那样挨个征求其他程序的意见了,所以相对而言,在令牌环算法里单个程序具有更高的通信效率。同时,在一个周期内,每个程序都能访问到临界资源,因此令牌环算法的公平性很好。

但是,不管环中的程序是否想要访问资源,都需要接收并传递令牌,所以也会带来一些无效通信。假设系统中有 100 个程序,那么程序 1 访问完资源后,即使其它 99 个程序不需要访问,也必须要等令牌在其他 99 个程序传递完后,才能重新访问资源,这就降低了系统的实时性。

 

综上,令牌环算法非常适合通信模式为令牌环方式的分布式系统,例如移动自组织网络系统。一个典型的应用场景就是无人机通信。

 

对于集中式和分布式算法都存在的单点故障问题,在令牌环中,若某一个程序出现故障,则直接将令牌传递给故障程序的下一个程序(例如,上图中无人机 1直接将令牌传送给无人机 3),从而很好地解决单点故障问题,提高系统的健壮性,带来更好的可用性。但,这就要求每个程序都要记住环中的参与者信息,这样才能知道在跳过一个参与者后令牌应该传递给谁。

 

4.2、分布式选举

 

为什么要有分布式选举?

 

主节点,在一个分布式集群中负责对其他节点的协调和管理,也就是说,其他节点都必须听从主节点的安排。

主节点的存在,就可以保证其他节点的有序运行,以及数据库集群中的写入数据在每个节点上的一致性。这里的一致性是指,数据在每个集群节点中都是一样的,不存在不同的情况。当然,如果主故障了,集群就会天下大乱,就好比一个国家的皇帝驾崩了,国家大乱一样。比如,数据库集群中主节点故障后,可能导致每个节点上的数据会不一致。这,就应了那句话“国不可一日无君”,对应到分布式系统中就是“集群不可一刻无主”。总结来说,选举的作用就是选出一个主节点,由它来协调和管理其他节点,以保证集群有序运行和节点间数据的一致性。

 

分布式选举的算法

那么,如何在集群中选出一个合适的主呢?这是一个技术活儿,目前常见的选主方法有基于序号选举的算法( 比如,Bully 算法)、多数派算法(比如,Raft 算法、ZAB 算法)等。接下来,就和我一起来看看这几种算法吧。

 

Bully 算法

Bully 算法是一种霸道的集群选主算法,为什么说是霸道呢?因为它的选举原则是“长者”为大,即在所有活着的节点中,选取 ID 最大的节点作为主节点。在 Bully 算法中,节点的角色有两种:普通节点和主节点。初始化时,所有节点都是平等的,都是普通节点,并且都有成为主的权利。但是,当选主成功后,有且仅有一个节点成为主节点,其他所有节点都是普通节点。当且仅当主节点故障或与其他节点失去联系后,才会重新选主。

 

目前已经有很多开源软件采用了 Bully 算法进行选主,比如 MongoDB 的副本集故障转移功能。MongoDB 的分布式选举中,采用节点的最后操作时间戳来表示 ID,时间戳最新的节点其 ID 最大,也就是说时间戳最新的、活着的节点是主节点。

 

 

Raft 算法

Raft 算法是典型的多数派投票选举算法,其选举机制与我们日常生活中的民主投票机制类似,核心思想是“少数服从多数”。也就是说,Raft 算法中,获得投票最多的节点成为主。

 

Google 开源的 Kubernetes,擅长容器管理与调度,为了保证可靠性,通常会部署 3 个节点用于数据备份。这 3 个节点中,有一个会被选为主,其他节点作为备。Kubernetes 的选主采用的是开源的 etcd 组件。而,etcd 的集群管理器 etcds,是一个高可用、强一致性的服务发现存储仓库,就是采用了 Raft 算法来实现选主和一致性的。

 

 

ZAB 算法

ZAB(ZooKeeper Atomic Broadcast)选举算法是为 ZooKeeper 实现分布式协调功能而设计的。相较于 Raft 算法的投票机制,ZAB 算法增加了通过节点 ID 和数据 ID 作为参考进行选主,节点 ID 和数据 ID 越大,表示数据越新,优先成为主。相比较于 Raft 算法,ZAB 算法尽可能保证数据的最新性。所以,ZAB 算法可以说是对 Raft 算法的改进。

 

使用 ZAB 算法选举时,集群中每个节点拥有 3 种角色:

  • Leader,主节点;
  • Follower,跟随者节点;
  • Observer,观察者,无投票权。

选举过程中,集群中的节点拥有 4 个状态:

  • Looking 状态,即选举状态。当节点处于该状态时,它会认为当前集群中没有 Leader,因此自己进入选举状态。
  • Leading 状态,即领导者状态,表示已经选出主,且当前节点为 Leader。
  • Following 状态,即跟随者状态,集群中已经选出主后,其他非主节点状态更新为Following,表示对 Leader 的追随。
  • Observing 状态,即观察者状态,表示当前节点为 Observer,持观望态度,没有投票权和选举权。

投票过程中,每个节点都有一个唯一的三元组 (server_id, server_zxID, epoch),其中server_id 表示本节点的唯一 ID;server_zxID 表示本节点存放的数据 ID,数据 ID 越大表示数据越新,选举权重越大;epoch 表示当前选取轮数,一般用逻辑时钟表示。

 

ZAB 选举算法的核心是“少数服从多数,ID 大的节点优先成为主”,因此选举过程中通过(vote_id, vote_zxID) 来表明投票给哪个节点,其中 vote_id 表示被投票节点的 ID,vote_zxID 表示被投票节点的服务器 zxID。ZAB 算法选主的原则是:server_zxID 最大者成为 Leader;若 server_zxID 相同,则 server_id 最大者成为 Leader。

4.3、分布式共识

假设,现在有 5 台服务器,分散在美国华盛顿、英国伦敦、法国巴黎、中国北京、中国上海,分别对应着用户{A,B,C,D,E}。现在,用户 A 给用户 B 转了 100 元。

在传统方法中,我们通过银行进行转账并记录该笔交易。但分布式在线记账方法中,没有银行这样的一个集中方,而是由上述 5 台服务器来记录该笔交易。但是,这 5 台服务器均是有各自想法的个体,都可以自主操作或记录,那么如何保证记录的交易是一致的呢?这,就

是分布式共识技术要解决的问题。

 

可以看出,分布式共识就是在多个节点均可独自操作或记录的情况下,使得所有节点针对某个状态达成一致的过程。通过共识机制,我们可以使得分布式系统中的多个节点的数据达成一致。

 

看到这里,相信你已经看出来了,我在这里说的分布式在线记账,就是近几年比较火的区块链技术解决的问题。而分布式共识技术,就是区块链技术共识机制的核心。

 

4.4、分布式事务

分布式事务,就是在分布式系统中运行的事务,由多个本地事务组合而成。在分布式场景下,对事务的处理操作可能来自不同的机器,甚至是来自不同的操作系统。

 

如何实现分布式事务?

实际上,分布式事务主要是解决在分布式环境下,组合事务的一致性问题。实现分布式事务有以下 3 种基本方法:

  1. 基于 XA 协议的二阶段提交协议方法;
  2. 三阶段提交协议方法;
  3. 基于消息的最终一致性方法。

 

4.5、分布式锁

基于数据库实现分布式锁,这里的数据库指的是关系型数据库;

基于缓存实现分布式锁;

基于 ZooKeeper 实现分布式锁。

 

基于数据库实现分布式锁比较简单,绝招在于创建一张锁表,为申请者在锁表里建立一条记录,记录建立成功则获得锁,消除记录则释放锁。该方法依赖于数据库,主要有两个缺点:

  1. 单点故障问题。一旦数据库不可用,会导致整个系统崩溃。
  2. 死锁问题。数据库锁没有失效时间,未获得锁的进程只能一直等待已获得锁的进程主动释放锁。一旦已获得锁的进程挂掉或者解锁操作失败,会导致锁记录一直存在数据库中,其他进程无法获得锁。

 

 

5、分布式资源管理和负载调度

 

5.1、分布式体系结构——集中式结构

集中式结构就是,由一台或多台服务器组成中央服务器,系统内的所有数据都存储在中央服务器中,系统内所有的业务也均先由中央服务器处理。多个节点服务器与中央服务器连接,并将自己的信息汇报给中央服务器,由中央服务器统一进行资源和任务调度:中央服务器根据这些信息,将任务下达给节点服务器;节点服务器执行任务,并将结果反馈给中央服务器。

 

 

集中式结构最大的特点,就是部署结构简单。这是因为,集中式系统的中央服务器往往是多个具有较强计算能力和存储能力的计算机,为此中央服务器进行统一管理和调度任务时,无需考虑对任务的多节点部署,而节点服务器之间无需通信和协作,只要与中央服务器通信协作即可。

 

5.2、分布式体系结构——非集中式结构

在非集中式结构中,服务的执行和数据的存储被分散到不同的服务器集群,服务器集群间通过消息传递进行通信和协调。

 

也就是说,在非集中式结构中,没有中央服务器和节点服务器之分,所有的服务器地位都是平等(对等)的,也就是我们常说的“众生平等”。这样一来,相比于集中式结构,非集中式结构就降低了某一个或者某一簇计算机集群的压力,在解决了单点瓶颈和单点故障问题的同时,还提升了系统的并发度,比较适合大规模集群的管理。所以近几年来,Google、 Amazon、Facebook、阿里巴巴、腾讯等互联网公司在一些业务中也相继采用了非集中式结构。

 

 

 

 

 

6、分布式计算技术

 

 

7、分布式是通信技术

 

7.1、分布式通信——远程调用

分布式项目架构 分布式项目的设计思想_数据_03

  1. 本地服务器也就是机器 A 中的订单系统,调用本地服务器上的支付系统中的支付操作Pay(Order),该方法会直接调用 Client Stub(其中,Stub 是用于转换 RPC 过程中在订单系统和支付系统所在机器之间传递的参数),这是一次正常的本地调用。
  2. Client Stub 将方法 Pay、参数 Order 等打包成一个适合网络传输的消息,通过执行一次系统调用(也就是调用操作系统中的函数)来发送消息。
  3. 订单系统所在机器 A 的本地操作系统通过底层网络通信,将打包好的消息根据支付系统所在机器 B 的地址发送出去。
  4. 机器 B 上的操作系统接收到消息后,将消息传递给 Server Stub。
  5. 机器 B 上的 Server Stub 将接收到的消息进行解包,获得里面的参数,然后调用本地的支付订单的操作 Pay(Order)。
  6. 机器 B 上的支付操作 Pay(Order) 完成后,将结果发送给 Server Stub,其中结果可使用XDR(External Data Representation,一种可以在不同计算机系统间传输的数据格式)语言表示。
  7. 机器 B 上的 Server Stub 将结果数据打包成适合网络传输的消息,然后进行一次系统调用发送消息。
  8. 机器 B 的本地操作系统通过底层网络将打包好的消息发送回机器 A。
  9. 机器 A 的操作系统接收到来自机器 B 的消息,并将消息发送给本地的 Client Stub。
  10. 本地的 Client Stub 将消息解包,然后将解包得到的结果返回给本地的订单系统。

 

从整个流程可以看出,机器 A 上的 Pay(Order)、 Client Stub 和网络调用之间的交互属于本地调用,机器 B 上的 Pay(Order)、Server Stub 和网络调用之间的交互也属于本地调用。而机器 A 和机器 B 之间的远程调用的核心是,发生在机器 A 上的网络调用和机器 B上的网络调用。

 

RPC 的目的,其实就是要将第 2 到第 8 步的几个过程封装起来,让用户看不到这些细节。从用户的角度看,订单系统的进程只是做了一次普通的本地调用,然后就得到了结果。也就是说,订单系统进程并不需要知道底层是如何传输的,在用户眼里,远程过程调用和调

用一次本地服务没什么不同。这,就是 RPC 的核心。

 

 

RPC 与本地调用主要有三点不同。

第一个区别是,调用 ID 和函数的映射。在本地调用中,进程内可共享内存地址空间,因此程序可直接通过函数名来调用函数。而函数名的本质就是一个函数指针,可以看成函数在内存中的地址。比如,调用函数 f(),编译器会帮我们找到函数 f() 相应的内存地址。但在RPC 中,只通过函数名是不行的,因为不同进程的地址空间是不一样的。

 

所以在 RPC 中,所有的函数必须要有一个调用 ID 来唯一标识。一个机器上运行的进程在做远程过程调用时,必须附上这个调用 ID。

 

另外,我们还需要在通信的两台机器间,分别维护一个函数与调用 ID 的映射表。两台机器维护的表中,相同的函数对应的调用 ID 必须保持一致。

 

当一台机器 A 上运行的进程 P 需要远程调用时,它就先查一下机器 A 维护的映射表,找出对应的调用 ID,然后把它传到另一台机器 B 上,机器 B 通过查看它维护的映射表,从而确定进程 P 需要调用的函数,然后执行对应的代码,最后将执行结果返回到进程 P。

 

 

序列化和反序列化。我们知道了调用方调用远程服务时,需要向被调用方传输调用 ID 和对应的函数参数,那调用方究竟是怎么把这些数据传给被调用方的呢?

 

在本地调用中,进程之间共享内存等,因此我们只需要把参数压到栈里,然后进程自己去栈里读取就行。但是在 RPC 中,两个进程分布在不同的机器上,使用的是不同机器的内存,因此不可能通过内存来传递参数。

 

而网络协议传输的内容是二进制流,无法直接传输参数的类型,因此这就需要调用方把参数先转成一个二进制流,传到被调用方后,被调用方再把二进制流转换成自己能读取的格式。这个过程,就叫作序列化和反序列化。

 

同理,被调用方返回的结果也需要有序列化和反序列化的过程,不然调用方无法获取到结果。也就是说,RPC 与本地调用相比,参数的传递需要进行序列化和反序列化操作。

 

第三个区别是,网络传输协议。序列化和反序列化解决了调用方和被调用方之间的数据传输格式问题,但要想序列化后的数据能在网络中顺利传输,还需要有相应的网络协议,比如TCP、UDP 等,因此就需要有一个底层通信层。

调用方通过该通信层把调用 ID 和序列化后的参数传给被调用方,被调用方同样需要该通信层将序列化后的调用结果返回到调用方。

 

也就是说,只要调用方和被调用方可以互传数据,就可以作为这个底层通信层。因此,它所使用的网络协议可以有很多,只要能完成网络传输即可。目前来看,大部分 RPC 框架采用的是 TCP 协议。

 

 

Dubbo 就是在引入服务注册中心的基础上,又加入了监控中心组件(用来监控服务的调用情况,以方便进行服务治理),实现了一个 RPC 框架。如下图所示,Dubbo 的架构主要包括 4 部分:

  • 服务提供方。服务提供方会向服务注册中心注册自己提供的服务。
  • 服务注册中心。服务注册与发现中心,负责存储和管理服务提供方注册的服务信息和服务调用方订阅的服务类型等。
  • 服务调用方。根据服务注册中心返回的服务所在的地址列表,通过远程调用访问远程服务。
  • 监控中心。统计服务的调用次数和调用时间等信息的监控中心,以方便进行服务管理或服务失败分析等。

 

 

RMI 的原理及应用

 

RMI 是一个基于 Java 环境的应用编程接口,能够让本地 Java 虚拟机上运行的对象,像调用本地对象一样调用远程 Java 虚拟机上的对象。

 

RMI 可以说是 RPC 的一种具体形式,其原理与 RPC 基本一致,唯一不同的是 RMI 是基于对象的,充分利用了面向对象的思想去实现整个过程,其本质就是一种基于对象的 RPC 实现。

 

RMI 与 PRC 最大的不同在于调用方式和返回结果的形式,RMI 通过对象作为远程接口来进行远程方法的调用,返回的结果也是对象形式,可以是 Java 对象类型,也可以是基本数据类型。 RMI 的典型实现框架有 EJB(Enterprise JavaBean,企业级 JavaBean),如果你需要深入了解这个框架的话,可以参考其官方文档。

 

 

7.2、分布式通信——发布订阅

 

生产者可以发送消息到消息中心,而消息中心通常以主题(Topic)进行划分,每条消息都会有相应的主题,消息会被存储到自己所属的主题中,订阅该主题的所有消费者均可获得该消息进行消费。

 

Kafka 发布订阅原理及工作机制

Kafka 是一种典型的发布订阅消息系统,其系统架构也是包括生产者、消费者和消息中心三部分。

 

  1. 生产者(Producer)负责发布消息到消息中心,比如电子论文的会议方或出版社;
  2. 消费者(Consumer)向消息中心订阅自己感兴趣的消息,获得数据后进行数据处理,比如订阅电子论文的老师或学生;
  3. 消息中心(Broker)负责存储生产者发布的消息和管理消费者订阅信息,根据消费者订阅信息,将消息推送给消费者,比如论文网站。在 Kafka 中,消息中心本质上就是一组服务器,也可以说是 Kafka 集群。

 

Kafka 的架构图,如下所示:

分布式项目架构 分布式项目的设计思想_分布式系统_04

可以看到,Kafka 中除了 Producer、Broker、Consumer 之外,还有一个 ZooKeeper 集群。Zookeeper 集群用来协调和管理 Broker 和 Consumer,实现了 Broker 和Consumer 的解耦,并为系统提供可靠性保证。

 

ZooKeeper 集群可以看作是一个提供了分布式服务协同能力的第三方组件,Consumer 和Broker 启动时均会向 ZooKeeper 进行注册,由 ZooKeeper 进行统一管理和协调。

 

ZooKeeper 中会存储一些元数据信息,比如对于 Broker,会存储主题对应哪些分区(Partition),每个分区的存储位置等;对于 Consumer,会存储消费组(Consumer  Group)中包含哪些 Consumer,每个 Consumer 会负责消费哪些分区等。

7.3、分布式通信——消息队列

 

发布订阅就是发布者产生数据到消息中心,订阅者订阅自己感兴趣的消息,消息中心根据订阅者的订阅情况,将相关消息或数据发送给对应的订阅者。所以,我将其的思想,概括为“送货上门”。在实际使用场景中,还有一种常用的通信方式,就是将消息或数据放到一个队列里,谁需要谁就去队列里面取。在分布式领域中,这种模式叫“消息队列”。

 

与发布订阅相比,消息队列技术的核心思想可以概括为“货物自取”。

 

 

8、分布式数据存储

 

分布式存储系统的核心逻辑,就是将用户需要存储的数据根据某种规则存储到不同的机器上,当用户想要获取指定数据时,再按照规则到存储数据的机器里获取。

如下图所示,当用户(即应用程序)想要访问数据 D,分布式操作引擎通过一些映射方式,比如 Hash、一致性 Hash、数据范围分类等,将用户引导至数据 D 所属的存储节点获取数据。

分布式项目架构 分布式项目的设计思想_服务器_05

数据分片技术,是指分布式存储系统按照一定的规则将数据存储到相对应的存储节点中,或者到相对应的存储节点中获取想要的数据,这是一种很常用的导购技术。这种技术,一方面可以降低单个存储节点的存储和访问压力;另一方面,可以通过规定好的规则快速找到数据所在的存储节点,从而大大降低搜索延迟,提高用户体验。

 

也就是说,当铁路局发布各个线路的火车票信息时,会按照一定规则存储到相应的机器中,

比如北京到上海的火车票存储到机器 A 中,西安到重庆的火车票存储到机器 B 中。当乘客查询火车票时,系统就可以根据查询条件迅速定位到相对应的存储机器,然后将数据返回给用户,响应时间就大大缩短了。如图所示,当查询北京 - 上海的火车票相关信息时,可以与机器 A 进行数据交互。

 

 

8.1、分布式数据复制技术

 

概括来讲,数据复制是一种实现数据备份的技术。比如,现在有节点 1 和节点 2,节点 1上存储了 10M 用户数据,直观地说,数据复制技术就是将节点 1 上的这 10M 数据拷贝到节点 2 上,以使得节点 1 和节点 2 上存储了相同的数据,也就是节点 2 对节点 1 的数据进行了备份。当节点 1 出现故障后,可以通过获取节点 2 上的数据,实现分布式存储系统的自动容错。

 

同步复制技术原理及应用

 

异步复制技术原理及应用

异步复制技术是指,当用户请求更新数据时,主数据库处理完请求后可直接给用户响应,而不必等待备数据库完成同步,即备数据库会异步进行数据的同步,用户的更新操作不会因为备数据库未完成数据同步而导致阻塞。显然,这种方式保证了系统的可用性,但牺牲了数据的一致性。

 

MySQL 集群默认的数据复制模式采用的是异步复制技术。

 

 

8.2、分布式缓存,一致性哈希

 

分布式缓存就是指在分布式环境或系统下,把一些热门数据存储到离用户近、离应用近的位置,并尽量存储到更快的设备,以减少远程数据传输的延迟,让用户和应用可以很快访问到想要的数据。这,是不是可以形象地理解为“身手钥钱”随身带呢?

 

 

 

 

9、分布式高可靠

 

负载均衡:轮询、随机、哈希和一致性哈希

 

流量控制:漏铜、令牌桶、

 

故障隔离:故障隔离就是,把故障通过某种方式与其他正常模块进行隔离,以保证某一模块出现故障后,不会影响其他模块。所以说,故障隔离,可以避免分布式系统出现大规模的故障,甚至是瘫痪,降低损失。

 

线程级隔离

线程级故障隔离,是指使用不同的线程池处理不同的请求任务。当某种请求任务出现故障时,负责其他请求任务的线程池不会受到影响,即会继续提供服务,从而实现故障的隔离。

 

进程级隔离:

随着业务逐渐扩大,业务系统也会越来越复杂,单体应用可能无法满足公司与用户的需求,这时候就需要对系统进行拆分。

一种常用的方式就是,将系统按照功能分为不同的进程,分布到相同或不同的机器中。如果系统的进程分布到不同机器上的话,从资源的角度来看,也可以说成是主机级的故障隔离。因为从另一个层面看,系统确实分布到了不同机器上,当某个机器出现故障时,不会对其他

机器造成影响。

 

系统实现进程级隔离后,进程间的协同必须通过进程间通信(IPC)来实现。进程间通信有很多方式,大体可以分为以下两类:

  1. 进程级故障隔离,目前在分布式应用中应用广泛,比如常见的电商、火车票购买等业务都可以采用。
  2. 如果进程都在同一台机器上,则可以通过管道、消息队列、信号量、共享内存等方式,来实现;

 

如果进程分布在不同机器上,则可以通过远程调用来实现,你可以再回顾下中的相关内容。

 

资源隔离

前面介绍的是以服务或功能模块为粒度进行隔离的,下面我们一起看下从资源角度进行隔离是怎么做的?

 

简单来说,资源隔离就是将分布式系统的所有资源分成几个部分,每部分资源负责一个模块,这样系统各个模块就不会争抢资源,即资源之间互不干扰。这种方式不仅可以提高硬件资源利用率,也便于系统的维护与管理,可以大幅提升系统性能。

 

 

微服务就是一个典型的例子。当前,很多公司都在将自己的业务系统微服务化,比如亚马逊、阿里、华为、微软等。在微服务的理念中,是尽可能将服务最小化,服务与服务之间进行解耦合,包括运行环境的相互隔离等。比如,现在通常采用容器进行隔离,我在

中分享的 Mesos、Kubernetes 等可实现容器管理与调度,而 Mesos 和 Kuberntes的上层应用很多都是微服务。实际上,在微服务框架中,一个服务通常对应一个容器,而一个容器其实就是操作系统中一个进程,不同容器负责不同的服务,就类似于刚才所讲的:不同进程负责系统不同的功能模块。

 

如图所示,如果将电商购物平台微服务化,则可以启动三个容器,分别负责订单服务、支付服务和配送服务。一个容器对应一个进程,因此微服务框架本质上还是一种进程级故障隔离策略。

分布式项目架构 分布式项目的设计思想_分布式项目架构_06

但与进程级隔离不同的是,微服务框架采用容器进行故障隔离。容器虽然本质上是操作系统的一个进程,但具备普通进程不具备的特性,比如资源隔离。

 

一个普通进程有很大的计算或内存需求时,可能会占满物理机上所有的 CPU、内存资源,导致其他进程没有资源可用,引发进程间的资源争夺;但容器可以实现资源限制,让每个容器占用的资源都有一个上限,比如 CPU、内存,均会设置一个上限值,这个上限值限定了该容器的处理能力,就好比一台服务器具有资源上限值一样。因此,一个容器使用的资源不会影响其他容器的资源,从而避免资源争抢,提高性能。

 

 

 

 未完成,待补充。。。。。。。