[译] 基于容器的分布式系统设计模式

[译] 基于容器的分布式系统设计模式

名称:Design patterns for container-based distributed systems

作者:Brendan Burns, David Oppenheimer, Google

发布:HotCloud'16: Proceedings of the 8th USENIX Conference on Hot Topics in Cloud Computing, June 2016

译者:sangmado

1  简介

在20世纪80年代末期和90年代早期,面向对象编程彻底改变了软件的开发方式,使用模块化的组件进行应用程序构建变得更为普遍。时至今日,我们看到分布式系统开发也在进行着类似的变革,基于容器化软件组件构建的微服务架构正在变得越来越流行。容器,依赖其在容器化领域内创立的多重优点,已经成为分布式系统构建的基础"对象"。随着架构风格的成熟,我们也看到了设计模式的涌现,就像我们在面向对象编程时一样,出于对细粒度代码细节的封装抽象,最终揭示了在各种应用程序和算法中相通的更高级的设计模式。

这篇论文描述了我们在基于容器构建的分布式系统中识别出的三种类型的设计模式:用于容器管理的单容器模式,多容器紧密协作的单节点模式,用于分布式算法的多节点模式。就像面向对象设计模式一样,这些为分布式计算而生的设计模式引入了最佳实践,简化了代码开发,并提升了使用它们的系统的可靠性。

2 分布式系统设计模式

设计模式的涌现和文档化,是在面向对象编程被使用多年之后才发生的。这些设计模式将一些常规编程问题的解决方法进行了代码化和规范化,而这些编码规约更进一步地改善了行业的编程水平,因为它使得经验不足的程序员也可以输出高质量的代码。同时,可复用的类库机制也使得代码越来越可靠,开发速度也比以往变得更快。

现如今,分布式系统软件工程的最先进技术,看起来更像是20世纪80年代早期编程的模样,而不像是后期的面向对象软件开发。这其中的一个成功案例就是 MapReduce 模式。MapReduce 模式将大数据编程的力量引入至广阔的应用领域和开发者群体,将正确的模式应用在合适的位置,极大地改进了分布式系统的质量、速度和可访问能力。尽管如此,MapReduce 的成功仍然受到了单一编程语言的限制,截至目前 Apache Hadoop 生态系统仍主要是由 Java 语言编写。所以,为分布式系统设计开发一套真正全面的模式,需要一套非常通用的、与语言无关的工具来表述系统中的元素。

万幸的是,最近两年 Linux 容器技术兴起并取得了广泛的采用。容器与容器镜像完美的满足了开发分布式系统中模式的抽象需求。迄今为止,容器和容器镜像已被广泛的验证,成为从开发至生产过程中交付软件的更好更可靠的方法。依靠紧密的封装,依赖自治,原子部署与成败标记等功能,其显著地改进了老式的云与数据中心应用部署方式。然而,除了在部署工具方面表现出色,实际上容器还有巨大的潜力,我们相信它注定可以成为类似对象在面向对象编程中的地位,并将推进分布式系统设计模式的发展。在接下来的章节中,将解释为什么我们相信这是必然的,并描述在未来的若干年中,已涌现出并常规化指导分布式系统软件工程设计的设计模式。

3 单容器管理模式

容器为接口定义提供了天然的边界,就像对象的边界一样。容器不仅可以暴露应用级别的功能,也可以通过接口回调为管理系统服务。

传统的容器管理接口都是极其有限的,一个容器一般仅暴露三个原语:run(), pause(), stop()。尽管这些接口用途很大,但更丰富的接口可以赋予系统开发和运维人员更多的管理功能。同时,目前几乎每种现代编程语言均支持基于 HTTP 的 Web 服务,而且支持类似 JSON 等的通用数据格式,通过使用容器托管 Web 服务器并指定端点,不仅可以暴露主要功能,更使得定义基于 HTTP 管理的 API 成为可能,

容器可以针对北向暴露非常丰富的应用程序信息,比如应用特定的监控指标,开发者关注的分析信息,组件配置信息和服务日志等。具体的实现中,Kubernetes、Aurora、Marathon 等容器管理系统均允许用户定义通过特定的端点来进行健康探测。

对于南向接口,容器提供一种机制来定义一个生命周期,使得编写被管理系统管控的软件组件更加方便。例如,一个集群管理系统可以为任务分配优先级,高优先级任务即使在集群超卖的条件下也可以被有保障的执行。这种保障是通过剔除正在运行的低优先级任务进行强制实施的,而那些低优先级任务则可在集群资源可用时再被执行。剔除机制可以简单粗暴到直接杀死低优先级任务,但这种方式给开发者带来了额外的负担,他们不得不考虑代码在任意时刻都能被杀死的场景。取而代之的,若是在应用程序和管理系统之间构建一个生命周期机制,开发者通过依赖和遵循契约,不仅系统开发变得更简单,同时也可以让应用程序变得更具管理性。例如,Kubernetes 使用 Docker 的 "优雅删除" 功能来对容器进行提前预警,通过发送 SIGTERM 信号给即将被终结的容器,在应用程序设定的时间窗口之后再发送 SIGKILL 信号。这样则可让应用程序在被终结前完成正在执行的操作,将业务状态写入磁盘等。想象下,我们可以提供一种机制来支持状态的序列化和状态恢复,以便基于状态的分布式系统对状态的管理更优雅。

