mirror of
https://github.com/cheetahlou/CategoryResourceRepost.git
synced 2025-10-22 01:43:44 +08:00
mod
This commit is contained in:
@@ -0,0 +1,170 @@
|
||||
<audio id="audio" title="09 | 分布式体系结构之集中式结构:一人在上,万人在下" controls="" preload="none"><source id="mp3" src="https://static001.geekbang.org/resource/audio/0b/82/0b232a4e9e54c1d03cc1066ac18dbf82.mp3"></audio>
|
||||
|
||||
你好,我是聂鹏程。今天,我来继续带你打卡分布式核心技术。
|
||||
|
||||
云这个话题对我们来说已经非常熟悉了。可以说,云在我们的生活中无处不在,比如我们平时看的视频通常就是放在云上的。当我们要播放一段视频时,请求会先转发到云上,从云上下载数据到本地,然后播放。在这里,你肯定会疑惑,云上资源那么丰富吗,可以存放这么多东西吗?
|
||||
|
||||
云上的资源确实丰富,因为它可以尽可能地把更多的服务器组织起来,作为一个统一的资源,为多个用户提供服务。**这里的重点是,把多个服务器管理起来,作为一个统一的资源提供服务**。而如何组织,就是分布式体系结构的范畴了。
|
||||
|
||||
你会发现,很多场景下,我们的请求都会汇总到一台服务器上,由这台服务器统一协调我们的请求和其他服务器之间的关系。这种由一台服务器统一管理其他服务器的方式,就是分布式体系结构中的集中式结构(也称为Master/Slave架构),其中统一管理其他服务器的服务器是主,其他服务器是从,可以形象地比喻为“一人在上,万人在下”。
|
||||
|
||||
接下来,我就带你一起打卡分布式体系结构中的集中式结构吧。
|
||||
|
||||
## 什么是集中式结构?
|
||||
|
||||
集中式结构就是,由一台或多台服务器组成中央服务器,系统内的所有数据都存储在中央服务器中,系统内所有的业务也均先由中央服务器处理。多个节点服务器与中央服务器连接,并将自己的信息汇报给中央服务器,由中央服务器统一进行资源和任务调度:中央服务器根据这些信息,将任务下达给节点服务器;节点服务器执行任务,并将结果反馈给中央服务器。
|
||||
|
||||
集中式结构最大的特点,就是部署结构简单。这是因为,集中式系统的中央服务器往往是多个具有较强计算能力和存储能力的计算机,为此中央服务器进行统一管理和调度任务时,无需考虑对任务的多节点部署,而节点服务器之间无需通信和协作,只要与中央服务器通信协作即可,具体示意图如下所示:
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/63/ba/63142a07af92ad80db90da02472489ba.png" alt="">
|
||||
|
||||
## 经典集中式结构
|
||||
|
||||
现在,我们理解了什么是集中式结构,为了加深理解,接下来我以Google Borg、Kubernetes和Apache Mesos三个经典的集群管理系统为例,带你深入学习集中式结构的原理。
|
||||
|
||||
### Google Borg
|
||||
|
||||
Borg是Google内部使用的集群管理系统,采用了典型的集中式结构,负责提交、调度、开始、重启和管理Google运行在其上的所有应用。
|
||||
|
||||
在Borg中,一个集群称为一个Cell,每个Cell里面有一个Leader,称为BorgMaster,即为中央服务器;其他服务器为节点服务器或从服务器,被称为Borglet。
|
||||
|
||||
首先,我们一起看看**BorgMaster**。它由两个进程组成,一个是Borgmaster主进程,一个是独立的scheduler进程:
|
||||
|
||||
- 主进程处理客户端的RPC请求,比如任务的执行状态更新或者查询等;同时,管理系统中所有实体的状态(比如,服务器、任务等),并负责和Borglet通信。
|
||||
- scheduler进程负责任务调度,通过任务对资源的需求以及当前Borglet所在服务器的资源情况进行匹配,为任务寻找一个合适的节点服务器执行。我会在第11篇文章“分布式调度之单体调度:物质文明、精神文明一手抓”中与你详细讲述具体的调度流程。
|
||||
|
||||
**接下来,我们一起看看Borglet。**它是运行在每个节点机器的一个agent,负责任务的拉起、停止、重启等,并管理和收集本服务器资源,将任务的状态、服务器状态等信息上报给BorgMaster。而BorgMaster会周期性地轮询每个Borglet,以获取节点服务器的状态和资源信息等。
|
||||
|
||||
Borg的整体架构示意图如下所示:
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/7a/b7/7a0390a4cdc10fdd3cebd36278d870b7.png" alt="">
|
||||
|
||||
>
|
||||
备注:此图引自[Borg论文](https://storage.googleapis.com/pub-tools-public-publication-data/pdf/43438.pdf)。
|
||||
|
||||
|
||||
Borg的主要用户是Google的开发者以及运行Google应用和服务的系统管理员(网站可靠性工程师,简称SRE)。用户以Job的形式向Borg提交工作,每个Job由运行一个或多个运行相同程序的Task组成。每个Job运行在一个Borg Cell中,并将一组机器当作一个单元进行管理。
|
||||
|
||||
Borg可以运行各种各样的任务,这些任务主要分为两类:
|
||||
|
||||
- 第一类是**长服务**,即长时间运行不停止的服务,并且要求能够处理短暂的、延迟敏感的请求(延迟要求在几微秒到几百毫秒之间)。这些服务主要用于面向终端用户的服务(比如Gmail、Google Docs、Web搜索),以及内部的一些基础设施服务(比如BigTable)。
|
||||
- 第二类是**批处理任务**。通常需要几秒到几天的时间来完成的批处理Job,这些Job对短时间的性能波动并不是非常敏感。
|
||||
|
||||
这些负载通常在Cell之间混合分布,每个Cell随着主要租户以及时间的不同会运行各种不同的应用:批处理类型的Job来了又走,而许多面向终端用户的Job又期望一个能长时间使用的模式。
|
||||
|
||||
对于这些不同的服务,要求Borg能很好地处理所有的情况。Borg主要有三大优点:
|
||||
|
||||
- 开发者只需关注应用,不需要关注底层资源管理。它隐藏了资源管理以及错误处理,因此用户能集中精力开发应用。
|
||||
- 高可靠性和可用性,支持多种应用。
|
||||
- 支持上千级服务器的管理和运行。
|
||||
|
||||
Borg并不是第一个解决这些问题的系统,但却是少数能在这么大规模处理这些问题的同时,还能实现这样的弹性和完整性的系统之一。
|
||||
|
||||
### Kubernetes
|
||||
|
||||
Kubernetes是Google开源的容器集群管理系统,是Borg的一个开源版本。Kubernetes 是用于自动部署、扩展和管理容器化应用程序的开源系统。其核心是,在集群的节点上运行容器化应用,可以进行自动化容器操作,包括部署、调度和在节点间弹性伸缩等。
|
||||
|
||||
Kubernetes也是典型的集中式结构,一个Kubernetes集群,主要由Master节点和Worker节点组成,以及客户端命令行工具kubectl和其他附加项。
|
||||
|
||||
**我们先来看看Master节点。**它运行在中心服务器上,Master节点由API Server、Scheduler、Cluster State Store和Control Manger Server组成,负责对集群进行调度管理。
|
||||
|
||||
- API Server:是所有REST命令的入口,负责处理REST的操作,确保它们生效,并执行相关业务逻辑。
|
||||
- Scheduler:根据容器需要的资源以及当前Worker节点所在节点服务器的资源信息,自动为容器选择合适的节点服务器。
|
||||
- Cluster State Store:集群状态存储,默认采用etcd,etcd是一个分布式key-value存储,主要用来做共享配置和服务发现。
|
||||
- Control Manager:负责整个集群的编排管理。它监视群集中节点的离开和加入,将群集的当前状态与etcd中存储的所需状态进行核对。比方说,当某个节点发生故障,它会在其它节点上增加新的Pod以匹配所需的副本数。
|
||||
|
||||
**接下来,我们看看Worker节点吧。**它作为真正的工作节点,运行在从节点服务器,包括kubelet和kube-proxy核心组件,负责运行业务应用的容器。
|
||||
|
||||
- kubelet:用于通过命令行与API Server进行交互,根据接收到的请求对Worker节点进行操作。也就是说,通过与API Server进行通信,接收Master节点根据调度策略发出的请求或命令,在Worker节点上管控容器(Pod),并管控容器的运行状态(比如,重新启动出现故障的Pod)等。Pod是Kubernetes的最小工作单元,每个Pod包含一个或多个容器。
|
||||
- kube-proxy:负责为容器(Pod)创建网络代理/负载平衡服务,从API Server获取所有Server信息,并根据Server信息创建代理服务,这种代理服务称之为Service。Kube-proxy主要负责管理Service的访问入口,即实现集群内的Pod客户端访问Service,或者是集群外访问Service,具有相同服务的一组Pod可抽象为一个Service。每个Service都有一个虚拟IP地址(VIP)和端口号供客户端访问。
|
||||
|
||||
Kubernetes架构示意图如下所示:
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/d2/40/d284550c48fc6bf258a15f36b289a940.png" alt="">
|
||||
|
||||
>
|
||||
备注:此图引自[https://www.padok.fr/en/blog/kubernetes-architecture-clusters](https://www.padok.fr/en/blog/kubernetes-architecture-clusters)
|
||||
|
||||
|
||||
图中, Kube DNS负责为整个集群提供DNS服务;CNI是Container Network Interface的一个标准的通用接口,用于连接容器管理系统和网络插件。
|
||||
|
||||
与Borg不同的是,Kubernetes主要是一个容器编排引擎,不仅支持Docker,还支持Rocket(另一种容器技术)。
|
||||
|
||||
Kubernetes也已经被很多公司采用,比如网易云、华为在需要使用容器进行资源隔离以运行相关业务的场景下,采用了大规模 Kubernetes 集群。
|
||||
|
||||
在容器管理方面,Kubernetes有很多优势。
|
||||
|
||||
- **自动化容器的部署和复制。**Kubernetes执行容器编排,因此不必人工编写这些任务的脚本。
|
||||
- **将容器组织为组,弹性伸缩。**Kubernetes引入Pod机制,Pod代表着能够作为单一应用程序加以控制的一组容器集合。通过Pod机制,Kubernetes实现了多个容器的协作,能够有效避免将太多功能集中到单一容器镜像这样的错误实践中。此外,软件可以向外扩展跨越多个Pods实现初步部署,且相关部署可随时进行规模伸缩。
|
||||
- **容器间负载均衡。**Services用于将具备类似功能的多个Pod整合为一组,可轻松进行配置以实现其可发现性、可观察性、横向扩展以及负载均衡。
|
||||
- **易于版本控制与滚动更新。**Kubernetes采取“滚动方式”实现编排,且可跨越部署范围内的全部Pod。这些滚动更新可进行编排,并以预定义方式配合当前可能尚不可用的Pods数量,以及暂时存在的闲置Pods数量。Kubernetes利用新的应用程序镜像版本对已部署Pods进行更新,并在发现当前版本存在不稳定问题时回滚至早期部署版本。
|
||||
|
||||
### Mesos
|
||||
|
||||
理解了Google Borg和Kubernetes的集中式结构,接下来我们再看看Apache旗下的开源分布式资源管理框架Mesos吧。它被称为是分布式系统的内核,最初由加州大学伯克利分校的AMPLab开发,后在Twitter得到广泛使用。
|
||||
|
||||
Mesos的开发受到了Borg系统的启发,也是采用的典型的集中式架构。**Mesos与Borg不同之处在于**,Borg的Master直接对接用户应用,也就是说用户可以向Borg的Master直接请求任务。但Mesos不可以,Mesos只负责底层资源的管理和分配,并不涉及存储、 任务调度等功能,因此Mesos Master对接的是Spark、Hadoop、Marathon等框架,用户的任务需要提交到这些框架上。也正因为此,Mesos的任务调度框架是双层结构。
|
||||
|
||||
在Mesos中,一个集群包括Mesos Master和多个Mesos Agent。其中,Mesos Master运行在中央服务器,Mesos Agent运行在节点服务器上。
|
||||
|
||||
Mesos Master负责收集和管理所有Agent所在服务器的资源和状态,并且对接Spark、Hadoop等框架,将集群中服务器的资源信息告知给这些框架,以便这些框架进行任务资源匹配和调度。Mesos Agent负责任务的拉起、停止、重启等,并负责收集所在服务器的资源(比如CPU、内存等)信息和状态,上报给Mesos Master。
|
||||
|
||||
Mesos Master通常采用一主两备的方式,以方便故障处理和恢复。而Mesos Master的选主策略,采用的就是我们在第4篇文章“[分布式选举:国不可一日无君](https://time.geekbang.org/column/article/143329)”中介绍的ZAB算法。
|
||||
|
||||
Mesos架构示意图如下所示:
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/4d/e1/4d3e460d6d783f32efaf4362fa9934e1.png" alt="">
|
||||
|
||||
>
|
||||
备注:此图引自《[Mesos架构 · Mesos中文手册](https://mesos-cn.gitbooks.io/mesos-cn/content/OverView/Mesos-Architecture.html)》
|
||||
|
||||
|
||||
如上所述,Mesos对接的是框架,并且可以同时对接多个框架,目前已经被很多公司使用。比如,国外的Twitter、Apple、Airbnb、Uber等,国内的爱奇艺、去哪儿、携程、当当等。
|
||||
|
||||
这些公司选择Mesos,主要是因为它具有如下优势:
|
||||
|
||||
- **效率**。Mesos对物理资源进行了逻辑抽象,在应用层而不是物理层分配资源,通过容器而不是虚拟机(VM)分配任务。因为应用程序的调度器知道如何最有效地利用资源,所以在应用层分配资源能够为每个应用程序的特殊需求做考量; 而通过容器分配任务则能更好地进行“装箱”。
|
||||
- **可扩展性**。Mesos可扩展设计的关键是两级调度架构,其中Framework进行任务调度,Mesos Master进行资源分配。由于Master不必知道每种类型的应用程序背后复杂的调度逻辑,不必为每个任务做调度,因此可以用非常轻量级的代码实现,更易于扩展集群规模。
|
||||
- **模块化。**每接入一种新的框架,Master无需增加新的代码,并且Agent模块可以复用,为此开发者可以专注于应用和框架的选择。这,就使得Mesos可以支持多种框架,适应不同的应用场景。
|
||||
|
||||
随着分布式应用程序和微服务的流行,越来越多的用户正在寻找一种技术,来帮助他们管理这些复杂的应用程序。而Mesos为数据中心带来的这些好处,就使得越来越多的人关注Mesos及其相关项目。
|
||||
|
||||
## 分析对比
|
||||
|
||||
Borg、Kubernetes和Mesos采用的都是集中式结构,要理解它们的实现原理,就要清楚其架构。所以,虽然这部分内容理解起来有难度,但希望你可以深入进去探其本质,这样在实际操作中,就可以从用途出发选择合适的集群管理架构。
|
||||
|
||||
接下来,我将这3种集群管理系统的特点梳理为了一张表格,以方便你理解与记忆。
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/89/a3/89fbc9c74143c3b9e7cc50908ecb58a3.jpg" alt="">
|
||||
|
||||
## 知识扩展:Mesos是如何支持容器部署的?
|
||||
|
||||
目前,容器技术十分热门,解决了服务打包发布、资源隔离的问题。我们知道,Kubernetes的设计主要针对的就是容器,那么Mesos又是如何支持容器部署呢?
|
||||
|
||||
Mesos本身只负责资源管理,不负责任务调度。但Mesos可以对接不同的框架,Mesos+Marathon可以支持容器调度和部署。Marathon支持容器的调度,将容器部署请求发给Mesos Master,Mesos Master再将请求转发给Mesos Agent,Mesos Agent的执行器会将容器拉起。
|
||||
|
||||
目前,Mesos+Marathon支持的容器,主要包括Docker和cgroups。
|
||||
|
||||
## 总结
|
||||
|
||||
今天,我主要与你分享了分布式系统中的集中式架构,并以Borg、Kubernetes、Mesos这三款知名的集群管理系统为例,与你描述了集中式架构的设计目的、框架结构,以及各组件模块的功能等。
|
||||
|
||||
Borg是Google公司内部使用的集群管理系统,既可以执行长服务,也可以执行批处理任务,是一个具有强大功能的、复杂的集群管理系统。
|
||||
|
||||
Kubernetes是Borg的简化开源版,是一个正在兴起的集群管理系统。Mesos和Kubernetes都是为帮助应用程序在群集环境中运行而创建的,Kubernetes更加专注于运行容器群集,具有更多功能。
|
||||
|
||||
Mesos是非常典型的开源集群管理系统。在Mesos之上,可以搭载诸如Spark、Hadoop等框架,甚至可以在Mesos上集成Kubernetes,扩展性强。
|
||||
|
||||
可以发现,这三种集群管理系统虽然具有不同的功能组件,但整体框架采用的都是集中式架构。因此,你只要理解了一个集群管理系统的架构,再去理解其他集中式的集群管理架构就会很容易了。
|
||||
|
||||
Kubernetes由于其成熟的社区、丰富的文档,所以如果你是一个新手的话,Kubernetes就是一个很棒的开始。加油,赶紧开启你的集群管理之旅吧。
|
||||
|
||||
好了,到最后,我再以一个思维导图为你总结一下本讲的内容,以方便你理解记忆。
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/0b/da/0bdd970b5b66b60846856e7b77641fda.png" alt="">
|
||||
|
||||
## 思考题
|
||||
|
||||
在集中式架构中,Master如何判断Slave是否存活呢?
|
||||
|
||||
我是聂鹏程,感谢你的收听,欢迎你在评论区给我留言分享你的观点,也欢迎你把这篇文章分享给更多的朋友一起阅读。我们下期再会!
|
176
极客时间专栏/分布式技术原理与算法解析/第二站:分布式资源管理与负载调度/10 | 分布式体系结构之非集中式结构:众生平等.md
Normal file
176
极客时间专栏/分布式技术原理与算法解析/第二站:分布式资源管理与负载调度/10 | 分布式体系结构之非集中式结构:众生平等.md
Normal file
@@ -0,0 +1,176 @@
|
||||
<audio id="audio" title="10 | 分布式体系结构之非集中式结构:众生平等" controls="" preload="none"><source id="mp3" src="https://static001.geekbang.org/resource/audio/51/34/51a48a257093967e4473b7d24845f934.mp3"></audio>
|
||||
|
||||
你好,我是聂鹏程。今天,我来继续带你打卡分布式核心技术。
|
||||
|
||||
在上一篇文章中,我带你了解了分布式体系结构中的集中式结构。虽然很多云上的管理都采用了集中式结构,但是这种结构对中心服务器性能要求很高,而且存在单点瓶颈和单点故障问题。
|
||||
|
||||
为了解决这个问题,分布式领域中又出现了另一个经典的系统结构,即非集中式结构,也叫作分布式结构。那什么是非集中式结构呢,它的原理是什么样的,又有哪些集群采用了这种结构呢?
|
||||
|
||||
今天,我们就一起打卡非集中式结构,揭开它的神秘面纱吧。
|
||||
|
||||
## 什么是非集中式结构?
|
||||
|
||||
在非集中式结构中,服务的执行和数据的存储被分散到不同的服务器集群,服务器集群间通过消息传递进行通信和协调。
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/f6/5e/f6c9e7033f09f240e21877b11170675e.png" alt="">
|
||||
|
||||
也就是说,在非集中式结构中,没有中央服务器和节点服务器之分,所有的服务器地位都是平等(对等)的,也就是我们常说的“众生平等”。这样一来,相比于集中式结构,非集中式结构就降低了某一个或者某一簇计算机集群的压力,在解决了单点瓶颈和单点故障问题的同时,还提升了系统的并发度,比较适合大规模集群的管理。
|
||||
|
||||
所以近几年来,Google、 Amazon、Facebook、阿里巴巴、腾讯等互联网公司在一些业务中也相继采用了非集中式结构。
|
||||
|
||||
接下来,我将为你介绍3种典型的非集中式架构系统,包括Akka集群、Redis集群和Cassandra集群,来帮助你深入理解非集中式架构。
|
||||
|
||||
## Akka集群
|
||||
|
||||
在介绍Akka集群的结构之前,我带你了解一下什么是Akka框架吧。
|
||||
|
||||
Akka是一个开发库和运行环境,用于构建可扩展的、弹性的、快速响应的应用程序。Akka框架是基于Actor模型实现的,Actor模型是一个封装了状态和行为的对象,它接收消息并基于该消息执行计算。Actor之间通信的唯一机制就是消息传递,每个Actor都有自己的MailBox。
|
||||
|
||||
比如,在分布式系统中,一个服务器或一个节点可以视为一个Actor,Actor与Actor之间采用mail进行通信,如下图所示:
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/3d/11/3d64427315a82ef08f4dd0fc88d9a311.png" alt="">
|
||||
|
||||
可以看到,Actor发送的Mail消息会存储在接收方的MailBox中。默认情况下,接收方按照mail到达的先后顺序,从MailBox中提取mail消息,并进行相应的计算处理。
|
||||
|
||||
>
|
||||
备注:关于Actor模型更详细的内容,我会在第17篇文章中与你讲述。
|
||||
|
||||
|
||||
显然,Actor模型采用异步消息调用机制,具有非阻塞、高性能等特点,可以用于处理并发问题。Akka集群充分利用了Actor模型的优势,提供了一个非集中式架构的集群管理模块,用来构建可扩展的、弹性的分布式应用程序。
|
||||
|
||||
Akka集群负责Actor模型底层的节点管理,包括故障检测、节点加入/退出集群等。也就是说,Akka集群为Actor模型提供了一个可容错、去中心化的节点集群管理系统,来保证Actor的运行和Actor之间的通信。
|
||||
|
||||
如下图所示,Akka集群是一个完全去中心化的分布式集群管理系统。一个集群由多个节点组成,每个节点都可以进行数据处理和任务执行,节点之间均可进行通信。节点有Leader节点和非Leader节点之分。与非Leader节点相比,**Leader节点只是增加了负责节点的加入和移除集群的功能**,所以并不会影响非集中式结构中节点的平等关系。
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/83/bf/83cbb5e70a1d7dc628f4b0cf1ec658bf.png" alt="">
|
||||
|
||||
可以看到,Akka集群的两个重点是数据传输和集群组建及管理,所以接下来我将从这两个方面与你介绍Akka集群。
|
||||
|
||||
**首先,我们看一下数据传输。**在Akka集群中,节点是对等的,也就是说每个节点是可以并发处理的,因此必然存在数据传输和一致性的问题。
|
||||
|
||||
比如,我们要针对数据进行操作,将X=1修改为X=2。现在集群中节点1进行了修改使得X=2,但其他节点上还是X=1,因此节点1需要将X=2的消息告知其他节点,以保证最终集群中所有节点上均为X=2。
|
||||
|
||||
其实,这个问题就是分布式共识问题。我已经在第5篇文章“[分布式共识:存异求同](https://time.geekbang.org/column/article/144548)”中,与你介绍了PoW、PoS和DPoS三种达成共识的方法,你可以再复习下相关内容。
|
||||
|
||||
**Akka集群主要采用的是谁的时间戳最新(也就是数据最新),就以谁为准的原则。**在这里我要重点与你讲述的是,如何将X=2这个消息传输给集群中的每个节点。
|
||||
|
||||
Akka集群采用了**Gossip协议**,该协议是最终一致性协议。它的原理是每个节点周期性地从自己维护的集群节点列表中,随机选择k个节点,将自己存储的数据信息发给这k个节点,接收到该信息的节点采用前面讲的共识原则,对收到的数据和本地数据进行合并,这样迭代几个周期后,集群中所有节点上的数据信息就一致了。
|
||||
|
||||
这就好比我们生活中的“谣言传播”一样,用户A告诉用户B“商场新开了一家火锅店”,用户B收到信息后再告诉用户C,然后用户C再告诉用户D。这样,用户A、B、C、D最终都知道了这个消息。
|
||||
|
||||
**接下来,我们看一下集群组建及管理。**下图展示了Akka集群的创建过程。在创建集群时,节点被分为三种类型,即:
|
||||
|
||||
- 种子节点。使用静态配置文件方式或者系统运行时指定方式,可以生成种子节点;种子节点是普通节点加入集群的联系点,可以自动接收新加入集群的节点的信息。
|
||||
- 首种子节点。首种子节点是配置文件中的第一个种子节点,其功能是集群第一次启动时,首种子节点启动起来,集群才能组建成功,保证集群第一次创建时只有一个集群。如下图A节点,就是Akka集群的首种子节点。
|
||||
- 普通节点。可以向种子节点或集群中的任意节点发送Join消息,请求加入集群。如下图的B和C节点,通过向A节点发送Join消息,从而加入到Akka集群。
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/fe/c8/fed5d41f891df279f114ca4492c5e1c8.png" alt="">
|
||||
|
||||
>
|
||||
图片来源:[https://getakka.net/articles/clustering/cluster-overview.html](https://getakka.net/articles/clustering/cluster-overview.html)
|
||||
|
||||
|
||||
Akka集群的每个节点启动后,读取配置文件获取种子节点列表,然后开始组建集群:
|
||||
|
||||
- 如果本节点为首种子节点,则把自己加入到集群列表中,即以自己为中心构建集群;
|
||||
- 如果本节点为种子节点,则向首种子节点请求加入集群,当首种子节点回复同意消息后,可以加入集群,否则不可加入集群;
|
||||
- 如果本节点为普通节点,则可以向任一种子节点(包括首种子节点)请求加入集群,收到同意后,则加入集群,否则不可加入集群。
|
||||
|
||||
加入首种子节点或种子节点的节点信息,会通过Gossip协议的传播方式传播给当前已加入的所有节点,以完成集群组建。当集群组建完成后,就不存在种子节点与普通节点之分了,每个节点均可执行Actor应用程序。
|
||||
|
||||
Akka集群可以构建可扩展的、弹性的分布式应用程序,因此在JVM中应用了Akka框架,从而实现并发编程。目前,豌豆荚、蘑菇街等公司采用了Akka集群。
|
||||
|
||||
**到这里,我们小结一下吧**。Akka集群是一个完全去中心化的集群管理系统,当集群组建完成后,每个节点均可执行Actor应用程序,因此支持并发操作。但,这个并发操作引入了数据同步和一致性问题,所以Akka集群采用了Gossip协议进行数据同步,通过谁的时间戳最新就以谁为准,来解决一致性问题。
|
||||
|
||||
在实际业务场景中,除了面向应用程序平台的分布式集群管理之外,分布式数据存储也是一个非常重要的话题。在这其中,分布式数据存储中的集群管理便是一个关键因素。那么接下来,我就以开源数据库Redis的集群管理系统为例,与你展开介绍吧。
|
||||
|
||||
## Redis集群
|
||||
|
||||
Redis是一个开源的、包含多种数据结构的高性能Key-value数据库,主要有以下特征:
|
||||
|
||||
- 支持多种数据结构,包括字符串(String)、散列(Hash)、列表(List)、集合(Set)、有序集合(Sorted Set)等;
|
||||
- 支持数据的持久化和备份。数据可以保存在磁盘中,下次直接加载使用,且可以采用主从模式(Master/Slave)进行数据备份。
|
||||
- 基于内存运行,具有极高的性能。
|
||||
|
||||
Redis的这些特征均是为数据存储进行服务的,数据可分片存储在不同的Redis节点上,多个Redis节点间可共享数据,而提供这项能力的就是Redis 集群。
|
||||
|
||||
Redis 集群中不存在中央节点,是典型的去中心化结构,每个节点均可与其他节点通信。所有节点均可负责存储数据、记录集群的状态(包括键值到正确节点的映射),客户端可以访问或连接到任一节点上。Redis 集群的架构图,如下所示。
|
||||
|
||||
当然,节点之间的数据传输仍采用了Gossip协议,来保证集群中数据的最终一致性。
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/dd/71/ddfc9c0ba59b44f975e720544f173371.png" alt="">
|
||||
|
||||
Redis集群中的节点用于数据存储,所以**在设计时,需要考虑数据的可靠性和分片存储问题**。
|
||||
|
||||
对于可靠性的问题,集群中每个节点均存在主备,也就是说每台服务器上都运行两个Redis服务,分别为主备,主故障后,备升主。
|
||||
|
||||
而对于数据的分片存储问题,Redis集群引入了**“哈希槽”**的这一概念。Redis 集群内置了16384个哈希槽,每个节点负责一部分哈希槽。当客户端要存储一个数据或对象时,Redis先对key进行CRC16校验,然后进行16384取模,也即HASH_SLOT = CRC16(key) mod 16384,来决定哈希槽的编号,从而确定存储在哪个节点上。
|
||||
|
||||
比如,当前集群有3个节点,那么:
|
||||
|
||||
- 节点 A 包含 0 到 5500号哈希槽;
|
||||
- 节点 B 包含5501 到 11000 号哈希槽;
|
||||
- 节点 C 包含11001 到 16383号哈希槽。
|
||||
|
||||
Redis集群利用哈希槽实现了数据的分片存储,从而将Redis的写操作分摊到了多个节点上,提高了写并发能力。
|
||||
|
||||
**到这里,我们小结一下。**Redis集群是一个非集中式集群管理系统,没有中心节点,不会因为某个节点造成性能瓶颈,每个节点均支持数据存储,且采用分片存储方式,提高了写的并发能力。同时,每个节点的设计采用主备设计,提高了数据的可靠性。
|
||||
|
||||
鉴于这些优点,Redis已被Twitter、Uber、GitHub、Instagaram等公司采用。
|
||||
|
||||
除了Redis外,还有一个开源分布式key-value数据库系统Cassandra。接下来,我就再与你分享下Cassandra集群的设计,以加深你对非集中式架构的理解。
|
||||
|
||||
## Cassandra集群
|
||||
|
||||
与Redis类似,Cassandra也支持数据的分布式存储和操作。因此,Cassandra的集群架构与数据分片存储方案,与Redis集群类似。
|
||||
|
||||
如下图所示,Cassandra集群的系统架构是基于一致性哈希的完全P2P结构,没有Master的概念,所有节点都是同样的角色,彻底避免了因为单点问题导致的系统不稳定。Cassandra集群节点间的状态同步,也是通过Gossip协议来进行P2P通信的。
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/a6/c4/a6f5b593bb407d445213e2b2c102dfc4.png" alt="">
|
||||
|
||||
集群中的每个节点,都可以存储数据,并接收来自客户端的请求。**Cassandra集群数据存储与Redis的不同之处是,**Redis集群每个节点代表一部分哈希槽,一个哈希槽代表一个哈希值区间,而Cassandra集群中每个节点代表一个哈希值。
|
||||
|
||||
在Cassandra集群中,每次客户端可以向集群中的任意一个节点请求数据,接收到请求的节点将key值进行哈希操作,找出在一致性哈希环上是哪些节点应该存储这个数据,然后将请求转发到相应节点上,并将查询结果反馈返回给客户端。
|
||||
|
||||
目前,Cassandra集群因为完全去中心化的结构模式,已经被Hulu、Apple、Comcast、Instagram、Spotify、eBay、Netflix等公司使用。
|
||||
|
||||
**到这里,我们小结一下吧。**Cassandra采用去中心化的架构,解决了集中式结构的单点故障问题,同时因为数据基于哈希值分区存储,提高了读写数据的并发能力。在Cassandra集群中,没有Master的概念,每个节点代表一个哈希值,通过哈希映射的方式决定数据存储的位置。集群间的状态同步通过Gossip协议来进行P2P的通信。
|
||||
|
||||
## 对比分析
|
||||
|
||||
好了,以上就是Akka集群、Redis集群和Cassandra集群的主要内容了。为了便于理解与记忆,我将这3个集群的主要特征梳理为了一张表格,如下所示:<br>
|
||||
<img src="https://static001.geekbang.org/resource/image/be/88/be7d5a5b7e435f34fc60b5bf9a54a988.jpg" alt="">
|
||||
|
||||
## 知识扩展:如何优化Gossip协议中的重复消息问题?
|
||||
|
||||
非集中式结构的通信协议采用了Gossip协议。而Gossip是一种谣言传播协议,每个节点周期性地从节点列表中选择k个节点,将本节点存储的信息传播出去,直到所有节点信息一致,即算法收敛了。
|
||||
|
||||
这里有个问题,如果每次都是随机选择k个节点的话,势必会存在重复选择同样节点的可能,增加消息量。你觉得这个问题是否可以优化,又应该如何优化呢?
|
||||
|
||||
首先,这个问题肯定是可以优化的。解决方案是,每个节点记录当前传输的消息且还未达到收敛的时候,已经发送给了哪些节点,然后每次选择时从没有发送过的节点列表中随机选择k个节点,直到所有节点均被传输或集群收敛为止。这样,一方面减少了重复消息量,另一方面加快了收敛速度。
|
||||
|
||||
## 总结
|
||||
|
||||
集中式结构虽然易于理解,但容易出现单点瓶颈和单点故障等问题,而非集中结构才是超大规模分布式系统的首选结构。所以今天,我以Akka集群、Redis集群和Cassandra集群的结构为例,与你详细介绍了非集中式架构。
|
||||
|
||||
Akka集群是一个完全去中心化的集群管理系统,节点之间都是P2P的连接模式,通过Gossip协议来进行通信,节点之间有角色划分,负责数据存储的节点会进行存储数据。
|
||||
|
||||
Redis集群也是P2P的网状连接模式,但是基于key-value的数据库模型,每个节点都可以执行数据的计算和存储。此外,Redis集群引入了哈希槽的概念,来解决数据的分片存储问题。
|
||||
|
||||
Cassandra集群的结构是一致性哈希的P2P,节点会构成一个环结构,通过哈希映射来选择对应的节点。
|
||||
|
||||
好了,到最后,我再以一个思维导图为你总结一下这三个集群核心知识点,以方便你理解与记忆。
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/00/67/00b66db06581dd66e5f33f8100d78f67.png" alt="">
|
||||
|
||||
虽然这三种集群的节点组织结构各有不同,但节点之间都是通过Gossip协议来传递信息的。因此,在实现过程中,集群的消息传输、节点的功能等,在不同的分布式系统中都是类似的,而难点主要在于集群结构的设计。
|
||||
|
||||
由于Akka集群、Redis集群和Cassandra集群都是典型的非集中式集群组织结构,目前应用已经非常广泛了,所以有很多的实现案例可供你借鉴了。对于具体集群使用配置可参考相应的官网手册,讲得比较全和细。
|
||||
|
||||
相信你通过对今天的学习,很快就可以上手非集中式架构的集群管理应用和实践了。加油,挑战一下自己吧!
|
||||
|
||||
## 思考题
|
||||
|
||||
边缘计算中边缘设备的管理,你认为适合非集中式结构还是集中式结构呢,原因又是什么呢?
|
||||
|
||||
我是聂鹏程,感谢你的收听,欢迎你在评论区给我留言分享你的观点,也欢迎你把这篇文章分享给更多的朋友一起阅读。我们下期再会!
|
@@ -0,0 +1,148 @@
|
||||
<audio id="audio" title="11 | 分布式调度架构之单体调度:物质文明、精神文明一手抓" controls="" preload="none"><source id="mp3" src="https://static001.geekbang.org/resource/audio/07/cc/07f5b6488c527c35a8096d2e63ca44cc.mp3"></audio>
|
||||
|
||||
你好,我是聂鹏程。今天,我来继续带你打卡分布式核心技术。
|
||||
|
||||
在前两篇文章中,我和你分析了云资源管理的集中式架构和非集中式架构。可以看出,分布式系统架构的目的是,将多个服务器资源管理起来,寻找合适的服务器去执行用户任务。
|
||||
|
||||
那,什么是合适的服务器呢?衡量一个服务器是否合适会涉及很多条件或约束,比如在一些场景下,任务存在优先级,那当我们需要执行多个任务的时候,通常需要满足优先级高的任务优先执行的条件。但在这些条件中,服务器资源能够满足用户任务对资源的诉求是必须的。
|
||||
|
||||
而为用户任务寻找合适的服务器这个过程,在分布式领域中叫作**调度**。在分布式系统架构中,调度器就是一个非常重要的组件。它通常会提供多种调度策略,负责完成具体的调度工作。
|
||||
|
||||
当然,不同的分布式架构的调度器原理也不一样,最常见或最直观的是单体调度,就是任务和分布式系统中的空闲资源直接进行匹配调度。也就是说,调度器同时管理任务和资源,如果把资源比作“物质文明”,把任务比作“精神文明”,那么单体调度就是“物质文明和精神文明一手抓”。
|
||||
|
||||
接下来,我带你一起打卡分布式调度架构之单体调度。
|
||||
|
||||
首先,让我们先了解一下什么是单体调度。
|
||||
|
||||
## 什么是单体调度?
|
||||
|
||||
分布式系统中的单体调度是指,一个集群中只有一个节点运行调度进程,该节点对集群中的其他节点具有访问权限,可以对其他节点的资源信息、节点状态等进行统一管理,同时根据用户下发的任务对资源的需求,在调度器中进行任务与资源匹配,然后根据匹配结果将任务指派给合适的节点。
|
||||
|
||||
**单体调度器拥有全局资源视图和全局任务,可以很容易地实现对任务的约束并实施全局性的调度策略**。目前很多集群管理系统采用了单体调度设计,比如我们[第9讲](https://time.geekbang.org/column/article/148187)中提到的Google Borg、Kubernetes等。
|
||||
|
||||
如下图所示,图中展示了一个典型的单体调度框架。Master节点上运行了调度进程(负责资源管理、Tasks和资源匹配);Node 1,Node 2,....., Node N对应着我们在第9篇文章中讲的Master/Slave架构中的Slave节点。
|
||||
|
||||
在单体调度框架中,多个Node节点会将本节点的State(例如,资源信息等)上报给Master节点。Master节点将Nodes State信息记录在Cluster State模块,Cluster State模块用于管理集群中节点的资源等状态。Master节点中的Scheduling Logic模块用于进行Tasks与节点资源的匹配。当Master需要下发任务时,Cluster State模块会将节点的资源状态传送给Scheduling Logic模块,以便Scheduling Logic模块进行Tasks与资源匹配,并根据匹配结果将Task发送给匹配到的节点。
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/e9/59/e95c887c8aa9b8837a6845f027f9fb59.png" alt="">
|
||||
|
||||
## 单体调度设计
|
||||
|
||||
下图展示了单体调度的基本模型,具有全局系统视角的单体调度器是“Scheduler”模块,“Scheduler”模块包含多个子模块,包括记录全局资源信息的“Resource State”模块、负责任务调度的“Task Scheduling”模块等。“Scheduler”主要的工作就是基于每个节点的资源信息,根据制定的资源-任务匹配规则,从而将任务下发给对应的节点。
|
||||
|
||||
每个节点都有本地的资源管理模块“Resource Manager”,上报节点资源信息,并接收来自中央调度器下发的任务。
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/50/8a/507b44264047c783153998e91005998a.png" alt="">
|
||||
|
||||
在Borg和Kubernetes这两个典型的集中式集群管理系统中,Scheduler是它们的核心。而Kubernetes又是Borg的开源版本。所以接下来,我就以Borg为例,与你讲述它的调度器该如何设计,才能保证在大规模集群上,运行来自不同应用的成千上万的作业。
|
||||
|
||||
## Borg调度设计
|
||||
|
||||
调度的初衷是为作业或任务寻找合适的计算资源,也就是说作业或任务是调度的对象。那么“作业”和“任务”到底是什么呢?下面,我带你先了解一下作业和任务的概念以及关系。
|
||||
|
||||
**我们先来看看作业和任务的定义分别是什么吧。**
|
||||
|
||||
一个Borg 作业(job)的属性通常包括作业名称、作业产生者和作业包含的任务数量。作业可以有一些约束来限制作业中的任务(task)运行在指定的机器上,比如机器ID、任务所需数据所在机器等属性。这些约束可以是刚性的也可以是柔性的,其中柔性约束表示偏好,而非必须。需要注意的是,一个作业只能在一个集群中运行。
|
||||
|
||||
而一个任务对应的是一组Linux进程,运行在一台机器上的一个容器内或直接运行在节点上。任务也有一些属性,比如资源需求量、在作业中的序号等。
|
||||
|
||||
那么,**作业和任务是什么关系呢?**
|
||||
|
||||
概括来说,一个作业可以包含多个任务。作业类似于用户在一次事务处理或计算过程中要求计算机所做工作的总和,而任务就是一项项具体的工作。
|
||||
|
||||
一个作业中的任务大多有相同的属性,比如,CPU核、内存、硬盘空间、硬盘访问速度、TCP端口等。在任务运行时, 这些相同的属性,可以被覆盖 ,比如特定任务的命令行参数、各维度的资源等。
|
||||
|
||||
多个任务可以在多台机器上同时执行,从而加快作业的完成速度,提高系统的并行程度。而具体将哪个任务分配给哪个机器去完成,就是调度器要做的事儿了。
|
||||
|
||||
接下来,我就与你讲述下Borg的调度器——Scheduler组件,来帮助你理解Borg内部的任务调度流程,以加深你对单体调度的理解。其实,很多框架比如Hadoop、Spark等都是采用了单体调度设计,它们整体的思想类似,所以我希望通过对Borg调度的讲解,能够帮助你理解你所在业务中的调度逻辑。
|
||||
|
||||
我们先来回忆下Borg的系统架构图吧。
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/22/1c/228b7925b0f131dfd5e21fd2538e7a1c.png" alt=""><br>
|
||||
Scheduler负责任务的调度,当用户提交一个作业给BorgMaster后,BorgMaster会把该作业保存到Paxos仓库中,并将这个作业的所有任务加入等待队列中。调度器扫描任务等待队列,根据预定义的调度算法,将队列中的任务分配到满足作业需求且有足够资源的计算节点上(也即上上图所示的Borglet节点)。
|
||||
|
||||
这里我要再强调一下,**调度是以任务为单位的,而不是以作业为单位**。调度器在扫描队列时,按照任务的优先级顺序,从高到低进行选择;且高优先级未被分配的任务可以抢占低优先级已被分配的任务。同优先级的任务则以轮询的方式处理。这样的任务分配次序,可以保证任务的公平,并避免队首的大型任务阻塞队列。
|
||||
|
||||
接下来,我们再看看调度器的核心部分,也就是调度算法吧。
|
||||
|
||||
## Borg调度算法
|
||||
|
||||
Borg调度算法的核心思想是“筛选可行,评分取优”,具体包括两个阶段:
|
||||
|
||||
1. **可行性检查**,找到一组可以运行任务的机器(即上图中的Borglet);
|
||||
1. **评分**,从可行的机器中选择一个合适的机器(即上图中的Borglet)。
|
||||
|
||||
**首先,我们看一下可行性检查阶段的具体规则**。
|
||||
|
||||
在可行性检查阶段,调度器会找到一组满足任务需求,且有足够可用资源(包括空闲资源,和已经分配给低优先级任务但可以抢占的资源)的机器。
|
||||
|
||||
假设系统中有6个可以执行任务的机器,依次标记为节点1~6。现在有一个任务A,只能部署在节点1、节点3或节点5中,并且任务A的资源需求为0.5个CPU,200MB内存。根据任务A的约束条件,可以先从所有节点中筛选出节点1、节点3和节点5,然后根据任务A的资源需求,再从这3个节点中寻找满足任务资源需求的节点。
|
||||
|
||||
**然后,我们看看评分阶段。**
|
||||
|
||||
在评分阶段,调度器确定每台可行机器的适宜性。Borg的评分机制有很多种,但是不同的评分机制,都是针对可行性检查阶段中筛选出的机器,从这些可行的机器中根据评分机制进行打分,从而选出最适合调度的一台机器。
|
||||
|
||||
在评分过程中,我们可以根据不同的场景制定不同的评价指标,比如最小化被抢占的任务数、尽量选择已经执行了相同任务的机器、目标任务是否需要跨域部署、是否把不同优先级任务进行混合部署等。 根据不同的考虑因素,可以定制不同的评分算法。
|
||||
|
||||
**其中,常见的评分算法,包括“最差匹配”和“最佳匹配”两种。**
|
||||
|
||||
Borg早期使用修改过的E-PVM算法来评分,该算法的核心是将任务尽量分散到不同的机器上,以并行的方式提高任务执行的速度。该算法的问题在于,它会导致每个机器都有少量的无法使用的剩余资源,称为“碎片资源”,因此有时称其为“**最差匹配**”(worst fit)。
|
||||
|
||||
比如,现在有两个机器,机器A的空闲资源为1个CPU和1G内存、机器B的空闲资源为0.8个CPU和1.2G内存;同时有两个任务,Task1的资源需求为0.4个CPU和0.3G内存、Task2的资源需求为0.3CPU和0.5G内存。
|
||||
|
||||
按照最差匹配算法思想,Task1和Task2会分别分配到机器A和机器B上,导致机器A和机器B都存在一些资源碎片,如果此时来了一个Task3,其任务需求为0.8个CPU和0.8G内存,则Task3就可能无法立即分配到机器上,需要等到Task1或Task2执行完才能被分配执行。
|
||||
|
||||
与“**最差匹配**”相反的是“**最佳匹配**”(best fit),即把同一个机器上的任务塞得越满越好。这样就可以“空”出一些“空闲”的机器(它们仍运行存储服务),用于部署计算资源需求大的任务。
|
||||
|
||||
比如,在上面的例子中,按照最佳匹配算法的思想,Task1和Task2会被一起部署到机器A或机器B上,这样未被部署的机器就可以用于执行Task3这样的大型任务了。
|
||||
|
||||
但是,如果用户或Borg错误估计了资源需求,紧凑的“**最佳匹配**”操作会对性能造成巨大的影响。比如,用户估计它的任务A需要0.5个CPU和1G内存,运行该任务的服务器上由于部署了其他任务,现在还剩0.2个CPU和1.5G内存,但用户的任务A突发峰值时(比如电商抢购),需要1个CPU和3G内存,很明显,初始资源估计错误,此时服务器资源不满足峰值需求,导致任务A不能正常运行。
|
||||
|
||||
所以说,“**最佳匹配**”策略不利于有突发负载的应用,而且对申请少量计算资源的批处理作业也不友好。因为这些作业申请少量计算资源批处理作业,可以分配到多个机器上,从而使用机器上的碎片化资源,使得任务可以更快速地被调度执行。
|
||||
|
||||
但是,“**最佳匹配**”策略中,批处理作业往往会集中分配在一起,因此占用的并非是碎片化资源。此外,“**最佳匹配**”这种策略有点类似“把所有鸡蛋放到一个篮子里面”,当这台服务器故障后,运行在这台服务器上的任务都会故障,对业务会造成很大的影响。
|
||||
|
||||
“**最差匹配**”和“**最佳匹配**”,这两个评分算法各有利弊。**在实践过程中,我们往往会根据实际情况来选择更合适的评分算法。**比如,对于资源比较紧缺,且业务流量比较规律,基本不会出现突发情况的场景,可以选择最佳匹配算法;如果资源比较丰富,且业务流量会经常出现突发情况的场景,可以选择最差匹配算法。
|
||||
|
||||
Borg的任务部署机制是支持优先级高的任务抢占低优先级任务的,如果评分算法选中的机器上没有足够的资源来运行新任务,Borg会抢占该机器上已经部署的低优先级任务的资源,从最低优先级的任务开始,逐级向上抢占任务资源,直到可用资源足够运行新任务。其中,被抢占的任务放回到调度器的等待队列里。
|
||||
|
||||
当然有很多调度框架是支持用户根据自己的场景自定义调度策略的,比如优先级策略、亲和性策略、反亲和性策略等。
|
||||
|
||||
## 知识扩展:多个集群/数据中心如何实现单体调度呢?
|
||||
|
||||
今天这篇文章中,我与你讲述的单体调度,其实是针对一个集群或一个数据中心的,那么多个集群或多个数据中心,能不能基于单体调度实现呢?
|
||||
|
||||
答案是肯定的,这就是**集群联邦**的概念了。
|
||||
|
||||
所谓集群联邦,就是将多个集群联合起来工作,核心思想是增加一个控制中心,由它提供统一对外接口,多个集群的Master向这个控制中心进行注册,控制中心会管理所有注册集群的状态和资源信息,控制中心接收到任务后会根据任务和集群信息进行调度匹配,选择到合适的集群后,将任务发送给相应的集群去执行。
|
||||
|
||||
集群联邦的概念,其实就是单体调度的分层实现。如果你对集群联邦感兴趣的话,推荐你看一下Kubernetes的集群联邦设计和工作原理。
|
||||
|
||||
## 总结
|
||||
|
||||
今天,我以Borg为例,与你讲述了单体调度架构的设计及调度算法。
|
||||
|
||||
单体调度是指一个集群中只有一个节点运行调度进程,该调度进程负责集群资源管理和任务调度,也就是说单体调度器拥有全局资源视图和全局任务。
|
||||
|
||||
单体调度的特征,可以总结为以下四点:
|
||||
|
||||
- 单体调度器可以很容易实现对作业的约束并实施全局性的调度策略,因此适合作为批处理任务和吞吐量较大、运行时间较长的任务。
|
||||
- 单体调度系统的状态同步比较容易且稳定,这是因为资源使用和任务执行的状态被统一管理,降低了状态同步和并发控制的难度。
|
||||
- 调度算法只能全部内置在核心调度器当中,因此调度框架的灵活性和策略的可扩展性不高。
|
||||
- 单体调度存在单点故障的可能性。
|
||||
|
||||
现在,我再用一个思维导图为你总结一下今天的主要内容,以方便你理解记忆。
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/04/05/0441c5efbdc8cf188818be0197be9505.png" alt="">
|
||||
|
||||
单体调度器虽然具有单点瓶颈或单点故障问题,但因为其具有全局资源视图和全局任务,简单易维护,被很多公司广泛采用,比如Google、阿里、腾讯等公司。另外,我们今天介绍的Borg集群管理系统,以及其开源版Kubernetes集群管理系统,使用的都是单体调度结构。
|
||||
|
||||
单体调度结构虽然结构单一,但是其调度算法可以扩展甚至自定义,也就是说你可以根据业务特征,自定义调度策略,比如优先级策略、亲和性策略等。
|
||||
|
||||
学完了关于单体调度的知识后,赶紧上手试试,定制一个独特的调度算法或设计一个特定的单体调度器吧。如果你在这个过程中遇到了什么问题,就留言给我吧。
|
||||
|
||||
## 思考题
|
||||
|
||||
你能和我分享下,Google Borg是采用什么技术实现的资源隔离吗?
|
||||
|
||||
我是聂鹏程,感谢你的收听,欢迎你在评论区给我留言分享你的观点,也欢迎你把这篇文章分享给更多的朋友一起阅读。我们下期再会!
|
@@ -0,0 +1,167 @@
|
||||
<audio id="audio" title="12 | 分布式调度架构之两层调度:物质文明、精神文明两手抓" controls="" preload="none"><source id="mp3" src="https://static001.geekbang.org/resource/audio/d8/54/d80be89e2ae5bf38e663280d40c93554.mp3"></audio>
|
||||
|
||||
你好,我是聂鹏程。今天,我来继续带你打卡分布式核心技术。
|
||||
|
||||
我在上一篇文章中,与你分享了单体调度。单体调度的核心是,所有节点的资源以及用户的任务均由中央服务器统一管理和调度。因此,中央服务器很容易成为单点瓶颈,会直接导致其支持的调度规模和服务类型受限。
|
||||
|
||||
于是两层调度就出现了。那么,到底什么是两层调度呢,它是如何设计的,又有哪些调度算法呢?接下来,就和我一起打卡分布式调度架构的两层调度,去探寻这些问题的答案吧。
|
||||
|
||||
## 什么是两层调度?
|
||||
|
||||
在单体调度架构中,中央服务器的单点瓶颈问题,会限制调度的效率和支持的任务类型。中央服务器的性能会限制调度的效率,很好理解,但为什么会限制支持的任务类型呢?
|
||||
|
||||
简单地说,这是因为不同的服务具有不同的特征,对调度框架和计算的要求都不一样。比如说,你的业务最开始时只有批处理任务,后来发展到同时还包括流数据任务,但批处理任务是处理静态数据,流数据任务却是处理实时数据。显然,单体调度框架会随着任务类型增加而变得越来越复杂,最终出现扩展瓶颈。
|
||||
|
||||
那么,为了提升调度效率并支持多种类型的任务,最直接的一个想法就是,能不能把资源和任务分开调度,也就是说一层调度器只负责资源管理和分配,另外一层调度器负责任务与资源的匹配呢。
|
||||
|
||||
很显然,这个解决方案是可以的。这种调度架构,就是我们通常所说的**两层调度**。如果我们还是把资源比作物质文明、把任务比作精神文明的话,两层调度就可以理解为“物质文明与精神文明两手抓”。
|
||||
|
||||
两层调度结构对应的就是两层调度器,资源的使用状态同时由中央调度器和第二层调度器管理,中央调度器从整体上进行资源的管理与分配,将资源分配到第二层调度器;再由第二层调度器负责将资源与具体的任务配对,因此第二层调度可以有多个调度器,以支持不同的任务类型。
|
||||
|
||||
如下图所示,Scheduler-1表示第一层调度,负责收集和管理集群中的资源信息;Scheduler-2 表示第二层调度,Scheduler-1会将集群资源发送给Scheduler-2,然后Scheduler-2根据任务的资源需求和Scheduler-1发送的资源信息进行任务匹配和调度。
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/8e/a2/8e7371cc970c85c23fc8f2e70f4fa3a2.png" alt="">
|
||||
|
||||
两层调度器中的第一层调度器仍是一个经简化的中央调度器,通常放在分布式集群管理系统中,而第二层调度则是由各个应用程序框架完成。两层调度器的职责分别是:第一层调度器负责管理资源并向框架分配资源,第二层调度器接收分布式集群管理系统中第一层调度器分配的资源,然后根据任务和接收到的资源进行匹配。
|
||||
|
||||
采用两层调度结构的集群管理系统有很多,典型代表是 Apache Mesos 和 Hadoop YARN。我在[第9篇文章](https://time.geekbang.org/column/article/148187)中讲述Mesos的体系结构时,和你分析了它采用的是典型的两层调度。那么今天,我就继续以Mesos为例,带你学习两层调度的架构设计和对应的分配算法吧。
|
||||
|
||||
## 两层调度设计
|
||||
|
||||
由于Mesos只负责底层资源的管理和分配,并不涉及存储、 任务调度等功能,因此Mesos要实现类似Borg那样的资源与任务管理,还需要上层框架的配合。
|
||||
|
||||
具体到两层调度架构上,Mesos本身实现的调度器为第一层调度,负责资源管理,然后将第二层任务调度交给了框架完成。接下来,我们就具体看看吧。
|
||||
|
||||
### 两层调度架构
|
||||
|
||||
以Mesos为基础的分布式资源管理与调度框架包括两部分,即Mesos资源管理集群和框架。
|
||||
|
||||
- 资源管理集群是由一个Master节点和多个Slave节点组成的集中式系统。每个集群有且仅有一个Master节点,负责管理Slave节点,并对接上层框架;Slave节点向Master节点周期汇报资源状态信息,并执行框架提交的任务。
|
||||
- 框架(Framework)运行在Mesos上,是负责应用管理与调度的“组件”,比如Hadoop、Spark、MPI和Marathon等,不同的框架用于完成不同的任务,比如批处理任务、实时分析任务等。框架主要由调度器(Scheduler)和执行器(Executor)组成,调度器可以从Master节点获取集群节点的信息 ,执行器在Slave节点上执行任务。
|
||||
|
||||
从上述的架构描述可以看出,Mesos是一个典型的双层调度框架。Mesos Master上有一个调度器(也就是Allocation Module),负责管理并分配集群中的所有资源,是第一层调度。框架上负责任务的管理与调度的调度器,是第二层调度,如下图所示。
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/0b/a4/0bb9bb6a32b73b094a8fb61bca5c6ca4.png" alt="">
|
||||
|
||||
接下来,我们再看看Mesos两层调度的基本原理吧。
|
||||
|
||||
- 框架向Mesos Master注册;
|
||||
- Mesos Slave节点定期或周期向Mesos Master上报本节点的空闲资源;
|
||||
- Mesos Master的Scheduler进程收集所有节点的空闲资源信息,并以Resource Offer的方式将空闲资源发送给注册的框架;
|
||||
- 框架的Scheduler接收到Mesos发送的资源后,进行任务调度与匹配,匹配成功后,将匹配结果下发给Mesos Master,并由Mesos Master转发给相应节点的执行器执行任务。
|
||||
|
||||
可以看出,Mesos实现双层调度时,采用Resource Offer机制衔接了第一层和第二层调度。**Resource Offer机制**指的是,Mesos Master主动将节点空闲资源,以类似发放(Offer)的方式发给每个框架,如果框架需要则使用,不需要则还回。
|
||||
|
||||
也就是说,通过Resource Offer机制,第一层调度将资源主动告知第二层调度,然后第二层调度进行具体的任务匹配,从而实现了任务调度与资源管理的分离,Mesos Master通过资源分配算法决定给各个Framework提供多少资源,而Framework则决定接受哪些资源,以及哪些任务使用这些资源运行。这样一来,一个两层调度架构就实现了。
|
||||
|
||||
在Mesos的两层调度中,Framework第二层调度器中的任务与资源匹配的调度策略很常见,也有很多文章做了比较深入的分析了,所以如果你想要深入研究的话,可以参考下Hadoop、Spark等的调度策略,这里我就不多说了。
|
||||
|
||||
接下来,我们重点看下Mesos第一层调度算法,理解其如何为框架分配资源,以支持多用户多框架。
|
||||
|
||||
### 资源分配算法
|
||||
|
||||
Mesos的资源分配算法解决的问题是,决策需要将当前可用资源分配给哪些框架以及分配多少。接下来,我将重点与你介绍两种主要的资源分配算法,即:最大最小公平算法(Max-min Fairness,MMF)和主导资源公平算法(Dominant Resource Fairness,DRF)。
|
||||
|
||||
**首先,我们看看最大最小公平算法。**这是一种在兼顾公平的前提下,尽可能让更多人满意的资源分配算法。为什么这么说呢?因为这个算法有3个主要原则:
|
||||
|
||||
- 按照用户对资源需求量递增的顺序进行空闲资源分配;
|
||||
- 不存在用户得到的资源超过自己需求的情况;
|
||||
- 对于分配的资源不满足需求的用户,所获得的资源是相等的。
|
||||
|
||||
在执行资源分配时,最大最小公平算法按照上述3条原则进行多次迭代,每次迭代中资源均平均分配,如果还有剩余资源,就进入下一次迭代,一直到所有用户资源得到满足或集群资源分配完毕,迭代结束。
|
||||
|
||||
接下来,我们通过一个具体的例子来看看最大最小公平算法的资源分配流程吧。
|
||||
|
||||
假设,现在有总量为100的空闲资源,有4个用户A、B、C、D对该资源的需求量分别为(35,10,25,45),分配流程如下所示:
|
||||
|
||||
1. 按照用户对资源的需求量升序排列,则4个用户的需求量为(B:10,C:25,A:35,D:45)。
|
||||
1. 平均分配空闲资源。资源空闲总量100,除以用户数4,则平均空闲资源量为25;按照第一步中需求量分配后,用户资源需求量为(0,0,10,20),且用户B由于资源需求量小于25,因此会剩余资源。此时空闲资源量为15,资源需求人数为2。
|
||||
1. 重复第二步,平均分配资源,15/2=7.5,即分别为用户A和D分配7.5份资源,此时用户资源需求量为(0,0,2.5,12.5),空闲资源量为0,资源需求人数为2。
|
||||
1. 所有资源已分配完,算法终止。
|
||||
|
||||
最大最小公平算法的执行流程,如下图所示。
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/73/a4/737a82ebe2ca35f29b2bf8a5c06c4ea4.png" alt="">
|
||||
|
||||
在这个案例中,最大最小公平算法是由于所有资源全部分配完才终止的。至此,对于需求量为(10,25,35,45)的用户们来说,分配到的资源是(10,25,32.5,32.5)。这个算法的另外一个结束条件是,资源分配满足了所有用户的资源需求,即当没有用户有资源需求时,算法也会终止。
|
||||
|
||||
**接下来,我们再看看主导资源公平算法。**
|
||||
|
||||
最大最小公平算法采用了绝对公平的方式分配资源,会导致大量的资源浪费,比如用户需求量为35和45的用户A和用户D,均分配了32.5的空闲资源,但由于资源不满足需求,这两个用户均无法使用。
|
||||
|
||||
而主导资源公平算法在考虑用户公平性的前提下,还考虑了用户对不同资源类型的需求,以尽可能地合理分配资源。也就是说,同样的资源量,主导资源公平算法可以尽可能地满足更多的用户。
|
||||
|
||||
在Mesos中,框架对资源的需求往往包括对CPU、内存等多种类型资源的需求。针对多种资源的需求,主导资源公平算法首先计算已经分配给用户的每一种资源的占用率(Resource Share),比如已经分配的CPU占总资源量的多少,已经分配的内存占总资源量的多少。所有资源占用率中的最大值称作该**用户的主导资源占用率**,而主导资源占用率对应的资源就是用户的主导资源。
|
||||
|
||||
我们通过一个具体的案例,看看如何判断用户的主导资源吧。如下图所示,假设系统中的资源共包括18个 CPU 和36 GB 内存,有两个Framework(Framework A和Framework B)分别运行了两种任务,假设Framework A运行内存密集型任务,Framework B运行CPU密集型任务,且每个任务所需要的资源量是一致的,分别是<2 CPU, 8 GB> 和 <6 CPU, 2 GB>。
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/31/58/315826c3aff89714530b370e520edd58.png" alt="">
|
||||
|
||||
第一步:计算资源分配量。
|
||||
|
||||
假设x和y分别是Framework A和Framework B分配的任务数,那么Framework A消耗的资源为{2x CPU,8x GB},Framework B消耗的资源数为{6y CPU,2y GB},分配给两个Framework的总资源量为(2x+6y)个CPU和(8x+2y)GB内存。
|
||||
|
||||
第二步:确定主导资源。
|
||||
|
||||
对于Framework A来说,每个任务要消耗总CPU资源的2/18,总内存资源的8/36,所以Framework A的主导资源为内存;对于Framework B来说,每个任务要消耗总CPU资源的6/18和总内存资源的2/36,因而Framework B的主导资源为CPU。
|
||||
|
||||
第三步:DRF算法的核心是平衡所有用户的主导资源占用率,尽可能试图最大化所有用户中最小的主导资源占用率。通过求解下列公式,可以计算出Framework A和Framework B分配的任务数,并且要在满足公式的条件下,使得x和y越大越好。
|
||||
|
||||
```
|
||||
2x+6y≤18
|
||||
8x+2y≤36
|
||||
8x/36=6y/18
|
||||
|
||||
```
|
||||
|
||||
通过求解可以得出:x=3,即Framework A可以运行3个任务;y=2,即Framework B可以运行2个任务。这样分配的话,每个Framework获取了相同比例的主导资源,即:A获取了2/3的内存,B获取了2/3的CPU,从而在主导资源上体现了调度算法的公平性。
|
||||
|
||||
在实际任务分配过程中,主导资源率是根据已经分配给Framework的资源,占集群中总资源量的多少进行计算的,并且在每次分配过程中,会选择主导资源最小的Framework进行分配,也就是试图最大化所有用户中最小的主导资源占用率。
|
||||
|
||||
如果你想深入研究主导资源公平算法的话,可参考“[Dominant Resource Fairness: Fair Allocation of Multiple Resource Types](https://cs.stanford.edu/~matei/papers/2011/nsdi_drf.pdf)”这篇论文。
|
||||
|
||||
**现在,我来对比下这两种调度算法吧。**
|
||||
|
||||
最大最小公平算法适用于单一类型的资源分配场景,而主导资源公平算法适用于多种类型资源混合的场景。并且,最大最小公平算法从公平的角度出发,为每个用户分配不多于需求量的资源;而主导资源公平算法从任务出发,目的在于尽量充分利用资源使得能够执行的任务越多越好。
|
||||
|
||||
## 知识扩展:两层调度如何保证不同的业务不会互相干扰?
|
||||
|
||||
类似Mesos这样的两层调度机制,可以同时支持多个框架和多种类型的业务,那么如何保证这些业务运行时不会互相干扰呢?
|
||||
|
||||
首先,我们思考一下什么情况下会存在业务运行时相互干扰呢。答案就是,当多个业务运行在同一台机器上,共同使用CPU、内存,以及系统环境时会存在相互干扰。
|
||||
|
||||
要解决这个问题,我想你肯定会问,不同的业务能在独立的环境中运行吗?也就是说,隔离不同的业务资源和环境,应该就不会存在相互干扰了吧。不错,解决这个问题的办法就是资源隔离,就好比我们现在接触的虚拟机一样,在同样的服务器上安装多个虚拟机,不同的用户在不同的虚拟机上运行,这些用户互不干扰。在Mesos中,实现这种资源隔离的是**容器**。
|
||||
|
||||
容器的实质是进程,该进程运行于属于自己的独立的命名空间,可以拥有自己的 root 文件系统、自己的网络配置、自己的进程空间,甚至是自己的用户 ID 空间。Mesos支持的容器,包括Linux自带的cgroups和Docker。
|
||||
|
||||
所以说,Mesos正是用容器隔离开了不同的业务,使得它们运行时不会互相干扰。
|
||||
|
||||
## 总结
|
||||
|
||||
今天,我以Mesos为例,与你讲述了两层调度的架构设计和资源分配算法。我们一起总结下今天的核心内容吧。
|
||||
|
||||
两层调度是一种资源和任务分开调度的设计,也就是说一层调度器只负责资源的管理和分配,另外一层调度器负责任务与资源的匹配。
|
||||
|
||||
在Mesos中,第一层资源调度由Mesos提供,第二层任务调度由框架提供,Mesos将资源以Resource Offer的形式发放给框架调度器,框架调度器根据任务需求和得到的资源信息进行任务匹配调度,为此提高了调度的并发性。
|
||||
|
||||
而关于第一层的调度算法,通常有最大最小公平算法和主导资源公平算法等。
|
||||
|
||||
两层调度的一个问题是,由于第二层调度只能获得部分资源视图,因此无法实现全局最优调度。
|
||||
|
||||
最后,我将今天这篇文章的核心知识点梳理为了一张思维导图,以方便你理解与记忆。
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/0c/30/0cd6814755cfa921f5366c71cb224330.png" alt="">
|
||||
|
||||
两层调度提供了多租户多框架的支持,如果你的业务类型比较多或者面向的是不同的租户的话,建议你采用两层调度框架。
|
||||
|
||||
相信你通过这篇文章可以看到,在分布式领域中,同时支持多种框架、支持多种类型任务调度的调度机制,并没有那么神秘,只要你静下心来弄明白这篇文章的调度机制,以后遇到类似的调度机制,就可以做到心中有数了。
|
||||
|
||||
不得不说,Mesos的两层调度设计得非常巧妙,并且Mesos支持你自己写一个调度器注册到Mesos作为第二层调度。赶快动手实践一下吧,[Mesos的官网](http://mesos.apache.org/)提供了相应的案例,方便你入门,加油,相信你一定可以!
|
||||
|
||||
## 思考题
|
||||
|
||||
你觉得,Mesos双层调度机制容易导致资源碎片问题吗?原因又是什么呢?
|
||||
|
||||
我是聂鹏程,感谢你的收听,欢迎你在评论区给我留言分享你的观点,也欢迎你把这篇文章分享给更多的朋友一起阅读。我们下期再会!
|
||||
|
||||
|
@@ -0,0 +1,158 @@
|
||||
<audio id="audio" title="13 | 分布式调度架构之共享状态调度:物质文明、精神文明多手协商抓" controls="" preload="none"><source id="mp3" src="https://static001.geekbang.org/resource/audio/6e/d3/6e9617a11d6df758aa13656e435c86d3.mp3"></audio>
|
||||
|
||||
你好,我是聂鹏程。今天,我来继续带你打卡分布式核心技术。
|
||||
|
||||
在上一篇文章中,我们一起学习了两层调度。在两层调度架构中,第二层调度只知道集群中的部分资源,无法进行全局最优调度。那么,是否有办法解决全局最优调度的问题呢?
|
||||
|
||||
答案是肯定的,解决办法就是我今天要带你打卡的共享状态调度。
|
||||
|
||||
接下来,我们就一起看看共享状态调度到底是什么,以及它的架构和工作原理吧。
|
||||
|
||||
## 什么是共享状态调度?
|
||||
|
||||
通过我们前两篇文章的讲述,不难发现,集群中需要管理的对象主要包括两种:
|
||||
|
||||
- 一是,资源的分配和使用状态;
|
||||
- 二是,任务的调度和执行状态;
|
||||
|
||||
在单体调度中,这两种对象都是由单体调度器管理的,因此可以比较容易地保证全局状态的一致性,但问题是可扩展性较差(支持业务类型受限),且存在单点瓶颈问题。
|
||||
|
||||
而在两层调度中,这两种对象分别由第一层中央调度器和第二层Framework调度器管理,由于Framwork调度器只能看到部分资源,因此不能保证全局状态的一致性,也不容易实现全局最优的调度。
|
||||
|
||||
为了解决这些问题,一种新的调度器架构被设计了出来。这种架构基本上沿袭了单体调度器的模式,通过将单体调度器分解为多个调度器,每个调度器都有全局的资源状态信息,从而实现最优的任务调度,提供了更好的可扩展性。
|
||||
|
||||
也就是说,这种调度架构在支持多种任务类型的同时,还能拥有全局的资源状态信息。要做到这一点,这种调度架构的多个调度器需要共享集群状态,包括资源状态和任务状态等。因此,这种调度架构,我们称之为**共享状态调度器**。
|
||||
|
||||
如果我们继续把资源比作物质文明、把任务比作精神文明的话,相对于单体调度和两层调度来说,共享状态调度就是“物质文明与精神文明多手协商抓”。
|
||||
|
||||
共享状态调度架构的示意图,如下所示:
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/4e/5b/4ec7d31cf93905fdb5a6d81afa60105b.png" alt="">
|
||||
|
||||
可以看出,**共享状态调度架构为了提供高可用性和可扩展性,将集群状态之外的功能抽出来作为独立的服务。**具体来说就是:
|
||||
|
||||
- State Storage模块(资源维护模块)负责存储和维护资源及任务状态,以便Scheduler查询资源状态和调度任务;
|
||||
- Resource Pool即为多个节点集群,接收并执行Scheduler调度的任务;
|
||||
- 而Scheduler只包含任务调度操作,而不是像单体调度器那样还需要管理集群资源等。
|
||||
|
||||
共享状态调度也支持多种任务类型,但与两层调度架构相比,主要有两个不同之处:
|
||||
|
||||
- 存在多个调度器,每个调度器都可以拥有集群全局的资源状态信息,可以根据该信息进行任务调度;
|
||||
- 共享状态调度是乐观并发调度,在执行了任务匹配算法后,调度器将其调度结果提交给State Storage,由其决定是否进行本次调度,从而解决竞争同一种资源而引起的冲突问题,实现全局最优调度。而,两层调度是悲观并发调度,在执行任务之前避免冲突,无法实现全局最优匹配。
|
||||
|
||||
看到这里,我再和你说说**乐观并发调度和悲观并发调度的区别**吧。
|
||||
|
||||
乐观并发调度,强调事后检测,在事务提交时检查是否避免了冲突:若避免,则提交;否则回滚并自动重新执行。也就是说,它是在执行任务匹配调度算法后,待计算出结果后再进行冲突检测。
|
||||
|
||||
悲观并发调度,强调事前预防,在事务执行时检查是否会存在冲突。不存在,则继续执行;否则等待或回滚。也就是说,在执行任务匹配调度算法前,通过给不同的Framework发送不同的资源,以避免冲突。
|
||||
|
||||
现在,我们已经对共享状态调度有了一个整体印象,知道了它可以解决什么问题。那么接下来,我们再看看这种调度架构是如何设计的吧。
|
||||
|
||||
## 共享状态调度设计
|
||||
|
||||
共享状态调度的理念最早是Google针对两层调度器的不足,提出的一种调度架构。这种调度结构的典型代表有Google的Omega、微软的Apollo,以及Hashicorp的Nomad容器调度器。
|
||||
|
||||
作为Google公司的第二代集群管理系统,Omega在设计时参考了Borg的设计,吸收了Borg的优点,并改进了其不足之处。所以接下来,我就以Omega为例和你讲述共享状态调度的架构和工作原理吧。这样一来,你可以对照着[第11篇文章](https://time.geekbang.org/column/article/150811)中Borg的调度设计一起理解。
|
||||
|
||||
### Omega调度架构
|
||||
|
||||
Omega集群中有一个“Cell”的概念,每个Cell管理着部分物理集群,一个集群有多个Cell。实际上,你可以直接将这里的“Cell”理解为一个集群的子集群或子节点的集合。
|
||||
|
||||
Omega集群的调度架构示意图,如下所示。
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/28/d9/2865c041745b3cd70aa369710e3799d9.png" alt="">
|
||||
|
||||
我在介绍共享状态调度的架构时提到,State Storage模块负责存储和维护资源及任务状态,里面有一个Cell State文件,记录着全局共享的集群状态。实际上,State Storage组件中的集群资源状态信息,就是主本,而Cell State就是以主副本的形式存在的。每个调度器都包含一个私有的Cell State副本,也就是拥有了一个集群资源状态信息的副本,进而达到了共享集群资源状态信息的目的。
|
||||
|
||||
在Omega系统中,没有中央资源分配器,所有资源分配决策都在调度器(Scheduler)中进行。每个调度器都可以根据私有的Cell State副本,来制定调度决策。
|
||||
|
||||
调度器可以查看Cell的整个状态,并申请任何可用的集群资源。一旦调度器做出资源调度决策,它就会在原子提交中更新本地的Cell State的资源状态副本。若同时有多个调度器申请同一份资源,State Storage模块可以根据任务的优先级,选择优先级最高的那个任务进行调度。
|
||||
|
||||
可以看出,在Omega系统中的每个调度器,都具有对整个集群资源的访问权限,从而允许多个调度器自由地竞争空闲资源,并在更新集群状态时使用乐观并发控制来调解资源冲突问题。
|
||||
|
||||
这样一来,Omega就有效地解决了两层调度中Framework只拥有局部资源,无法实现全局最优的问题。
|
||||
|
||||
接下来,我们看一下Omega共享调度的工作原理吧。
|
||||
|
||||
### Omega共享调度工作原理
|
||||
|
||||
Omega 使用事务管理状态的设计思想,将集群中资源的使用和任务的调度类似于基于数据库中的一条条事务(Transaction)一样进行管理。显然,数据库是一个共享的状态,对应Omega中的Cell State,而每个调度器都要根据数据库的信息(即集群的资源信息)去独立完成自己的任务调度策略。
|
||||
|
||||
接下来,我们就具体看看吧。
|
||||
|
||||
如下图所示,在一个应用执行的过程中,调度器会将一个Job中的所有Task与Resource进行匹配,可以说Task与Resource之间是进行多对多匹配的。其间,调度器会设置多个Checkpoint来检测Resource是否都已经被占用,只有这个Job的所有Task可以匹配到可用资源时,这个Job才可以被调度。
|
||||
|
||||
这里的Job相当于一个事务,也就是说,当所有Task匹配成功后,这个事务就会被成功Commit,如果存在Task匹配不到可用资源,那么这个事务需要执行回滚操作,Job调度失败。
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/ce/89/ce672cf357f29fceab9d64d9d768cc89.png" alt="">
|
||||
|
||||
无论事务是否执行成功,调度器都会在事务执行之后,重新从主本那里同步更新本地Cell State的资源状态副本,以保证本地集群信息状态的有效性。若事务未成功执行,则调度器会在必要时重新运行其调度算法并再次尝试申请资源。
|
||||
|
||||
也就是说,调度器对Job的调度是具有原子性的,一个Job的所有Task都是一起调度的,即使部分Task调度失败了,调度器再次调度时也必须再次调度整个Job。多个调度器可以并行调度,无需等待其他调度器调度结果,若存在冲突时,进行冲突处理,比如根据Job的优先级,优先级高则获得资源。
|
||||
|
||||
由此我们可以看到,**Omega涉及了Job并发调度**。针对这一点,Omega采用了传统数据库中的乐观锁(MVCC,Multi-Version Concurrency Control,基于多版本的并发访问控制),即每一个应用都发放了所有的可用资源,在更新集群状态时使用乐观并发控制来解决资源冲突问题,来提高Omega的并发度。
|
||||
|
||||
不同的Omega调度器可以实现不同的策略,但有一些调度规则是所有调度器必须达成一致的,比如哪些资源是允许分配的、如何评估作业的优先级等。
|
||||
|
||||
因此,**Omega调度器将两层调度器中的集中式资源调度模块简化成了一些持久化的共享数据(状态)和针对这些数据的验证代码**。而这里的“共享数据”,实际上就是整个集群的实时资源状态信息,而验证代码就是解决调度冲突的调度规则。
|
||||
|
||||
## 知识扩展:单体调度、两层调度和共享调度的区别是什么?
|
||||
|
||||
现在,我已经带你学习了单体调度、双层调度和共享调度,那么这三种调度的区别是什么呢?接下来,我们就一起回忆并对比下吧。
|
||||
|
||||
我把这三种调度的架构示意图放到一起,先帮你有一个整体认识。
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/e2/7d/e262616421b8a9437e5f0f183c0ae77d.png" alt="">
|
||||
|
||||
**单体调度**,是由一个中央调度器去管理整个集群的资源信息和任务调度,也就是说所有任务只能通过中央调度器进行调度。
|
||||
|
||||
这种调度架构的优点是,中央调度器拥有整个集群的节点资源信息,可以实现全局最优调度。但它的缺点是,无调度并发性,且中央服务器存在单点瓶颈问题,导致支持的调度规模和服务类型受限,同时会限制集群的调度效率。因此,单体调度适用于小规模集群。
|
||||
|
||||
**两层调度**,是将资源管理和任务调度分为两层来调度。其中,第一层调度器负责集群资源管理,并将可用资源发送给第二层调度;第二层调度接收到第一层调度发送的资源,进行任务调度。
|
||||
|
||||
这种调度架构的优点是,避免了单体调度的单点瓶颈问题,可以支持更大的服务规模和更多的服务类型。但其缺点是,第二层调度器往往只对全局资源信息有部分可观察性,因此任务匹配算法无法实现全局最优。双层调度适用于中等规模集群。
|
||||
|
||||
**共享状态调度**,多个调度器,每个调度器都可以看到集群的全局资源信息,并根据这些信息进行任务调度。相较于其他两个调度架构来说,共享状态调度架构适用的集群规模最大。
|
||||
|
||||
这种调度架构的优点是,每个调度器都可以获取集群中的全局资源信息,因此任务匹配算法可以实现全局最优性。但,也因为每个调度器都可以在全局范围内进行任务匹配,所以多个调度器同时调度时,很可能会匹配到同一个节点,从而造成资源竞争和冲突。
|
||||
|
||||
虽然Omega的论文宣称可以通过乐观锁机制,避免冲突。但在工程实践中,如果没有妥善处理资源竞争的问题,则很可能会产生资源冲突,从而导致任务调度失败。这时,用户就需要对调度失败的任务进行处理,比如重新调度、任务调度状态维护等,从而进一步增加了任务调度操作的复杂度。
|
||||
|
||||
我将单体调度、两层调度、共享状态调度总结在了一张表格中:
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/84/1e/84553e72142841d5dc776c35e9c3f31e.jpg" alt="">
|
||||
|
||||
## 总结
|
||||
|
||||
今天,我主要与你分享了分布式调度架构设计中的共享状态调度。我们一起来总结下今天的核心知识点吧。
|
||||
|
||||
首先,我讲述了什么是共享状态调度。概括地说,共享状态调度是将单体调度器分解为多个服务,由多个服务共享集群状态,包括资源状态和任务状态等。
|
||||
|
||||
接下来,我以Google的Omega集群管理系统为例,和你分享了共享状态调度的架构和工作原理。共享状态调度包含多个调度器,每个调度器都可以看到集群的全局资源信息,并根据这些信息进行任务调度。
|
||||
|
||||
最后,我要和你说明的是,共享状态调度是乐观并发调度,调度器将其调度的结果以原子的方式提交给资源维护模块,由其决定是否进行本次调度。
|
||||
|
||||
接下来,我整理一张思维导图来帮助你巩固今天的核心知识点。
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/65/df/651b95130de61792b85d958fcc9c7cdf.png" alt="">
|
||||
|
||||
我想让你知道的是,在分布式领域中,共享状态调度,是Google号称的下一代集群管理系统Omega的调度机制,可以解决双层调度无法实现全局最优的问题,同时也避免了单体调度的单点瓶颈问题。
|
||||
|
||||
但,说到这儿你可能会回想起曾经看到的两句话:
|
||||
|
||||
1. 为了达到设计目标,Omega 的实现逻辑变得越来越复杂。 在原有的 Borg 共享状态模型已经能满足绝大部分需要的情况下,Omega 的前景似乎没有那么乐观。
|
||||
1. Omega系统缺点是,在小集群下没有优势。
|
||||
|
||||
这里,我再与你解释下,为什么说Omega是Google准备打造的下一代集群管理系统。
|
||||
|
||||
从调度架构方面来看,Borg无法支持同时存在多种业务类型的场景,并且存在单点瓶颈问题。而Omega解决了Borg的这两个问题,但是当多个调度器并行调度时,可能存在资源冲突,当资源申请产生冲突时,会导致大量任务或任务多次调度失败,增加了任务调度失败的故障处理的复杂度,比如需要进行作业回滚、任务状态维护等。
|
||||
|
||||
因此,设计一个好的冲突避免策略是共享状态调度的关键。对于小规模集群来说,其集群规模、任务数量等都不大,使用单体调度就可以满足其任务调度的需求,避免了考虑复杂的冲突避免策略。也就是说,共享状态调度比较适合大规模、同时存在多种业务类型的场景,不太适合小规模集群。
|
||||
|
||||
## 思考题
|
||||
|
||||
共享状态调度的核心是解决并发冲突,那你认为有没有什么好的方法可以解决冲突呢?
|
||||
|
||||
我是聂鹏程,感谢你的收听,欢迎你在评论区给我留言分享你的观点,也欢迎你把这篇文章分享给更多的朋友一起阅读。我们下期再会!
|
||||
|
||||
|
126
极客时间专栏/分布式技术原理与算法解析/第二站:分布式资源管理与负载调度/14 | 答疑篇:分布式事务与分布式锁相关问题.md
Normal file
126
极客时间专栏/分布式技术原理与算法解析/第二站:分布式资源管理与负载调度/14 | 答疑篇:分布式事务与分布式锁相关问题.md
Normal file
@@ -0,0 +1,126 @@
|
||||
<audio id="audio" title="14 | 答疑篇:分布式事务与分布式锁相关问题" controls="" preload="none"><source id="mp3" src="https://static001.geekbang.org/resource/audio/a2/0e/a27f98e580e56d6f2fbf48667fb7cc0e.mp3"></audio>
|
||||
|
||||
你好,我是聂鹏程。
|
||||
|
||||
到目前为止,“分布式技术原理与算法解析”专栏已经更新13篇文章了,主要与你介绍了“分布式起源”“分布式协调与同步”和“分布式资源管理与负载调度”。
|
||||
|
||||
在这里,我首先要感谢你们在评论区留下的一条条精彩留言。这让我感觉到,你们对分布式技术的浓厚兴趣,以及对我和对这个专栏的支持。这不但活跃了整个专栏的氛围、丰富了专栏的内容,也让我备受鼓舞,干劲儿十足地去交付更高质量的文章。
|
||||
|
||||
比如,@xfan、@Jackey等同学积极思考问题,并对文中有疑惑的地方结合其他资料给出了自己的思考和建议;再比如,@zhaozp 、@静水流深等同学,每次更新后都在坚持打卡学习;再比如,@约书亚等同学针对分布式事务提出了非常好的问题,@每天晒白牙等同学对文中内容进行了非常好的总结。
|
||||
|
||||
这些同学有很多,我就不再一一点名了。感谢你们的同时,我也相信,积极参与并留言也会帮助你更深入地理解每一个知识点。所以我希望,你接下来可以多多留言给我,让我们一起学习,共同进步。
|
||||
|
||||
留言涉及的问题有很多,但我经过进一步地分析和总结后,发现针对分布式事务和分布式锁的问题比较多,同学们的疑惑也比较多。
|
||||
|
||||
确实,这两大问题属于分布式技术的关键问题。因此,今天的这篇答疑文章,我就围绕这两大问题来进行一次集中的分析和回答吧。
|
||||
|
||||
我们先来看一下分布式事务的相关问题。
|
||||
|
||||
## 分布式事务的相关问题
|
||||
|
||||
在第6篇文章[“分布式事务:All or Nothing”](https://time.geekbang.org/column/article/144970)中,我介绍了两阶段提交协议和三阶段提交协议。有很多同学提出了疑问:两阶段提交协议(2PC)和三阶段提交协议(3PC)的区别,到底是什么?
|
||||
|
||||
在回答这个问题前,我建议你先回到[第6篇文章](https://time.geekbang.org/column/article/144970),去回忆一下它们的流程。然后,我们看看**2PC和3PC的第一步到底是不是“类似”的?**
|
||||
|
||||
2PC的第一步投票(voting)阶段中,参与者收到事务执行询问请求时,就执行事务但不提交;而3PC却写着在PreCommit阶段执行事务不提交。2PC和3PC的第一步,是非常不类似吧?
|
||||
|
||||
其实,我说它们类似,是指它们均是通过协调者,来询问参与者是否可以正常执行事务操作,参与者也都会给协调者回复。
|
||||
|
||||
- 在2PC中,如果所有参与者都返回结果后,会进入第二阶段,也就是提交阶段,也可以说是执行阶段,根据第一阶段的投票结果,进行提交或取消。
|
||||
- 在3PC中,进入真正的提交阶段前,还会有一个预提交阶段,这个预提交阶段不会做真正的提交,而是会将相关信息记录到事务日志中,当所有参与者都返回Yes消息后,才会真正进入提交阶段。
|
||||
|
||||
这样说明后,相信你对这个问题的疑惑应该解决了吧。
|
||||
|
||||
现在,我们继续延展一下这个问题吧。
|
||||
|
||||
**追问1:**3PC在预提交阶段,才开始执行事务操作,那协调者发送CanCommit给参与者的时候,参与者根据什么返回Yes或者No消息呢?
|
||||
|
||||
3PC在投票阶段(CanCommit阶段),协调者发送CanCommit询问后,参与者会根据自身情况,比如自身空闲资源是否足以支撑事务、是否会存在故障等,预估自己是否可以执行事务,但不会执行事务,参与者根据预估结果给协调者返回Yes或者No消息。
|
||||
|
||||
**追问2:**3PC出现的目的是,解决2PC的同步阻塞和数据不一致性问题。那么,我们不可以在2PC中直接去解决这些问题吗?3PC多了预提交和超时机制,就真的解决这些问题了吗?
|
||||
|
||||
**我们先来看看同步阻塞的问题。**
|
||||
|
||||
在2PC中,参与者必须等待协调者发送的事务操作指令,才会执行事务,比如提交事务或回滚等操作,如果协调者故障,也就是说参与者无法收到协调者的指令了,那么参与者只能一直等待下去。这就好比在一个班级里面,班主任是协调者,学生是参与者,班主任告诉学生今天下午6点组织一个比赛,但班主任今天生病了,根本到不了学校,并且也无法发送信息告诉学生,那么学生们就只能一直等待。
|
||||
|
||||
3PC在协调者和参与者中都引入了超时机制(2PC只是在协调者引入了超时),也就是说当参与者在一定时间内,没有接收到协调者的通知时,会执行默认的操作,从而减少了整个集群的阻塞时间。这就好比班主任生病了,学生默认等待半个小时,如果班主任还没有任何通知,那么默认比赛取消,学生可以自由安排,做自己的事情去了。
|
||||
|
||||
但其实,阻塞在实际业务中是不可能完全避免的。在上面的例子中,学生等待超时的半个小时中,其实还是阻塞的,只是阻塞的时间缩短了。所以,相对于2PC来说,3PC只是在一定程度上减少(或者说减弱)了阻塞问题。
|
||||
|
||||
**接下来,我们再看看数据不一致的问题吧。**
|
||||
|
||||
通过上面的分析可以看到,同步阻塞的根本原因是协调者发生故障,想象一下,比如现在有10个参与者,协调者在发送事务操作信息的时候,假设在发送给了5个参与者之后发生了故障。在这种情况下,未收到信息的5个参与者会发生阻塞,收到信息的5个参与者会执行事务,以至于这10个参与者的数据信息不一致。
|
||||
|
||||
3PC中引入了预提交阶段,相对于2PC来讲是增加了一个预判断,如果在预判断阶段协调者出现故障,那就不会执行事务。这样,可以在一定程度上减少故障导致的数据不一致问题,尽可能保证在最后提交阶段之前,各参与节点的状态是一致的。
|
||||
|
||||
所以说,3PC是研究者们针对2PC中存在的问题做的一个改进,虽然没能完全解决这些问题,但也起到了一定的效果。
|
||||
|
||||
在实际使用中,通常采用多数投票策略来代替第一阶段的全票策略,类似采用Raft算法选主的多数投票策略,即获取过半参与者的投票数即可。关于Raft算法的选主的具体原理,你可以再回顾下第4篇文章“[分布式选举:国不可一日无君](https://time.geekbang.org/column/article/143329)”中的相关内容。
|
||||
|
||||
**追问3:3PC也是只有一个协调者,为什么就不会有单点故障问题了?**
|
||||
|
||||
首先,我先明确下这里所说的单点故障问题。
|
||||
|
||||
因为系统中只有一个协调者,那么协调者所在服务器出现故障时,系统肯定是无法正常运行的。所以说,2PC和3PC都会有单点故障问题。
|
||||
|
||||
但是,3PC因为在协调者和参与者中都引入了超时机制,可以减弱单点故障对整个系统造成的影响。为什么这么说呢?
|
||||
|
||||
因为引入的超时机制,参与者可以在长时间没有得到协调者响应的情况下,自动将超时的事务进行提交,不会像2PC那样被阻塞住。
|
||||
|
||||
好了,以上就是关于分布式事务中的2PC和3PC的相关问题了,相信你对这两个提交协议有了更深刻的认识。接下来,我们再看一下分布式锁的相关问题吧。
|
||||
|
||||
## 分布式锁的相关问题
|
||||
|
||||
在第7篇文章[“分布式锁:关键重地,非请勿入”](https://time.geekbang.org/column/article/145505)后的留言中,我看到很多同学都问到了分布式互斥和分布式锁的关系是什么。
|
||||
|
||||
我们先来回顾下,分布式互斥和分布式锁分别是什么吧。
|
||||
|
||||
在分布式系统中,某些资源(即临界资源)同一时刻只有一个程序能够访问,这种排他性的资源访问方式,就叫作**分布式互斥。**这里,你可以再回顾下[第3篇文章](https://time.geekbang.org/column/article/141772)中的相关内容。
|
||||
|
||||
**分布式锁**指的是,在分布式环境下,系统部署在多个机器中,实现多进程分布式互斥的一种锁。这里,你可以再回顾下[第7篇文章](https://time.geekbang.org/column/article/145505)中的相关内容。
|
||||
|
||||
分布式锁的目的是,保证多个进程访问临界资源时,同一时刻只有一个进程可以访问,以保证数据的正确性。因此,我们可以说分布式锁是实现分布式互斥的一种手段或方法。
|
||||
|
||||
除了分布式互斥和分布式锁的关系外,很多同学都针对基于ZooKeeper和基于Redis实现分布式锁,提出了不少好问题。我们具体看看这些问题吧。
|
||||
|
||||
**首先,我们来看一下基于ZooKeeper实现分布式锁的问题。**有同学问,ZooKeeper分布式锁,可能存在多个节点对应的客户端在同一时间完成事务的情况吗?
|
||||
|
||||
这里,我需要先澄清一下,ZooKeeper不是分布式锁,而是一个分布式的、提供分布式应用协调服务的组件。基于ZooKeeper的分布式锁是基于ZooKeeper的数据结构中的临时顺序节点来实现的。
|
||||
|
||||
请注意,这里提到了ZooKeeper是一个分布式应用协调服务的组件。比如,在一个集中式集群中,以Mesos为例,Mesos包括master节点和slave节点,slave节点启动后是主动去和master节点建立连接的,但建立连接的条件是,需要知道master节点的IP地址和状态等。而master节点启动后会将自己的IP地址和状态等写入ZooKeeper中,这样每个slave节点启动后都可以去找ZooKeeper获取master的信息。而每个slave节点与ZooKeeper进行交互的时候,均需要一个对应的客户端。
|
||||
|
||||
这个例子,说明了存在多个节点对应的客户端与ZooKeeper进行交互。同时,由于每个节点之间并未进行通信协商,且它们都是独立自主的,启动时间、与ZooKeeper交互的时间、事务完成时间都是独立的,因此存在多个节点对应的客户端在同一时间完成事务的这种情况。
|
||||
|
||||
接下来,我们看一下基于Redis实现分布式锁的问题。**Redis为什么需要通过队列来维持进程访问共享资源的先后顺序?**
|
||||
|
||||
在我看来,这是一个很好的问题。
|
||||
|
||||
@开心小毛认为,Redis的分布式锁根本没有队列,收到setnx返回为0的进程会不断地重试,直到某一次的重试成为DEL命令后第一个到达的setnx从而获得锁,至于此进程在等待获得锁的众多进程中是不是第一个发出setnx的,Redis并不关心。
|
||||
|
||||
其实,客观地说,这个理解是合情合理的,是我们第一反应所能想到的最直接、最简单的解决方法。可以说,这是一种简单、粗暴的方法,也就是获取不到锁,就不停尝试,直到获取到锁为止。
|
||||
|
||||
但你有没有想过,当多个进程频繁去访问Redis时,Redis会不会成为瓶颈,性能会不会受影响。带着这个疑惑,我们来具体看看基于Redis实现的分布式锁到底需不需要队列吧。
|
||||
|
||||
如果没有队列维护多进程请求,那我们可以想到的解决方式,就是我刚刚和你分析过的,通过多进程反复尝试以获取锁。
|
||||
|
||||
但,这种方式有三个问题:
|
||||
|
||||
- 一是,反复尝试会增加通信成本和性能开销;
|
||||
- 二是,到底过多久再重新尝试;
|
||||
- 三是,如果每次都是众多进程进行竞争的话,有可能会导致有些进程永远获取不到锁。
|
||||
|
||||
在实际的业务场景中,尝试时间的设置,是一个比较难的问题,与节点规模、事务类型均有关系。
|
||||
|
||||
比如,节点规模大的情况下,如果设置的时间周期较短,多个节点频繁访问Redis,会给Redis带来性能冲击,甚至导致Redis崩溃;对于节点规模小、事务执行时间短的情况,若设置的重试时间周期过长,会导致节点执行事务的整体时间变长。
|
||||
|
||||
基于队列来维持进程访问共享资源先后顺序的方法中,当一个进程释放锁之后,队列里第一个进程可以访问共享资源。也就说,这样一来就解决了上面提到的三个问题。
|
||||
|
||||
## 总结
|
||||
|
||||
我针对前面13篇文章留言涉及的问题,进行了归纳总结,从中摘取了分布式事务和分布式锁这两个知识点,串成了今天这篇答疑文章。
|
||||
|
||||
今天没来得及和你扩展的问题,后续我会再找机会进行解答。最后,我要和你说的是,和我一起打卡分布式核心技术,一起遇见更优秀的自己吧。
|
||||
|
||||
篇幅所限,留言区见。
|
||||
|
||||
我是聂鹏程,感谢你的收听,欢迎你在评论区给我留言分享你的观点,也欢迎你把这篇文章分享给更多的朋友一起阅读。我们下期再会!
|
Reference in New Issue
Block a user