漫谈分布式系统(十五):扩展性的最后障碍

微信扫一扫,分享到朋友圈

漫谈分布式系统(十五):扩展性的最后障碍

这是《漫谈分布式系统》系列的第 15 篇,预计会写 30 篇左右。每篇文末有为懒人准备的 TL;DR,还有给勤奋者的关联阅读。扫描文末二维码,关注公众号,听我娓娓道来。也欢迎转发朋友圈分享给更多人。

系列第一章我们讲过,分布系统把数据的存储和计算切分到多个节点,获得了


横向扩展


横向扩展的威力无穷,理论上有多少节点,就能汇聚多少存储和计算能力。

从这个角度看,分布式系统有发展成


去中心化系统(Decentralized System)

的可能。但由于架构设计上的问题,却导致了至少在大数据领域,分布式系统大都不是去中心化系统,反倒都有「中心」。

master-slave 架构导致的中心化问题

具体来说,很多分布式系统出于各自的考虑,都采用了 master-slave 的架构。

下面我们以前面文章提到最多的 HDFS 和 YARN 为例,一起看下这种设计导致的问题。

HDFS 的中心化架构

HDFS 采用 master-slave 架构,是为了统一维护元数据。

回顾下系列第 3 篇文章讲过的 HDFS 读写数据的流程。

这个是写数据的简化流程图:

这个是读数据的简化流程图:

两个图都只是示意图,没有包含 pipeline 写、packet 拆分等细节。不过足以说明问题。

从这两个图可以看到,无论是读还是写数据,都必须首先去 NameNode(以下简称 NN) 访问元数据。

而经过前面几篇数据一致性文章的讲解,元数据统一管理的好处显而易见:数据一致性得到保障。

这点非常重要,数据被拆成 block 分散在各个机器上,元数据一旦不一致,数据哪怕还在,也没法拼回可用的文件了。

但是,元数据也是数据,也是有资源开销的。而单点 NameNode (HA 也只是 standby 一个实例)的资源是有限的,横向扩展下去,总有一天会遇到瓶颈。

NN 的内存开销主要有两部分:

  • 存量数据的元数据
    ,常驻内存老年代,可以明确算出来。

  • 增量数据和操作消耗
    ,快速进出新生代,视集群文件操作繁忙程度而定。

一个文件或 block 对象的内存开销是 150 bytes,由此可以推算出第一部分的实际开销,再给第二部分预留些余量,推荐的设置是每 100 万 block 给 1GB 内存。所以,当你有 1 亿个文件或 block 对象时,需要的内存大概是 100GB。

下面这个图是我之前在一个生产集群截的 NN 的内存情况,当时文件 + block 对象一共大概 1.5 亿。

很明显,


当数据量持续增长下去,NN 的内存需求就会突破服务器的物理内存。NN 就会变成集群的性能瓶颈,甚至直接拖垮整个集群


YARN 的中心化架构

Hadoop 对计算资源采用 master-slave 架构,是为了统一调度计算资源和任务。

调度计算资源,是为了提高整体利用率;调度任务是为了组织任务执行过程。

由于不用像 HDFS NN 那样存储数据,计算资源和任务调度的性能压力更多体现在处理能力上。对外表现为处理延时(latency)。

下面是 Hadoop 第一代计算资源调度框架,即所谓 MRv1 架构图。

可以看到,


资源的调度和任务的调度,这两个职责都压在 JobTracker 的身上


随着集群规模变大导致需要调度的计算资源变多,以及数据量和业务处理程序变多导致需要调度的任务变多,都使得在服务器有限的资源下,JobTracker 的性能开始成为集群的瓶颈。

很明显,任务调度的开销,增长的速度会比资源调度快的多。毕竟上新程序比上新节点成本低的多也快的多。所以,有了所谓的 MRv2(YARN),也是现在大部分公司的主流架构:

JobTracker 的职责被一分为二,资源调度的职责保留给新角色 RM,任务调度的职责拆给了新角色ApplicationMaster,并且 AM 和任务是一对一伴生的关系,不会遇到性能瓶颈。

但是还不够,资源的调度依然可能存在性能瓶颈。

