20 世纪 80 年代末至 90 年代初,面向对象编程思想给软件开发带来了一轮技术革新,就像润物细无声的春雨那般,向全世界的程序员们快速普及了模块化构建应用程序的方法,一直流行至今。
当下,我们可以看到类似的革新出现在了分布式系统开发,具体特点如下:
基于面向对象,四人帮基于经验提出和总结了对于一些常见软件设计问题的标准解决方案,其描述了一系列基于接口的模式,可以在各种环境中重用,这被称之为软件设计模式。历史一定程度上来说是重复的,随着这种架构模式的成熟,基于容器的分布式系统的设计模式也就自然而然地浮现了。
本篇主要阐述的是Brendan Burns
在基于容器的分布式系统中发现的三种设计模式:
基于容器分布式系统的设计模式会给分布式计算编码带来以下优势:
模式的目的是提供一般建议或结构来指导设计,这样做的好处有以下三点:
就像对象会定义边界一样,容器为定义接口提供了天然的边界;它不仅可以暴露特定应用的功能,还可以通过钩子函数来管理系统。传统的容器管理接口是极其有限的,如:
这些接口只能说满足基础的使用需求,但是就目前的现状来看,更丰富的接口可以为系统开发者与操作者提供更多的功能。鉴于HTTP
和JSON
的普及程度,可以考虑通过容器在特定的节点托管一个Web
服务来实现。这样做的目的是什么,可以从下面两个角度来看待:
k8s
使用Docker
的graceful deletion
功能,这就允许应用程序通过完成当前任务,把状态写入磁盘等等操作之后再终止,将这个功能扩展一下就可以使使有状态的分布式系统的状态管理更加容易。上面提到了单容器的接口,我们稍稍延伸一下,对于一个多容器组成的应用,会有怎样的设计模式呢?当然,此时我们仍旧有些限制条件需要讲清楚:
k8s
需要有Pod
这个逻辑概念扩展和增强现有的应用容器
目前最常见的多容器部署模式就是边车模式,边车模式就是由两个容器组成的单节点模式:
边车模式的一般方式如上图所示,可以看到应用程序容器和边车容器共享了许多资源:
我们通过下面的例子来看一下边车容器存在的必要性以及好处,图示如下:
其中主容器是一个web
服务,而日志处理
边车容器的工作就是收集本地磁盘的服务器日志,并将其流式传输至存储集群,这样做的好处有:
Web
服务器的cgroup
使得其处理延时降低,而日志处理容器则在web
服务器空闲时使用cpu
时间片进行日志处理改变和管理应用容器与外部世界的通信方式
第一次看大使模式,很可能会想这不就是另一种形式的边车模式吗?其实不然,首先第一点,大使模式下所有的请求响应信息交换全部是大使容器来完成的,应用程序容器只能和大使容器进行交流。
这种模式主要利用的特性是同一Pod
中的容器可以共享相同的localhost
网络接口,而且可以从两个角度看大使容器:
确保应用程序实现统一的监控接口
真实世界的应用程序大概率会有出现下面列出的几种情况:
假设我们需要有效地监控和运维应用程序,这就要求应用程序可以提供统一的通用接口来进行指标收集。这就是适配器发挥作用的场景了,对于不同应用容器提供的不同接口,可以使用适配器适配这种异构性并转化为一致的接口且原有服务代码不需要做任何改动。
主应用程序通过localhost
或者volume
与适配器容器通信,适配器经过一层处理提供统一的输出给外部使用者,一些常用的使用场景如下:
不要将模块化容器局限于单机容器协调上,其实模块化容器还可以使构建协调的多节点分布式应用程序变得更加容易。接下来将描述其中的三种分布式系统模,与前一节中的模式一样,这些模式也需要对 Pod
这个逻辑概念的支持。
分布式系统中最常见的问题就是领导选举(Leader election)问题,副本被普遍使用在一个组件的多个相同的实例之间共享负载,副本的另一个更加复杂的作用就是使得某一特定副本作为整个部署集的leader,其他副本作为热备(这个区分过程比较复杂),当原本 Leader 宕机时可以快速被选举为新的 Leader,以恢复系统功能。系统甚至可以并行地进行领导者选举,例如多个分片均需要确定领导者。
上图介绍了一个简单的分布式选举的例子:图中三个副本,任何一个副本都有可能成为主副本,首先第一个副本为主,若其不巧发生故障,第二阶段就会通过选举将第三个副本变成主副本,最后,第一个副本回复,重新加入集群,第三副本依旧作为主节点运行调度。
现在确实有许多类库可以进行领导者选举,但它们通常比较复杂并且难以被正确理解和使用,此外,它们还受到特定编程语言实现的限制。
所以本部分探讨的就是将领导者选举机制从应用程序中剥离至领导者选举专属容器中,我们可以考虑提供一组领导者选举容器,每个容器都与需要进行领导者选举的应用程序共同调度,这样就可以在这些领导者选举容器之间执行选举。
同时,它们可以在localhost
上为需要进行领导者选举的应用程序容器提供一个简化的HTTP API
(例如becomeLeader
、renewLeadership
等)。
这些领导者选举容器只需要由这个复杂领域的专家进行一次性构建即可,然后不管应用程序开发人员选择何种编程语言,都可以复用其简化的接口。这种方式代表了软件工程中最好的抽象和封装过程。
一个简单通用的容器化工作队列图示如下:
最左侧提供了一组需要被执行的工作项,然后工作队列管理容器接受输入工作项,将其分发给多个执行器进行消费,并且多个执行器中间没有任何交互,这样的好处是可以根据实际运行情况增加执行器数量来赢取时间。
虽然工作队列和领导者选举一样,是一个研究得很透彻的课题并且有很多框架对它们进行了很好的实现,但这些分布式系统设计模式仍然是可以在面向容器的架构中获益。在以前的系统中,框架将程序限制在单一的语言环境中(如Python
中的Celery
)。
对于一个容器,由于run()&mount()
接口的实现,使得实现一个通用的工作队列框架变得简单直接,可以将任意的处理代码打包成一个容器,再结合任意数据就构建成了一个完整的工作队列系统。开发完整工作队列所涉及的所有其他工作都可以由通用工作队列框架处理,并且可以被任何有相同需求的系统复用,用户代码集成等细节让我们看看下面的图示:
通用工作队列的图示,可重用框架容器以深灰色显示,而开发人员容器以浅灰色显示。
让我们结合上面的两张图一起看看,在我看来这就是一个从第一张图抽象出用户自定义代码从而形成第二张图的过程,且看我详细列出关键点。
用户定义的工作项由工作队列管理容器接收,此时涉及到一个论文中没有描述的点就是队列管理容器对于接收的工作项是默认进行了标准定义的,也就是说工作项都是定义好的标准输入。但实际情况并不可能让所有的输入项都有相同的输入标准,必须由一段用户自定义的处理代码来将输入标准化,不知机智的你是否想到了大使模式:
当工作队列管理容器获取了对应的工作项,接下来的工作就是下发给执行器进行处理,具体做法是调用一次性API
触发工作流,并且在工作容器的整个生命周期是不会有其他调用产生的。
最后一个我们要着重介绍的设计模式是分散/收集模式(Scatter/gather pattern),首先简单介绍一下这个模式:
分散/收集模式是一个树形模式,当外部客户端向根节点发送一个初始请求,根节点会将这个请求分发给大量服务器,每个服务器分片均返回部分数据,然后跟节点将这些部分结果组合起来形成一个针对原始请求的完整响应。
对于上面这样一个流程,实际上可以抽象出以下几个标准出来:
上述流程的大部分代码都是通用的,就像面向对象编程中一样,我们可以将这个一个个拆分实现并进行容器化。
值得一提的是要实现一个分散/收集系统,需要一个用户提供两个容器:
主体内容来自:
不错的资料:
最近一直在了解低代码开发平台相关知识,就是前面提到的一站式机器学习云研发平台[4],其中关于资源管理这块离不开k8s
的使用,因此花了不少精力在这上面,不出意外会出一个系列的学习笔记:
而且在学习分布式设计模式的同时也对分布式系统产生了一些兴趣,这一块后续会一起好好研究研究。
Design patterns for container-based distributed systems: https://static.googleusercontent.com/media/research.google.com/en//pubs/archive/45406.pdf
[2]Designing Distributed Systems: https://book.douban.com/subject/34844678/
[3][译] 基于容器的分布式系统设计模式: https://www.cnblogs.com/gaochundong/p/design-patterns-for-container-based-distributed-systems.html
[4]一站式机器学习云研发平台: https://www.howie6879.cn/p/%E4%B8%80%E7%AB%99%E5%BC%8F%E6%9C%BA%E5%99%A8%E5%AD%A6%E4%B9%A0%E4%BA%91%E5%BC%80%E5%8F%91%E5%B9%B3%E5%8F%B0/
感谢花时间阅读,有什么想法可以留言一起交流,如果对你有帮助,欢迎转发分享,点个好看呗哈哈 👉