IT系统天生就难以理解。这就是为什么我们将系统分成较小的部分。如果正确完成了这一划分,则无需了解系统的所有内容,我们仅需要单独了解每个部分以及小部分的组成方式即可。显然,“ 适当地 ” 进行划分并非易事,而且是问题的症结,我们已经研究了很长时间了。
这种划分是创建抽象的过程。抽象的作用是隐藏复杂性和不必要的细节。根据上下文,抽象具有不同的名称:API,系统,应用程序,微服务,模块,类,函数,系统调用。这些都是同一目标的体现。
但是,我们必须谨慎,不要将抽象与通用性混淆。正如Kevlin Henney 定义的那样:
将通用性概念与抽象性概念混为一谈是很容易的-我知道我有时会做的事情!尽管有时它们并存,但它们不是同一概念。通用化试图识别和定义不同专业化和可能性共有的特征;抽象试图删除不必要的细节。
随着我们正在开发的系统变得越来越大,我们需要引入新的抽象层,以便使它仍然易于理解。每增加一个新层,我们就会获得一个新的层次,可以在该层次上查看系统。变得重要的是在指定抽象层的组件之间的连接,而不是基于每个组件的实现细节。
因此,可理解的系统总大小随抽象层的数量呈指数增长:抽象层中的每个组件可以隐藏许多子组件,每个子组件可以隐藏许多子子组件,等等。一个非常重要的属性,它使我们能够创建非常复杂的系统,并且仍然对它们至少有一定程度的控制权!
我们可以从一个只有几类的小项目开始;随着事情的发展,我们将它们分为模块,每个模块都成为半独立的。一段时间后,我们可能会将我们的整体(但是多模块)应用程序拆分为多个微服务。而且在任何时候,我们的系统都不是处于真空状态:它与其他系统通信,每个系统都有其自己的抽象层对公众隐藏:微服务,模块,类等。
从外面看,抽象形成一个不可分割的整体。我们将其视为原子的:这就是抽象的全部;我们不想关心它的内部。
如何划分?
分解系统并非易事。选择正确的尺寸和适当的分割线边界一直是一项艰巨的任务。错误的抽象弊大于利。好的抽象是罕见的,并且由于遭受许多坏的抽象而后才出现。
但是,有一些不可否认的抽象成功案例。隐藏诸如机器代码,汇编,网络,设备访问,内核线程之类的较低级别的顾虑大部分是已解决的问题,并且每天对我们都有效。我们很少需要放大这些抽象,将它们用作黑匣子,正确地完成工作。
在编写应用程序时,我们不关心TCP / IP的底层内容,如何直接访问HDD或如何调度线程以及如何切换上下文。
但是,抽象层次结构越高,离业务领域越近,这种划分就越模糊和成问题。这有很多原因。首先,我们要处理的问题不再是技术问题,而是开始更加面向业务和领域。它不再只是0和1,而是涉及人类的复杂流程。
其次,问题开始变得更加专业化。网络,底层机器代码,线程是非常笼统的概念,可以轻松地重用。我们越接近核心问题域,我们的抽象就越针对特定问题,仅针对单个项目创建。
最后,由于这种专业化,我们在“正确的”抽象边界所在的位置上经验较少,数据点也较少。对于特定于项目的抽象,经验很少。
无服务器
好的抽象是好的,坏的抽象是坏的,但是…与无服务器有什么关系?比您最初想的要多!
所谓的“ 无服务器 ”方法使我们能够部署独立的功能(也称为lambda),这些功能可按需在提供商的基础架构上运行。最著名的提供商包括AWS,Google Cloud和Azure。我们只需要为函数执行所使用的资源付费;无需前期分配或费用。最重要的是,我们不必配置和维护服务器。
在创建基于“无服务器”的系统时,我们不仅需要部署功能,还需要描述如何触发这些功能。通常,这是通过云提供商的API或可视化当前设置并允许对其进行修改的GUI来完成的。触发器包括API调用,数据库事件,发送到队列的消息等。
开发无服务器系统,我们将很快发现我们希望将功能分组为应用程序/服务/微服务。如前所述,这与创建抽象的过程完全相同!无服务器不是解决IT系统复杂性的灵丹妙药:恰恰相反;它可能会带来更大的复杂性。
一旦我们尝试将功能分组到服务中,就需要进行以下连接:关于如何触发功能,应读取和写入何种数据源以及哪些其他系统的描述他们需要访问。
总结起来,无服务器世界中的服务由两部分组成:功能,构成该服务并定义逻辑;以及布线,这些布线描述了功能如何与外部世界交互—什么是功能触发器。
抽象是原子的
我们通常希望将抽象视为原子的不可分割的单元。我们希望它们成为执行某种功能的“黑匣子”。这既适用于“传统”部署的服务,也适用于“无服务器”服务。
以传统方式,在部署服务时,我们通常能够定义一个部署单元,例如docker映像,.jar文件或可执行文件。为了将“无服务器”服务视为适当的抽象,我们需要以某种方式定义该服务,这是构成我们服务的不可分割的单元。
这排除了使用GUI来描述无服务器功能和触发器的连接的方法。我们不希望将无服务器服务如何与外部世界交互的知识分散到我们多个云提供商提供的许多服务中。
但问题现在发生了;例如,当使用AWS时,功能可能在Lambda中定义;通过API网关的API;通过另外一家云提供商Cognito进行用户管理;一些元数据可能存储在S3上;等。使用Web界面时,几乎不可能了解各种服务的实现方式!
无服务器抽象的一种方式
人们尝试重新创建抽象也就不足为奇了,以便可以轻松地理解和再现单个无服务器服务。
这个区域中最受欢迎的项目是serverless.com;使用开源变体,您需要创建一个serverless.yml描述符,该描述符完全描述了整个服务。它包含有关提供程序的详细信息(因为serverless.com可以在Amazon,Google和Azure上部署服务),要部署的功能,其触发器以及需要创建的其他资源(如数据库表)的列表。
可以使用命令行工具将此类服务部署到提供商的基础结构,这使得既可以轻松地从头开始重新创建服务,又可以更新现有安装。这样,我们可以在多个环境(例如生产,暂存和测试)中轻松创建相同的服务。
问题解决了。我们有了抽象层。再次明确地界定了我们的服务,并通过进行了全面描述serverless.yml。现在,每个功能都可以视为较低层的抽象层,并且我们可以将多个此类无服务器服务组合在一起,以形成一个更大的系统。
与Docker区别?
这与定义“传统”部署单元(例如docker映像或.jar文件)有什么不同吗?毕竟,我们所有的代码都在其中,并描述了如何使用外部资源连接功能。
主要区别在于,我们运行部署serverless工具(或等效工具)而不是将部署单元打包在CI服务器上。如果您仔细研究,它实际上是从YAML到“ AWS VM”的解释器。
这样,在配置AWS / GCP / Azure等不同云提供商时时,我们实际上是在对“虚拟机”进行编程,该虚拟机由它们提供的服务定义。我们正在为“无服务器”服务创建部署单元。
(banq注:serverless.yml是一种针对不同云提供商环境的抽象配置,但是如果不同云提供商使用Knative这样统一的抽象的无服务器运行环境,这个配置就没有必要,或者在自己部署的Knative+K8s环境,就无需多此一层抽象)