上图也是我之前从一个生产系统取的,可以看到,平均的资源调度耗时在 2ms 左右。但随着任务数的增加,这个数据会逐渐上升,直至拖累整个集群。

解决分布式系统的中心化问题

HDFS NameNode 的去中心化

经过系列前几篇关于 partitioning 的文章的洗礼,应该不难想到,解决 HDFS NameNode 中心化问题的思路,就是


拆分


多弄几个 NameNode,每个 NameNode 只负责维护一部分元数据,这样,NN 也就能横向扩展下去了。

怎么拆分呢?

HDFS 是文件系统,而文件系统是以目录树的形式组织的,很自然能想到根据目录去拆分。

社区按这个思路提出了


HDFS Federation

的实现。把目录拆分后,像 Unix 系统一样以挂载点(mount point)的形式分配给不同的 Nameservice,来提供服务。

每个 DataNode 仍然向所有 NameNode 汇报,只是会为它们维护独立的 block pool。所以,在物理上,实际存储资源仍然是 NN 们共享的,切分主要体现在逻辑上。

例如下图,划分了 3 个 NS,并分别挂载了不同的目录。

这样,内部拆分就做好了,每个 NS 都独立完整地服务自己负责的目录,互相之间没有交互,甚至不知道彼此的存在。

剩下的,就是怎么对外提供统一的视图了。

HDFS 是个比较复杂的系统,为了减小改动以免影响稳定性,社区提出了在客户端实现统一视图的方案,即所谓


ViewFS Federation


<property>

<name>fs.defaultFS</name>

<value>viewfs://clusterDemo</value>

</property>

<property>

<name>fs.viewfs.mounttable.clusterDemo.link./tmp</name>

<value>hdfs://nameservice1/tmp</value>

</property>

<property>

<name>fs.viewfs.mounttable.clusterDemo.link./hive/b.db</name>

<value>hdfs://nameservice2/hive/b.db</value>

</property>

<property>

<name>fs.viewfs.mounttable.clusterDemo.link./hive/c.db</name>

<value>hdfs://nameservice2/hive/c.db</value>

</property>

<property>

<name>fs.viewfs.mounttable.clusterDemo.link./hive/a.db</name>

<value>hdfs://nameservice3/hive/a.db</value>

</property>

只需要像上面那样在客户端配置,就能自动把某个目录的文件操作请求转发给相应的 Nameservice 了。

ViewFS Federation 虽然改动小,但也有不少缺点:

  • 迁移过程很难做到对客户端透明,比如迁移数据到新 NS 时,需要协调所有客户端同步更新配置。

  • 客户端配置维护成本高,平台部门需要对所有集群客户端有强力控制,否则没更新到的客户端就会访问失败。

这很正常,ViewFS 毕竟是以客户端为中心的拆分。所以很自然能想到以服务端为中心的拆分。

也就是所谓


Router-based Federation(RBF)


只需要在 Nameservice 上架设一层代理,统一接收客户端请求并转发到相应的 Nameservice 上去,这个代理层由新角色 Router 承担。

而挂载表配置则统一收拢到中心化的 State Store 存储,目前有基于文件和 ZooKeeper 两种实现。

有了 State Store 存储状态,Router 就是完全无状态的,可以考虑部署在负载均衡器后面。

除了响应客户端请求外,Router 还负责监听 NN 的存活状态、负载等信息,并以心跳的形式发送给 State Store。

另外,为了保证 HA 以及 namservice 层的变化能及时同步到 State Store(又一个数据一致性场景),社区建议为每个 NN 部署多个 Router。很显然,这样可能导致数据冲突,不怕,在读取的时候用前面文章提过的 quorum 算法解决就好了。

有了这层代理,客户端和服务端就彻底解耦开来。我们就可以放心大胆在服务端搞事情了,比如添加新的 subcluster、调整挂载表、subcluster 间挪动数据做 rebalance 等等,对客户端都能做到透明了。

目前,由于推出时间和对稳定性的考虑,业界大部分公司都采用 ViewFS 的方案,而 RBF 仅在 Hadoop 3 提供,但也有越来越多的公司开始部署,部分甚至等不及已经实现了类似 RBF 的私有版本。

YARN ResourceManager 的去中心化