再举个复杂生命周期的例子,比如 Android 系统中的 Activity 模型,它支持一系列的回调接口和一个用于定义系统该如何触发这些回调的状态机。如果没有这些生命周期定义,开发鲁棒的、可靠的 Android 应用程序将变得难上加难。而在基于容器的系统上下文中,通常会定义一系列的回调钩子,以便在容器创建、启动、终止时进行调用。另一个关于南向接口 API 的例子是容器对 "自我复制" 机制的支持,以便对服务进行扩展。

4 单节点、多容器应用模式

除了单个容器的接口之外,我们还可以看到跨容器设计模式的涌现。之前我们已经确定了几个这样的模式。这些单节点模式由共生容器组成,它们被共同调度到同一台主机上。容器管理系统支持将多个容器作为一个原子单元进行协同调度,Kubernetes 将此抽象为 "Pods",而 Nomad 则将其抽象为 "task groups",这也是本节中描述的模式所必需的特性。

4.1 边车模式

边车模式是首个也是最常见的多容器部署模式,边车扩展并增强了主容器。例如,主容器可能是一个 Web 服务器,它可以与一个能够从本地磁盘收集 Web 服务器的日志 "logsaver" 边车容器配合,并将日志发送至存储系统集群。图1展示了边车模式的一个示例。另一个常见的示例是一个基于 Web 的本地磁盘内容服务器,这些内容由边车容器填充,边车容器定期对 Git 仓库、内容管理系统或其他数据源的内容进行定期同步。这两个例子在 Google 内部应用中都很常见。在同一机器上的容器可以共享同一个本地磁盘卷,也使得边车机制成为可能。

当然,尽管也可以将边车容器的功能构建到主容器中,但是使用独立容器有几个好处。

  • 首先,容器是资源计算和分配的单元,因此例如可以对一个 Web 服务器容器的 cgroup 进行配置,以便它对查询提供一致的低延迟响应,而 logsaver 容器则可配置为当 Web 服务器不繁忙时再执行。
  • 其次,容器是打包的单元,因此将业务服务和日志存储分离到不同的容器中,可以很容易地将它们的开发职责划分给两个独立的开发团队,并允许对它们进行独立测试,当然也可以一起测试。
  • 第三,容器是可复用的单元,因此边车容器可以与许多不同的主容器进行搭配(例如,一个日志存储容器可以与产生日志的任何组件一起使用)。
  • 第四,容器提供了一个失败管控边界,使整个系统能够正常降级(例如,即使日志存储程序发生故障,Web 服务器仍然可以继续提供服务)。
  • 最后,容器是部署单元,它允许对每个功能块进行升级,并在必要时独立地回滚。(当然也需要指出,这个好处也有一个缺点,即整个系统的测试矩阵必须考虑所有可能在生产中遇到的容器版本组合,这可能是巨大的,因为一组容器通常不能被原子性的整体升级。当然,虽然单体应用程序没有这个问题,但组件化系统在某些方面更加容易测试,因为它们是由可以独立测试的较小单元构建的。)

请注意,以上五个优点适用于本文后续部分中描述的所有容器模式。

4.2 大使模式

我们观察到的下一个模式是大使模式。大使容器代理了与主容器之间的通信。例如,开发人员可能将使用 memcache 协议的应用程序与 twemproxy 大使进行搭配。应用程序认为它只是与本地主机上的单个 memcache 进行交互,但实际上 twemproxy 则将分片请求转发至在其他地方安装的分布式 memcache 集群节点上。

这个容器模式在三方面对程序员的开发过程进行了简化:他们只需要思考和编写应用程序连接到一个在本地主机上的服务器,他们可以通过在本地机器上运行一个真正的 memcache 实例而非大使来对应用程序进行独立测试,他们可以在其他应用程序中复用 twemproxy 大使,甚至可以是不同的编程语言。因为同一机器上的容器共享相同的本地主机网络接口,方使得大使模式成为可能。图2展示了此模式的一个示例。

4.3 适配器模式

