名称:Designpatternsforcontainer-baseddistributedsystems
作者:BrendanBurns,DavidOppenheimer,Google
发布:HotCloud'16:Proceedingsofthe8thUSENIXConferenceonHotTopicsinCloudComputing,June2016
在20世纪80年代末期和90年代早期,面向对象编程彻底改变了软件的开发方式,使用模块化的组件进行应用程序构建变得更为普遍。时至今日,我们看到分布式系统开发也在进行着类似的变革,基于容器化软件组件构建的微服务架构正在变得越来越流行。容器,依赖其在容器化领域内创立的多重优点,已经成为分布式系统构建的基础"对象"。随着架构风格的成熟,我们也看到了设计模式的涌现,就像我们在面向对象编程时一样,出于对细粒度代码细节的封装抽象,最终揭示了在各种应用程序和算法中相通的更高级的设计模式。
这篇论文描述了我们在基于容器构建的分布式系统中识别出的三种类型的设计模式:用于容器管理的单容器模式,多容器紧密协作的单节点模式,用于分布式算法的多节点模式。就像面向对象设计模式一样,这些为分布式计算而生的设计模式引入了最佳实践,简化了代码开发,并提升了使用它们的系统的可靠性。
设计模式的涌现和文档化,是在面向对象编程被使用多年之后才发生的。这些设计模式将一些常规编程问题的解决方法进行了代码化和规范化,而这些编码规约更进一步地改善了行业的编程水平,因为它使得经验不足的程序员也可以输出高质量的代码。同时,可复用的类库机制也使得代码越来越可靠,开发速度也比以往变得更快。
现如今,分布式系统软件工程的最先进技术,看起来更像是20世纪80年代早期编程的模样,而不像是后期的面向对象软件开发。这其中的一个成功案例就是MapReduce模式。MapReduce模式将大数据编程的力量引入至广阔的应用领域和开发者群体,将正确的模式应用在合适的位置,极大地改进了分布式系统的质量、速度和可访问能力。尽管如此,MapReduce的成功仍然受到了单一编程语言的限制,截至目前ApacheHadoop生态系统仍主要是由Java语言编写。所以,为分布式系统设计开发一套真正全面的模式,需要一套非常通用的、与语言无关的工具来表述系统中的元素。
万幸的是,最近两年Linux容器技术兴起并取得了广泛的采用。容器与容器镜像完美的满足了开发分布式系统中模式的抽象需求。迄今为止,容器和容器镜像已被广泛的验证,成为从开发至生产过程中交付软件的更好更可靠的方法。依靠紧密的封装,依赖自治,原子部署与成败标记等功能,其显著地改进了老式的云与数据中心应用部署方式。然而,除了在部署工具方面表现出色,实际上容器还有巨大的潜力,我们相信它注定可以成为类似对象在面向对象编程中的地位,并将推进分布式系统设计模式的发展。在接下来的章节中,将解释为什么我们相信这是必然的,并描述在未来的若干年中,已涌现出并常规化指导分布式系统软件工程设计的设计模式。
容器为接口定义提供了天然的边界,就像对象的边界一样。容器不仅可以暴露应用级别的功能,也可以通过接口回调为管理系统服务。
传统的容器管理接口都是极其有限的,一个容器一般仅暴露三个原语:run(),pause(),stop()。尽管这些接口用途很大,但更丰富的接口可以赋予系统开发和运维人员更多的管理功能。同时,目前几乎每种现代编程语言均支持基于HTTP的Web服务,而且支持类似JSON等的通用数据格式,通过使用容器托管Web服务器并指定端点,不仅可以暴露主要功能,更使得定义基于HTTP管理的API成为可能,
再举个复杂生命周期的例子,比如Android系统中的Activity模型,它支持一系列的回调接口和一个用于定义系统该如何触发这些回调的状态机。如果没有这些生命周期定义,开发鲁棒的、可靠的Android应用程序将变得难上加难。而在基于容器的系统上下文中,通常会定义一系列的回调钩子,以便在容器创建、启动、终止时进行调用。另一个关于南向接口API的例子是容器对"自我复制"机制的支持,以便对服务进行扩展。
除了单个容器的接口之外,我们还可以看到跨容器设计模式的涌现。之前我们已经确定了几个这样的模式。这些单节点模式由共生容器组成,它们被共同调度到同一台主机上。容器管理系统支持将多个容器作为一个原子单元进行协同调度,Kubernetes将此抽象为"Pods",而Nomad则将其抽象为"taskgroups",这也是本节中描述的模式所必需的特性。
边车模式是首个也是最常见的多容器部署模式,边车扩展并增强了主容器。例如,主容器可能是一个Web服务器,它可以与一个能够从本地磁盘收集Web服务器的日志"logsaver"边车容器配合,并将日志发送至存储系统集群。图1展示了边车模式的一个示例。另一个常见的示例是一个基于Web的本地磁盘内容服务器,这些内容由边车容器填充,边车容器定期对Git仓库、内容管理系统或其他数据源的内容进行定期同步。这两个例子在Google内部应用中都很常见。在同一机器上的容器可以共享同一个本地磁盘卷,也使得边车机制成为可能。
当然,尽管也可以将边车容器的功能构建到主容器中,但是使用独立容器有几个好处。
请注意,以上五个优点适用于本文后续部分中描述的所有容器模式。
我们观察到的下一个模式是大使模式。大使容器代理了与主容器之间的通信。例如,开发人员可能将使用memcache协议的应用程序与twemproxy大使进行搭配。应用程序认为它只是与本地主机上的单个memcache进行交互,但实际上twemproxy则将分片请求转发至在其他地方安装的分布式memcache集群节点上。
这个容器模式在三方面对程序员的开发过程进行了简化:他们只需要思考和编写应用程序连接到一个在本地主机上的服务器,他们可以通过在本地机器上运行一个真正的memcache实例而非大使来对应用程序进行独立测试,他们可以在其他应用程序中复用twemproxy大使,甚至可以是不同的编程语言。因为同一机器上的容器共享相同的本地主机网络接口,方使得大使模式成为可能。图2展示了此模式的一个示例。
除了在一台机器上互相协作的容器外,模块化的容器使得构建协调的多节点分布式应用程序变得更加容易。接下来,我们将描述其中的三种分布式系统模式。与前一节中的模式类似,这些模式也需要系统对Pod抽象的支持。
分布式系统中最常见的问题之一就是领导者选举。通常实例副本用于在一个组件的多个相同实例之间共享负载,但应用程序副本的另一个更复杂的使用场景是需要将一个"领导者"副本与其他副本区分开来。如果当前领导者副本故障了,则其他的副本需要迅速的进行取代。系统甚至可以并行地进行领导者选举,例如,多个分片均需要确定领导者。
有许多类库可以进行领导者选举,它们通常很复杂,也难以正确理解和使用。此外,它们还受到使用特定编程语言实现带来的限制。一种替代的方案就是将领导者选举机制从应用程序中剥离至领导者选举专属容器中。提供一组领导者选举容器,它们中的每个容器都与需要进行领导者选举的应用程序共同调度,则可在这些领导者选举容器之间执行选举。同时,它们可以在localhost上为需要进行领导者选举的应用程序容器提供一个简化的HTTPAPI(例如,becomeLeader、renewLeadership等)。这些领导者选举容器可以由这个复杂领域的专家进行构建,然后不管应用程序开发人员选择何种编程语言,都可以复用其简化的接口。这种方式代表了软件工程中最好的抽象和封装过程。
尽管工作队列模式,也包括领导者选举模式,已经是被研究得很深入的课题,并且有许多框架也实现了它们,但这些分布式系统设计模式仍然是可以在面向容器的系统架构中获益。在以往的系统中,应用程序框架限制了项目的单一开发语言环境(如Python中的Celery),或者任务分发和二进制执行已与实现者脱离(如Condor)。
容器针对run()和mount()接口的实现,使得实现一个通用的工作队列框架变得简单直接,可以将任意的处理代码打包成一个容器,与任意数据,构建成一个完整的工作队列系统。开发人员只需要构建一个容器,可以从文件系统接收一个数据文件,并将其转换为一个输出文件;这个容器还可以作为工作队列的一个阶段来使用。所有其他的涉及开发一个完整的工作队列的工作均可由通用工作队列进行处理,并可在任意时刻进行复用。用户代码集成到这个工作队列共享框架的方式如图4所示。
最后一个我们要着重介绍的分布式系统涉及模式是分散/收集模式。给定一个系统,一个外部客户端向"根"或"父"节点发送一个初始请求,这个"根"节点将请求分发给大量服务器以执行并行计算,每个服务器分片均返回部分数据,然后"根"节点再将这些数据收集到原始请求的单个响应中。这种设计模式在搜索引擎中很常见。开发这样一个分布式系统需要大量的样板代码:分散请求、收集响应、与客户端交互等,而这些代码在面向对象编程中是完全通用的,重复的。通过容器化的方案来进行重构,当客户端发起请求至"根"容器时,按照开发人员的设计,请求被扇出至叶子容器集群中,开发人员再指定一个容器负责结果集的收集和聚合。
具体的说,实现分散/收集系统,用户需要提供两类容器。首先,一类容器实现叶子节点计算,这个容器执行部分计算并返回相应的结果。第二种容器是合并容器,这个容器需要汇总所有叶子容器的计算结果,并组织成一个单一的响应后输出。很容易看出用户可以实现任意深度的分散/收集系统,只需提供已实现这些相对简单的接口的容器即可。这样一个系统如图5所示。
我们在第5节中提到的所有分布式系统设计模式都有很长的历史。你可以在Github上找到许多领导者选举模式的实现,尽管它们看起来像是类库而不是独立的组件。也有许多流行的工作队列实现,包括Celery和AmazonSQS。分散/收集模式已被识别为一种企业级系统集成模式。
正如面向对象编程引出了面向对象"设计模式"和代码范式,我们也看到基于容器的架构也引出了基于容器的分布式系统设计模式。在本文中,我们识别了三种类型的模式:用于系统管理的单容器模式、用于容器间紧密协作的单节点模式和用于分布式算法的多节点模式。在这些模式中,容器提供了许多与面向对象系统中的"对象"相同的益处,例如可以方便地在多个团队之间划分应用实现,在新的上下文中复用组件等。此外,它们还为分布式系统提供了一些独特的好处,比如可以独立地升级组件,可以用多种语言编写组件,还可以让整个系统优雅地降级。我们相信,容器模式将会持续不断的扩充,在未来的几年里,就像面向对象编程在几十年前所做的那样,通过对分布式系统开发的标准化和规范化,它们将带来分布式系统编程的革命性变化。
想法不会凭空出现在我们的脑海里,本文的工作受到了BrianGrant、TimHockin、JoeBeda和CraigMcLuckie深深地影响。