HDFS 和 YARN 都属于 Hadoop。有 HDFS 的珠玉在前,YARN 要解决中心化问题,照抄就是了。

也就是所谓的


YARN Federation

方案。

架构上基本就是照抄 HDFS RBF。客户端请求发送给 Router,Router 从 State Store 查询到 sub-cluster 状态后,把请求转发给对应集群。

但实现上还是有些区别。

在 HDFS 里,各个 nameservice 是共享所有 DataNode 的,而拆分的核心是逻辑上的目录。

那 YARN 呢?

计算资源是动态分配的,需要就给,用完就回收,并没有逻辑上固定的拆分点。所以只能从物理上拆分。YARN Federation 下的各个 RM 并不共享所有的 NM,反而就是按照 NM 去拆分成组

但是这样会带来一个问题,原来所有 NM 都在一起集群,资源可以整体调度,现在拆分了,如果还是各个 RM 各自调度,资源如果不共享,整体利用率肯定会降低。

为了解决这个问题,就需要支持跨集群的资源调度。

于是引入一个新角色


AMRMProxy

,并部署在每个 NM 机器上。顾名思义,AMRMProxy 是给 ApplicationMaster 充当 RM 的 Proxy 的。

上图就是 YARN Federation 架构下,一个任务提交执行的主要流程。关键点有两个:

  • Client 不再向 RM 而改为向 Router 提交请求,Router 查询 PolicyStore 后,把请求转发给对应的 RM,然后创建 AM。

  • AM 不再向 RM 而是向 AMRMProxy 申请资源,AMRMProxy 查询 PolicyStore 后,给 AM 分配可能分散在各个 sub-cluster 的 container。

从根本上去中心化

前面说过,类似 HDFS 和 YARN 这样的分布式系统,是因为采用了 master-slave 的架构,才导致了中心的存在。

像 Federation 这样的方法,自然能很大程度缓解这个问题。

但是,有没有别的思路呢?退一步想想,如果一开始就没有问题,是不是就不用解决了?

那就


抛弃 master-slave 架构


仔细想想,本质上,master 的主要作用有两个:

  • 对存储系统来说,维护元数据。

  • 对计算系统来说,协调资源调度和计算过程。

这两个需求必须用中心化的架构来实现吗?不一定。

以存储系统为例,这个系列的第 12 篇文章提过的 Dynamo 就是很好的例子。Dynamo 的数据定位在客户端通过一致性哈希计算,也就没有了中心化存储元数据的需要。

当然,并不是所有系统都能像 Dynamo 这样去中心化,比如计算资源的调度就很难做到。不过,总归为我们提供了值得参考的另一种破局的思路。

TL;DR

  • 分布式系统看起来像去中心化系统,但通常都不是。

  • 中心的存在,使得分布式系统逐渐被中心的性能拖累,无法做到持续横向扩展。

  • 典型的体现是采用 master-slave 的架构,比如 HDFS 为了统一维护元数据,YARN 为了调度计算资源。

  • 从 partitioning 学到的经验,拆分就能解决性能问题。

  • HDFS Federation 以目录为单位逻辑拆分,有 ViewFS 和 Router-based 两种实现。

  • YARN 以节点为单位物理拆分,并通过 AMRMProxy 实现了跨集群的资源调度。

  • Federation 固然能
    缓解 中心化性能瓶颈,但治标不治本,更彻底的是像 Dynamo 这样抛弃 master-slave 架构的方案。

从这篇文章我们看到,分布式系统并不是完全分布式的,由于采用 master-slave 架构,导致了一旦 master 出现性能问题,就会拖累甚至拖垮整个集群。

我们也讲到了一些典型的解决中心化问题的办法。master 的性能问题算是解决了。那 slave 呢,会不会也存在性能问题?

下一篇,我们就一起看下,slave 的性能会不会有问题,或者至少,还有没有提升的空间。

原创不易

关注/分享/赞赏

给我坚持的动力

盖茨基金会向全球疫苗免疫联盟捐赠16亿美元 帮助贫困国家

上一篇

[译] 为什么 Kubernetes 如此受欢迎?你学了吗?

下一篇

你也可能喜欢

漫谈分布式系统(十五):扩展性的最后障碍

长按储存图像,分享给朋友