创建良好的分布式应用程序并非易事:此类系统通常遵循12要素应用程序和微服务原则。 它们必须是无状态的,可伸缩的,可配置的,独立发布的,容器化的,可自动化的,并且有时是事件驱动的和无服务器的。 创建后,它们应该易于升级,并且长期可以承受。 在当今的竞争需求之间找到良好的平衡仍然是一项艰巨的努力。
在本文中,我将探讨分布式平台如何发展以实现这种平衡,更重要的是,在分布式系统的演进中还需要发生什么事情,以简化可维护的分布式体系结构的创建。 如果您希望看到我在同一主题上的演讲,请在InfoQ上查看我的QConLondon录音。
分布式应用程序需求
在本次讨论中,我将把现代分布式应用程序的需求分为四个类别-生命周期,网络,状态,绑定-并简要分析它们在最近几年中的发展情况。
生命周期
让我们从基础开始。 当我们编写一项功能时,编程语言将指示生态系统中的可用库,打包格式和运行时。 例如,Java使用.jar格式,将所有Maven依赖项用作生态系统,并将JVM用作运行时。 如今,随着发布周期的缩短,生命周期更重要的是能够自动部署,从错误中恢复以及扩展服务的能力。 这组功能广泛地代表了我们的应用程序生命周期需求。
联网
从某种意义上讲,今天几乎每个应用程序都是分布式应用程序,因此需要联网。 但是现代分布式系统需要从更广泛的角度掌握网络。 从服务发现和错误恢复开始,到启用现代软件发布技术以及各种跟踪和遥测。 为了我们的目的,我们甚至将不同的消息交换模式,点对点和发布/订阅方法以及智能路由机制包括在此类别中。
州
当我们谈论状态时,通常是关于服务状态以及为什么最好是无状态的。 但是管理我们的服务的平台本身需要状态。 这是执行可靠的服务编排和工作流,分布式单例,临时调度(cron作业),幂等,有状态错误恢复,缓存等所需的。这里列出的所有功能都依赖于底层状态。 尽管实际的状态管理不在本文讨论范围之内,但关注状态的分布式原语及其抽象却令人关注。
捆绑
分布式系统的组件不仅必须彼此对话,而且还必须与现代或旧式外部系统集成。 这就要求连接器能够转换各种协议,支持不同的消息交换模式,例如轮询,事件驱动,请求/答复,转换消息格式,甚至能够执行自定义错误恢复过程和安全机制。
在不涉及一次性使用案例的情况下,以上内容代表了创建良好的分布式系统所需的通用原语的良好集合。 今天,许多平台都提供了这样的功能,但是我们在本文中寻找的是过去十年中我们使用这些功能的方式如何变化,以及在下一个十年中它将如何变化。 为了进行比较,让我们看一下过去的十年,看看基于Java的中间件如何满足这些需求。
传统中间件限制
企业服务总线(ESB)及其变体(例如面向消息的中间件,更轻量级的集成框架等)可满足上述需求的上一代,这是其中一种众所周知的传统解决方案。 ESB是一种中间件,可以使用面向服务的体系结构(即经典SOA)在异构环境之间实现互操作性。
虽然ESB将为您提供良好的功能集,但ESB的主要挑战是整体架构以及业务逻辑和平台之间紧密的技术耦合,从而导致了技术和组织的集中化。 当将服务开发并部署到这样的系统中时,它与分布式系统框架紧密结合,从而限制了服务的发展。 这通常只会在软件生命周期的后期才变得明显。
以下是每类需求的一些问题和局限性,这些问题和局限性使得ESB在现代时代不再有用。
生命周期
在传统的中间件中,通常只有一个受支持的语言运行时(例如Java),它规定了软件的打包方式,可用的库,需要修补的频率等。业务服务必须使用这些库。使其与以相同语言编写的平台紧密结合。 实际上,这导致协调的服务和平台升级,从而阻止了独立和常规的服务和平台发布。
联网
虽然传统的中间件具有围绕与其他内部和外部服务交互的高级功能集,但它具有一些主要缺点。 网络功能集中于一种主要语言及其相关技术。 对于Java语言,即JMS,JDBC,JTA等。更重要的是,网络问题和语义也深深地刻在业务服务中。 有一些具有抽象的库来解决网络问题(例如曾经很受欢迎的Hystrix项目),但是该库的抽象“泄漏”到了服务的编程模型,交换模式和错误处理语义以及库本身中。 虽然可以方便地在一个位置编码和读取与网络方面混合的整个业务逻辑,但这却将这两个问题紧密地结合到了一个实现中,最终形成了一条共同的演进之路。
州
为了执行可靠的服务编排,业务流程管理以及实施模式(例如Saga模式和其他运行缓慢的流程),平台需要在幕后保持持久状态。 类似地,临时动作(例如触发计时器和cron作业)建立在状态之上,并且需要数据库在分布式环境中进行群集和恢复。 这里的主要约束是以下事实:与状态交互的库和接口没有完全抽象出来,也没有与服务运行时分离。 通常,这些库必须配置有数据库详细信息,并且它们存在于服务中,从而将语义和依赖关系泄漏到应用程序域中。
捆绑
使用集成中间件的主要驱动程序之一是能够使用不同的协议,数据格式和消息交换模式连接到各种其他系统。 但是,这些连接器必须与应用程序一起使用,这意味着必须将依赖关系与业务逻辑一起进行更新和修补。 这意味着必须在服务中来回转换数据类型和数据格式。 这意味着必须根据消息交换模式来构造代码并设计流程。 这些是一些示例,说明即使抽象的端点也如何影响传统中间件中的服务实现。
云原生趋势
传统的中间件功能强大。 它具有所有必要的技术功能,但缺乏现代数字业务需求所要求的快速更改和扩展的能力。 这就是微服务体系结构及其设计现代分布式应用程序的指导原则所要解决的问题。
微服务背后的思想及其技术要求促进了容器和Kubernetes的普及和广泛使用。 这开始了一种新的创新方式,这种方式将影响我们今后几年处理分布式应用程序的方式。 让我们看看Kubernetes和相关技术如何影响每组需求。
生命周期
容器和Kubernetes将我们打包,分发和部署应用程序的方式发展为与语言无关的格式。 关于Kubernetes模式和Kubernetes对开发人员的影响的文章很多,在这里我将简短介绍。 但是请注意,对于Kubernetes,要管理的最小原语是容器,它专注于在容器级别和流程模型上交付分布式原语。 这意味着它在管理应用程序的生命周期方面,运行状况检查,恢复,部署和扩展方面做得很出色,但是在容器内的分布式应用程序的其他方面却没有做得很好,例如灵活的网络,状态管理和绑定。
您可能会指出,Kubernetes具有状态工作负载,服务发现,cron作业和其他功能。 的确是这样,但是所有这些原语都是在容器级别上的,并且在容器内部,开发人员仍然必须使用特定于语言的库来访问我们在本文开头列出的更详细的功能。 这就是推动诸如Envoy,Linkerd,Consul,Knative,Dapr,Camel-K等项目的原因。
联网
事实证明,Kubernetes提供的围绕服务发现的基本网络功能是一个很好的基础,但对于现代应用程序来说还不够。 随着微服务数量的增加和部署速度的加快,对更高级的发布策略,管理安全性,指标,跟踪,从错误中恢复,模拟错误等等方面的需求变得越来越具有吸引力,并产生了一种一种新的软件类别,称为服务网格。
这里更令人兴奋的是,趋势是将与网络相关的关注点从包含业务逻辑的服务移到单独的运行时(无论是sidecar还是节点级代理),再移到单独的运行时。 如今,服务网格可以进行高级路由,帮助测试,处理安全性的某些方面,甚至可以说出特定于应用程序的协议(例如Envoy支持Kafka,MongoDB,Redis,MySQL等)。 尽管服务网格作为一种解决方案可能尚未得到广泛采用,但它触及了分布式系统中的真正痛点,我坚信它将找到其形状和存在形式。
除了典型的服务机制外,还有其他项目,例如Skupper ,这些项目证实了将网络功能放入外部运行时代理的趋势。 Skupper通过第7层虚拟网络解决了多集群通信难题,并提供了先进的路由和连接功能。 但是,它没有将Skupper嵌入到业务服务运行时中,而是每个Kubernetes名称空间运行一个实例,该实例充当共享的补充工具。
综上所述,容器和Kubernetes在应用程序的生命周期管理方面迈出了重要的一步。 服务网格和相关技术遇到了真正的痛点,并为将更多职责从应用程序移到代理中奠定了基础。 让我们看看下一步。
州
我们在前面列出了依赖状态的主要集成原语。 管理状态非常困难,应将其委派给专门的存储软件和托管服务。 这不是这里的主题,而是在语言无关的抽象中使用状态来帮助集成用例。 今天,许多努力试图在语言无关的抽象后面提供有状态的原语。 有状态的工作流管理是基于云的服务中的强制性功能,例如AWS Step Functions,Azure Durable Functions等示例。在基于容器的部署中, CloudState和Dapr都依赖于sidecar模型来提供对以下方面的更好去耦分布式应用程序中的状态抽象。
我期待的是还将上面列出的所有有状态功能抽象到一个单独的运行时中。 这意味着工作流管理,单例,幂等,事务管理,cron作业触发器和有状态错误处理都可靠地发生在Sidecar(或主机级代理)中,而不是存在于服务中。 业务逻辑不需要在应用程序中包含此类依赖关系和语义,并且可以从绑定环境中声明性地请求此类行为。 例如,Sidecar可以充当cron作业触发器,幂等消费者和工作流管理器,而自定义业务逻辑可以作为回调来调用或插入工作流的某些阶段,错误处理,临时调用或唯一幂等要求等
另一个有状态用例是缓存。 无论是通过服务网格层执行请求缓存,还是使用诸如Infinispan,Redis,Hazelcast等之类的数据缓存,都有一些将缓存功能推到应用程序运行时之外的示例 。
捆绑
尽管我们的主题是将所有分布式需求与应用程序运行时脱钩,但这种趋势也继续伴随着绑定。 连接器,协议转换,消息转换,错误处理和安全中介都可以移出服务运行时。 我们还没有到那里,但是在诸如Knative和Dapr之类的项目中已经朝这个方向进行了尝试。 将所有这些职责移出应用程序运行时将导致更小,更注重业务逻辑的代码。 这样的代码将存在于独立于分布式系统需求的运行时中,而分布式系统需求可以作为预包装功能使用。
Apache Camel-K项目采用了另一种有趣的方法。 该项目没有使用代理运行时来伴随主应用程序,而是依靠智能的Kubernetes Operator来构建具有Kubernetes和Knative的附加平台功能的应用程序运行时。 在这里,单一代理是负责包括应用程序所需的分布式系统原语的操作员。 不同之处在于,某些分布式原语已添加到应用程序运行时中,而某些已在平台中启用(也可能包括Sidecar)。
未来架构趋势
从广义上看,我们可以得出结论,通过将功能转移到平台级别,分布式应用程序的商品化达到了新的领域。 除了生命周期之外,现在我们还可以观察到联网,状态抽象,声明性事件和端点绑定(现成可用),并且EIP在此列表中排在后面。 有趣的是,商品化使用进程外模型(sidecar)进行功能扩展,而不是使用运行时库或纯平台功能(例如新的Kubernetes功能)。
现在,我们通过将所有传统的中间件功能(也称为ESB)转移到其他运行时中来,这很快就会过去,不久,我们在服务中要做的就是编写业务逻辑。
与传统的ESB时代相比,此体系结构将业务逻辑与平台更好地分离了,但是还没有完全分离。 许多分布式原语,例如经典的企业集成模式(EIP):拆分器,聚合器,过滤器,基于内容的路由器; 流处理模式:映射,过滤,折叠,联接,合并,滑动窗口; 仍然必须包含在业务逻辑运行时中,并且许多其他依赖于多个不同且重叠的平台附加组件。
如果我们将在不同领域进行创新的各种云原生项目进行堆叠,那么最终将得到如下图所示:
这里的图仅用于说明目的,它有目的地选择代表性的项目并将其映射到分布式基元的类别。 实际上,您不会同时使用所有这些项目,因为其中某些项目是重叠的且不兼容的工作负载模型。 如何解释这个图?
- Kubernetes和容器在多语言应用程序的生命周期管理中取得了巨大飞跃,并为未来的创新奠定了基础。
- 服务网格技术通过高级网络功能在Kubernetes上进行了改进,并开始涉足应用程序方面。
- 尽管Knative主要通过快速扩展专注于无服务器工作负载,但它也满足了服务编排和事件驱动的绑定需求。
- Dapr以Kubernetes,Knative和Service Mesh的思想为基础,并深入应用程序运行时以解决有状态的工作负载,绑定和集成需求,充当现代的分布式中间件。
该图可帮助您直观地看到,很可能在将来,我们最终将使用多个运行时来实现分布式系统。 多个运行时不是因为多个微服务,而是因为每个微服务都将由多个运行时组成,最有可能是两个运行时-自定义业务逻辑运行时和分布式原语运行时。
引入多运行时微服务
这是对开始形成的多运行时微服务体系结构的简要说明。
您还记得科学家们制作的电影《阿凡达》和“机车服”“机甲”,他们去旷野探索潘多拉吗? 这种多运行时架构类似于这些Mecha套件,它们为类人驱动程序提供了超能力。 在电影中,您要穿上西装才能获得力量并获得破坏性武器。 在这种软件体系结构中,您将拥有构成应用程序核心的业务逻辑(称为微逻辑)和提供强大的现成分布式原语的sidecar mecha组件。 微机制与mecha功能相结合,形成了一个多运行时微服务,该服务将进程外功能用于其分布式系统需求。 最棒的是,Avatar 2即将面世,以帮助推广这种架构。 我们最终可以在所有软件会议上用令人赞叹的机甲图片替换老式的边车摩托车;-)。 接下来,让我们看看该软件体系结构的详细信息。
这是一个类似于客户端-服务器体系结构的两组件模型,其中每个组件都是独立的运行时。 它与纯客户端-服务器体系结构的不同之处在于,这两个组件都位于同一主机上,并且它们之间没有可靠的网络连接。 这两个组件的重要性相同,它们可以在任一方向上启动操作并充当客户端或服务器。 其中的一个组件称为Micrologic,它拥有从几乎所有分布式系统问题中剥离出来的非常少的业务逻辑。 另一个随附的组件是Mecha,它提供了本文中我们一直在讨论的所有分布式系统功能(生命周期除外,它是一个平台功能)。
Micrologic和Mecha可能是一对一的部署(称为sidecar模型),也可以是带有几个Micrologic运行时的共享Mecha。 第一种模型最适用于Kubernetes等环境,而第二种模型则适用于边缘部署。
微逻辑运行时特征
让我们简要地探讨Micrologic运行时的一些特征:
- Micrologic组件本身不是微服务。 它包含微服务将具有的业务逻辑,但是该逻辑只能与Mecha组件结合使用。 另一方面,微服务是自包含的,没有整体功能的一部分,也没有一部分处理流程扩展到其他运行时中。 Micrologic和其Mecha对应产品的组合形成了微服务。
- 这也不是功能或无服务器架构。 无服务器最著名的是其托管的快速扩展和从零扩展到零的功能。 在无服务器体系结构中,函数实现单个操作,因为这是可伸缩性的单位。 在这方面,功能不同于实现多种操作的Micrologic,但实现方式不是端到端的。 最重要的是,操作的实现分布在Mecha和Micrologic运行时上。
- 这是客户端-服务器体系结构的一种特殊形式,针对无需编码即可使用众所周知的分布式基元进行了优化。 另外,如果我们假设Mecha扮演服务器角色,那么每个实例都必须经过专门配置以与单个客户端一起工作。 它不是旨在与典型的客户端-服务器体系结构同时支持多个客户端的通用服务器实例。
- Micrologic中的用户代码不会直接与其他系统交互,也不会实现任何分布式系统原语。 它通过事实上的标准(例如HTTP / gRPC, CloudEvents规范)与Mecha进行交互 ,并且Mecha使用丰富的功能并在配置的步骤和机制的指导下与其他系统进行通信。
- 尽管Micrologic仅负责实施从分布式系统问题中剥离出来的业务逻辑,但仍必须至少实现一些API。 它必须允许Mecha和平台通过预定义的API和协议与其进行交互(例如,通过遵循Kubernetes部署的云原生设计原则 )。
Mecha运行时特征
以下是一些Mecha运行时特征:
- Mecha是一个通用的,高度可配置的,可重用的组件,提供分布式原语作为现成的功能。
- Mecha的每个实例都必须配置为与一个Micrologic组件(边车模型)一起使用,或者配置为与几个组件共享。
- Mecha不对Micrologic运行时做任何假设。 它与使用开放协议和格式(例如HTTP / gRPC,JSON,Protobuf,CloudEvents)的多语言微服务甚至单片系统一起使用。
- Mecha以简单的文本格式(如YAML,JSON)声明性地配置,该格式指示要启用的功能以及如何将其绑定到Micrologic端点。 对于专业的API交互,可以为Mechan附加规范,例如OpenAPI , AsyncAPI ,ANSI-SQL等。对于由多个处理步骤组成的有状态工作流,可以使用诸如Amazon State Language的规范。 对于无状态集成,可以使用类似于Camel-K YAML DSL的方法来使用企业集成模式( EIP )。 这里的关键点是,所有这些都是Mecha无需编码即可实现的简单的,基于文本的,声明性的,多种语言的定义。 请注意,这些都是未来派的预测,目前,尚无用于状态编排或EIP的Mechas,但我希望现有的Mechas(Envoy,Dapr,Cloudstate等)很快就会开始添加此类功能。 Mecha是应用程序级别的分布式原语抽象层。
- 与其依靠多个代理来实现不同的目的(例如网络代理,缓存代理,绑定代理),不如使用一个Mecha提供所有这些功能。 一些功能(例如存储,消息持久性,缓存等)的实现将被其他云或本地服务插入并支持。
- 管理平台(例如Kubernetes或其他云服务)可以提供一些与生命周期管理有关的分布式系统问题,而不是使用诸如Open App Model这样的通用开放规范的Mecha运行时。
这种架构的主要好处是什么?
好处是业务逻辑和越来越多的分布式系统问题之间的松耦合。 软件系统的这两个要素具有完全不同的动力学。 业务逻辑始终是内部编写的唯一的自定义代码。 它经常更改,具体取决于您的组织优先级和执行能力。 另一方面,分布式原语是解决这篇文章中列出的问题的人,它们是众所周知的。 这些由软件供应商开发,并作为库,容器或服务使用。 该代码根据供应商的优先级,发布周期,安全补丁,开放源代码管理规则等而更改。这两个组之间几乎没有可见性并且无法相互控制。
微服务原理有助于通过有限的上下文使不同的业务领域脱钩,每个微服务都可以独立发展。 但是微服务架构无法解决将业务逻辑与中间件问题耦合在一起带来的困难。 对于某些依赖于集成用例的微服务,这可能不是一个大因素。 但是,如果您的领域涉及复杂的集成(每个人都越来越多),那么遵循微服务原则将无济于事,避免与中间件耦合。 即使中间件被表示为您包含在微服务中的库,当您开始迁移和更改这些库时,这种耦合也会变得显而易见。 而且,您需要的分布原语越多,您与集成平台的耦合就越多。 将中间件作为预定义的API(而不是库)上的独立运行时/进程使用,有助于松散耦合并实现每个组件的独立演化。
这也是为供应商分发和维护复杂的中间件软件的更好方法。 只要与中间件的交互是通过涉及开放API和标准的进程间通信进行的,软件供应商就可以按照自己的节奏自由发布补丁和升级。 消费者可以自由使用他们喜欢的语言,库,运行时,部署方法和过程。
这种架构的主要缺点是什么?
进程间通信。 分布式系统的业务逻辑和中间件机制(您知道名称的来源)在不同的运行时中,并且需要HTTP或gRPC调用而不是进程内方法调用。 但是请注意,这不是应该转到其他计算机或数据中心的网络调用。 Micrologic运行时和Mecha应该以较低的延迟和最小的网络问题并置在同一主机上。
复杂。 下一个问题是,是否值得进行复杂的开发,并维护此类系统以获得所获利益。 我认为答案将越来越倾向于是。 分布式系统的需求和发布周期的步伐正在增加。 并且此架构为此进行了优化。 我前段时间写道,未来的开发人员必须具有混合开发技能 。 这种体系结构确认并进一步加强了这种趋势。 应用程序的一部分将用高级编程语言编写,部分功能将由必须进行声明性配置的现成组件提供。 这两个部分的互连不是在编译时或在启动时通过进程内依赖注入,而是在部署时通过进程间通信互连。 该模型可实现更高的软件重用率和更快的变更速度。
微服务之后的功能不起作用
微服务架构有一个明确的目标。 它针对变化进行了优化。 通过将应用程序划分到业务域中,此体系结构通过独立的团队进行分离,管理和独立发布的服务,为软件的发展和可维护性提供了最佳的服务边界。
如果我们看一下无服务器体系结构的编程模型,它主要基于功能。 功能已针对可伸缩性进行了优化。 通过功能,我们将每个操作分成一个独立的组件,以便可以快速,独立和按需扩展。 在此模型中,部署粒度是一项功能。 选择函数是因为它是代码构造,其输入的速率与缩放行为直接相关。 这是一种针对极端可伸缩性(而不是复杂系统的长期可维护性)进行了优化的体系结构。
从AWS Lambda的流行及其完全托管的运营性质来看,Serverless的其他方面又如何呢? 在这方面,“ AWS无服务器”为配置速度进行了优化,但缺乏控制和锁定功能。 但是完全托管的方面不是应用程序体系结构,而是一种软件使用模型。 它在功能上是正交的,类似于使用基于SaaS的平台,该平台在理想情况下应适用于任何类型的体系结构,无论是单片,微服务,机电还是功能。 在许多方面,AWS Lambda类似于完全托管的Mecha架构,但有一个很大的区别:Mecha不执行功能模型,而是允许围绕业务域使用更具凝聚力的代码构造,而与所有中间件无关。
另一方面,Mecha架构为中间件独立性优化了微服务。 尽管微服务彼此独立,但它们在很大程度上依赖于嵌入式分布式基元。 Mecha架构将这两个问题分为单独的运行时,从而允许独立团队独立发布它们。 这种脱钩可以改善第二天的操作(例如修补和升级)以及业务逻辑内聚单元的长期可维护性。 在这方面,Mecha体系结构是微服务体系结构的自然发展,它通过根据引起最大摩擦的边界拆分软件来进行开发。 与功能模型相比,该优化以软件重用和演进的形式提供了更多好处,而功能模型却以代码的过度分配为代价进行了优化,以实现极大的可伸缩性。
结论
分布式应用程序有许多要求。 创建有效的分布式系统需要多种技术和良好的集成方法。 尽管传统的单片中间件提供了分布式系统所需的所有必要技术功能,但它却缺乏快速更改,适应和扩展业务所需的能力。 这就是为什么基于微服务的架构背后的思想为容器和Kubernetes的快速普及做出了贡献的原因。 随着云原生领域的最新发展,我们现在通过将所有传统中间件功能转移到平台和现成的辅助运行时中来全面发展。
应用程序功能的这种商品化主要是使用进程外模型进行功能扩展,而不是运行时库或纯平台功能。 That means that in the future it is highly likely that we will use multiple runtimes to implement distributed systems. Multiple runtimes, not because of multiple microservices, but because every microservice will be composed of multiple runtimes; a runtime for the custom micro business logic, and an off-the-shelf, configurable runtime for distributed primitives.
This article was originally published on InfoQ here .
翻译自: https://www.javacodegeeks.com/2020/06/multi-runtime-microservices-architecture.html