我们观察到的最后一个单节点模式是适配器模式。与简化外部世界呈现视图的大使模式相反,适配器模式则是通过简化的、同质化的外部世界视图来进行呈现。其通过标准化输出和跨多个容器的接口来实现这一点。适配器模式的一个典型案例,就是通过适配器模式来确保系统中的所有容器都具有相同的监控接口。现今的应用程序使用各种各样的方法来输出它们的指标(例如 JMX、statsd 等)。但是,如果所有应用程序都提供一致的监控接口,则使用一个监控工具就可以更容易的收集、聚合和展示来自异构应用程序的指标。在 Google,我们通过代码规约实现了这一点,但这只有在您从头构建软件时才有可能实现。那些遗留的应用程序和开源应用程序,可以通过适配器模式提供统一的接口,而不需要修改原始应用程序。主容器可以通过本地主机或共享的本地卷与适配器进行通信。如图3所示。请注意,尽管一些现有的监控解决方案也能够与多种类型的后端进行通信,那是因为它们在监控系统本身中使用特定于应用程序的代码,这显然不太符合关注点分离模式。

5 多节点应用模式

除了在一台机器上互相协作的容器外,模块化的容器使得构建协调的多节点分布式应用程序变得更加容易。接下来,我们将描述其中的三种分布式系统模式。与前一节中的模式类似,这些模式也需要系统对 Pod 抽象的支持。

5.1 领导者选举模式

分布式系统中最常见的问题之一就是领导者选举。通常实例副本用于在一个组件的多个相同实例之间共享负载,但应用程序副本的另一个更复杂的使用场景是需要将一个 "领导者" 副本与其他副本区分开来。如果当前领导者副本故障了,则其他的副本需要迅速的进行取代。系统甚至可以并行地进行领导者选举,例如,多个分片均需要确定领导者。

有许多类库可以进行领导者选举,它们通常很复杂,也难以正确理解和使用。此外,它们还受到使用特定编程语言实现带来的限制。一种替代的方案就是将领导者选举机制从应用程序中剥离至领导者选举专属容器中。提供一组领导者选举容器,它们中的每个容器都与需要进行领导者选举的应用程序共同调度,则可在这些领导者选举容器之间执行选举。同时,它们可以在 localhost 上为需要进行领导者选举的应用程序容器提供一个简化的 HTTP API (例如,becomeLeader、renewLeadership 等)。这些领导者选举容器可以由这个复杂领域的专家进行构建,然后不管应用程序开发人员选择何种编程语言,都可以复用其简化的接口。这种方式代表了软件工程中最好的抽象和封装过程。

5.2 工作队列模式

尽管工作队列模式,也包括领导者选举模式,已经是被研究得很深入的课题,并且有许多框架也实现了它们,但这些分布式系统设计模式仍然是可以在面向容器的系统架构中获益。在以往的系统中,应用程序框架限制了项目的单一开发语言环境(如 Python 中的 Celery),或者任务分发和二进制执行已与实现者脱离(如 Condor)。

容器针对 run() 和 mount() 接口的实现,使得实现一个通用的工作队列框架变得简单直接,可以将任意的处理代码打包成一个容器,与任意数据,构建成一个完整的工作队列系统。
开发人员只需要构建一个容器,可以从文件系统接收一个数据文件,并将其转换为一个输出文件;这个容器还可以作为工作队列的一个阶段来使用。所有其他的涉及开发一个完整的工作队列的工作均可由通用工作队列进行处理,并可在任意时刻进行复用。用户代码集成到这个工作队列共享框架的方式如图4所示。

5.3 分散/收集模式

最后一个我们要着重介绍的分布式系统涉及模式是分散/收集模式。给定一个系统,一个外部客户端向"根"或"父"节点发送一个初始请求,这个"根"节点将请求分发给大量服务器以执行并行计算,每个服务器分片均返回部分数据,然后"根"节点再将这些数据收集到原始请求的单个响应中。这种设计模式在搜索引擎中很常见。开发这样一个分布式系统需要大量的样板代码:分散请求、收集响应、与客户端交互等,而这些代码在面向对象编程中是完全通用的,重复的。通过容器化的方案来进行重构,当客户端发起请求至"根"容器时,按照开发人员的设计,请求被扇出至叶子容器集群中,开发人员再指定一个容器负责结果集的收集和聚合。

具体的说,实现分散/收集系统,用户需要提供两类容器。首先,一类容器实现叶子节点计算,这个容器执行部分计算并返回相应的结果。第二种容器是合并容器,这个容器需要汇总所有叶子容器的计算结果,并组织成一个单一的响应后输出。很容易看出用户可以实现任意深度的分散/收集系统,只需提供已实现这些相对简单的接口的容器即可。这样一个系统如图5所示。

6 相关工作

早期的面向服务的 SOA 架构与基于容器的分布式系统架构有许多相通的特征。例如,两者都强调可重用的组件需要通过定义良好的接口基于网络进行通信。另一方面,与我们所描述的多容器模式相比,SOA 系统中的组件往往粒度更大,耦合更松散。此外,SOA 中的组件通常实现具体的业务,而我们在这里所关注的组件更倾向于通过通用类库来简化分布式系统的构建。近期涌现出的 "微服务" 一词,也可以描述我们在本文中讨论的组件类型。

