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

  • 2021 年 2 月 23 日
  • AI

面向对象编程引爆了软件开发领域,其设计模式使得初级程序员也能写出易维护和可重用的代码。随着基于容器的微服务架构越发流行,同时容器的完整性和隔离性,可以使得它作为分布式系统中的“对象”,从而构建分布式系统部署的设计模式。包括,单容器管理模式单节点多容器模式分布式算法的多节点模式

一、单容器管理模式(Single-container management patterns)

跟面向对象编程中的“对象”一样,容器天然拥有“接口”的概念。包括,用户自定义的调用接口和管理系统中的hook接口。一般来说,容器管理接口非常有限,主要围绕3个动词来定义,即run(),pause()和stop()。但随着Restful的兴起,在容器中内嵌一个web server,作为管理端点提供各种接口是非常方便的。

站在容器的角度向上看,它可以暴露全面的应用信息,包括监控(QPS、健康信息等)、Profile(线程、堆栈、锁、网络流量统计等)、配置和日志。比如,Kubernetes、Aurora和Marathon都会提供类似/health的HTTP端点来执行用户自定义的健康检查。

站在容器的角度向下看,容器也提供生命周期的管理接口。因此,基于容器可以更容易编写可以被管理系统控制的应用组件内。比如,集群管理系统会为任务分配优先级,当集群资源不足时,会驱逐正在运行的低优先级任务并等待资源足够后,调度高优先级任务。最粗暴的驱逐是直接杀掉低优先级任务,但这会为开发人员提供不必要的排错负担。因此,应用软件和管理系统间必须要定义一个规范的生命周期,使应用更容易被管理的同时更可靠。比如,Kubernetes使用“优雅删除”的特性来实现驱逐,即首先发送SIGTERM信号,让应用软件捕获后可以自行处理收尾逻辑,包括完成在途操作、脏数据刷磁盘等等,并在指定时间后,再次发送SIGKILL信号来结束流程。在此流程上进一步扩展,可以实现对状态序列化和状态恢复的支持,使得有状态的分布式系统更容易被管理。

一个更复杂的例子是安卓的应用程序,它有一系列的状态回调,包括onCreate,onStart()和onStop()。在基于容器的应用开发中,容器在创建时、启动时和终止前,为用户提供了hook来实现自定义逻辑。

二、单节点多容器模式(Single-node, multi-container application patterns)

在容器提供的接口之上,容器管理系统必须要能够支持将多个“共生容器”同时调度到一个节点上。比如,Kubernetes的“Pods”和Nomad的“task groups”。这就引出了不同“共生容器”的设计模式。

1、Sidecar模式

首当其冲的就是Sidecar模式,它通过多容器部署实现对主容器的扩展和增强。比如,主容器是一个web server,其sidecar容器是一个log saver,它将server的本地日志定时同步到一个分布式存储系统中。或者相反,sidecar是一个sync agent,定时将远程内容同步到本地,支持web server提供内容服务。毕竟,sidecar模式可以让单节点上的多个容器共享本地磁盘的volumes。

虽然从理论上,将sidecar容器合入主容器也是非常方便的,但解耦能带来5个好处:首先,容器是资源审计和分配的原子单位。这使得web server容器可以配置cgroups支持低延时,而log saver在其空闲时才开始同步;第二,容器是程序打包的基本单位,web server和log saver可以由2个不同的小组开发和测试,权责清晰;第三,容器是重用的基本单位,sidecar容器可以作为一个通用组件与其他主容器编排部署;第四,容器提供了失效边界,功能上互不影响,可以实现优雅降级;最后,容器是部署的基本单位,主容器和sidecar容器都能单独升级和回滚。但是要注意,测试人员就必须注意生产环境中的联合版本,并且多组容器间将无法进行原子升级。

2、Ambassador模式

Ambassador容器与主容器共享network namesapce,可以作为其proxy,代理出入的通信流量。比如,一个需要与memcache集群通信的应用可以将twemproxy作为一个Ambassador容器,实现客户端的负载均衡。这给开发人员带来3点好处:首先,开发时可以只关注对单台localhost memcacbe服务的逻辑;其次,应用测试时可以在本地起一个memcache服务,以standalone的方式完成;最后,twemproxy同样是一个通用ambassador组件,提供对不同语言开发应用的一致代理。

3、Adapter模式

如果说Ambassador模式为应用程序简化了复杂的外部世界,那么Adapter模式完全相反,它为外部世界简化了复杂的应用程序。即,它为多个容器提供标准化的接口和输出。比如,当今的应用提供各种复杂的方式暴露监控信息(JMX,statsd等),apater模式为复杂的世界提供了统一接口,并不需要应用容器作出任何改变。主容器可以通过localhost或local volume与Adapter保持通信,并由Adapter聚合所有监控信息,统一对外输出。

三、多节点应用模式(Multi-node application patterns

当谈到基于容器的多节点分布式系统时,必须要求容器管理平台支持Pod层面的抽象概念。

1、选主模式(Leader election pattern)

在多副本集群中,Leader是一个重要概念,市面上也有各种难以理解的、不同语言实现的选主lib库。应用容器可以尝试使用选主容器来作为替选方案。

构建一组选主容器集合,并与需要选主的应用容器一一绑定实现选主。选主容器使用简单的HTTP API(becomeLeader、renewLeadership等)告知应用容器选主结果。选主容器可以专注复杂领域的优化实现,可以一次构建、到处使用,并且无需关注应用程序的实现语言。这也是软件工程领域的最佳抽象和封装。

2、队列模式(Work queue pattern)

与选主一样,市面上的队列框架受限于单一语言的开发环境,或者需要业务开发者适配开发。基于容器的队列模式可以仅通过2个接口(run和mount),就实现对任意语言和异构数据的简单、直接处理。比如,应用开发人员仅需要读取文件系统的输入文件,处理后再输出到文件系统,即可作为队列模式处理中的一个阶段。同样,队列容器框架也是一次构建,到处使用。

3、Scatter/gather模式

搜索引擎场景下,客户端向根节点发出请求,根节点将请求分发到后端服务集群执行并行计算,根节点汇总每个shard节点返回部分数据结果,最后返回给客户端。类似MapReduce的框架同样可以基于容器来实现。用户提供2个容器来实现业务逻辑:首先是在叶子节点上执行计算的容器,该容器执行部分计算并返回响应的结果;其次是汇总计算结果的容器,该容器负责将所有叶子节点容器的计算结果汇总成单条结果,响应客户端。注意,负责与客户端交互并分发请求任务的根节点是通用的容器实现。

四、参考文献

1、Design Patterns for Container-based Distributed Systems