网络组件的标准化管理接口概念至少可以追溯到 SNMP 时代,而 SNMP 主要关注于硬件组件的管理,还没有出现负责管理基于微服务或容器的系统的标准。但这并没有阻碍多种多样的容器管理系统的发展,比如 Aurora、ECS、Docker Swarm、Kubernetes、Marathon 和 Nomad 等。

我们在第5节中提到的所有分布式系统设计模式都有很长的历史。你可以在 Github 上找到许多领导者选举模式的实现,尽管它们看起来像是类库而不是独立的组件。也有许多流行的工作队列实现,包括 Celery 和 Amazon SQS。分散/收集模式已被识别为一种企业级系统集成模式。

7 结论

正如面向对象编程引出了面向对象"设计模式"和代码范式,我们也看到基于容器的架构也引出了基于容器的分布式系统设计模式。在本文中,我们识别了三种类型的模式:用于系统管理的单容器模式、用于容器间紧密协作的单节点模式和用于分布式算法的多节点模式。在这些模式中,容器提供了许多与面向对象系统中的"对象"相同的益处,例如可以方便地在多个团队之间划分应用实现,在新的上下文中复用组件等。此外,它们还为分布式系统提供了一些独特的好处,比如可以独立地升级组件,可以用多种语言编写组件,还可以让整个系统优雅地降级。我们相信,容器模式将会持续不断的扩充,在未来的几年里,就像面向对象编程在几十年前所做的那样,通过对分布式系统开发的标准化和规范化,它们将带来分布式系统编程的革命性变化。

8 鸣谢

想法不会凭空出现在我们的脑海里,本文的工作受到了 Brian Grant、Tim Hockin、Joe Beda 和 Craig McLuckie 深深地影响。

参考文献

  • [1] Docker Engine http://www.docker.com
  • [2] rkt: a security-minded standards-based container engine https://coreos.com/rkt/
  • [3] Erich Gamma, John Vlissides, Ralph Johnson, Richard Helm, Design Patterns: Elements of Reusable Object-Oriented Software, Addison-Wesley, Massachusetts, 1994.
  • [4] Jeffrey Dean, Sanjay Ghemawat, MapReduce: Simplified Data Processing on Large Clusters, Sixth Symposium on Operating System Design and Implementation, San Francisco, CA 2004.
  • [5] Apache Hadoop, http://hadoop.apache.org
  • [6] Kubernetes, http://kubernetes.io
  • [7] Apache Aurora, https://aurora.apache.org.
  • [8] Marathon: A cluster-wide init and control system for services, https://mesosphere.github.io/marathon/
  • [9] Managing the Activity Lifecycle, http://developer.android.com/training/basics/activitylifecycle/index.html
  • [10] Brendan Burns, The Distributed System ToolKit: Patterns for Composite Containers, http://blog.kubernetes.io/2015/06/the-distributedsystem-toolkit-patterns.html
  • [11] Nomad by Hashicorp, https://www.nomadproject.io/
  • [12] Gregor Hohpe, Enterprise Integration Patterns, Addison-Wesley, Massachusetts, 2004.
  • [13] Celery: Distributed Task Queue, http://www.celeryproject.org/
  • [14] Amazon Simple Queue Service, https://aws.amazon.com/sqs/
  • [15] https://www.kernel.org/doc/Documentation/cgroupv1/cgroups.txt
  • [16] Service Oriented Architecture, https://en.wikipedia.org/wiki/Serviceorientedarchitecture
  • [17] Amazon EC2 Container Service, https://aws.amazon.com/ecs/
  • [18] Docker Swarm https://docker.com/swarm
  • [19] J. Case, M. Fedor, M. Schoffstall, J. Davin, A Simple Network Management Protocol (SNMP), https://www.ietf.org/rfc/rfc1157.txt, 1990.
  • [20] R. G. Gallager, P. A. Humblet, P. M. Spira, A distributed algorithm for minimum-weight spanning trees, ACM Transactions on Programming Languages and Systems, January, 1983.
  • [21] M.J. Litzkow, M. Livny, M. W. Mutka, Condor: a hunter of idle workstations, IEEE Distributed Computing Systems, 1988.
  • [22] https://linuxcontainers.org/

 

原文地址:Design patterns for container-based distributed systems

声明:本篇文章《[译] Design patterns for container-based distributed systems》为翻译文章,由译者发布自博客园个人技术博客,未经作者本人同意禁止以任何的形式转载,任何自动的或人为的爬虫转载行为均为耍流氓。

posted @ 2020-05-04 18:31  sangmado  阅读(2140)  评论(2编辑  收藏  举报