跳转到主要内容

主要收获

  1. 创建分布式系统并非易事。围绕“微服务”架构和“12因素应用程序”设计出现了最佳实践。这些提供了与交付生命周期、网络、状态管理以及与外部依赖关系的绑定相关的指导方针。
  2. 然而,以可扩展和可维护的方法一致地实现这些原则是具有挑战性的。
  3. 解决这些原则的传统技术方法包括企业服务总线(ESB)和面向消息中间件(MOM)。虽然这些解决方案提供了一个很好的功能集,但主要的挑战是业务逻辑和平台之间的整体架构和紧密的技术耦合。
  4. 随着云、容器和容器编排器(Kubernetes)的流行,解决这些原则的新解决方案也出现了。例如,Knative用于交付,服务网格用于联网,Camel-K用于绑定和集成
  5. 使用这种方法,业务逻辑(称为“微逻辑”)形成了应用程序的核心,可以创建sidecar“meca”组件,这些组件提供强大的开箱即用分布式原语。
  6. 这种微逻辑和Mecha组件的解耦可能会改善第二天的操作,如修补和升级,并有助于维持业务逻辑的内聚单元的长期可维护性。

创建良好的分布式应用程序并非易事:此类系统通常遵循12因素应用程序和微服务原则。它们必须是无状态的、可扩展的、可配置的、独立发布的、容器化的、自动化的,有时是事件驱动的和无服务器的。一旦创建,它们应该易于升级,并且可以长期维护。在这些相互竞争的需求与当今的技术之间找到良好的平衡仍然是一项艰巨的努力。

在本文中,我将探讨分布式平台是如何发展以实现这种平衡的,更重要的是,在分布式系统的发展过程中还需要做些什么来简化可维护的分布式架构的创建。如果你想看到我现场谈论这个话题,请加入我三月在伦敦的QCon。

分布式应用程序需求

在这次讨论中,我将把现代分布式应用程序的需求分为四类——生命周期、网络、状态、绑定——并简要分析它们近年来的发展情况。

Distributed application needs

生命周期

让我们从基础开始。当我们编写一个功能时,编程语言规定了生态系统中可用的库、打包格式和运行时。例如,Java使用.jar格式,所有Maven依赖项作为一个生态系统,JVM作为运行时。如今,随着发布周期的加快,生命周期中更重要的是能够以自动化的方式部署、从错误中恢复和扩展服务。这组功能广泛代表了我们的应用程序生命周期需求。

网络

在某种意义上,今天几乎每个应用程序都是分布式应用程序,因此需要联网。但现代分布式系统需要从更广泛的角度来掌握网络。从服务发现和错误恢复开始,实现现代软件发布技术以及各种跟踪和遥测。出于我们的目的,我们甚至将在这一类别中包括不同的消息交换模式、点对点和发布/订阅方法以及智能路由机制。

状态

当我们谈论状态时,通常是关于服务状态以及为什么最好是无状态。但管理我们服务的平台本身需要状态。这是进行可靠的服务编排和工作流、分布式单例、临时调度(cron作业)、幂等性、有状态错误恢复、缓存等所必需的。这里列出的所有功能都依赖于隐藏状态。虽然实际的状态管理不是本文的范围,但分布式原语及其依赖于状态的抽象是令人感兴趣的。

结合(绑定)

分布式系统的组件不仅要相互通信,还要与现代或遗留的外部系统集成。这需要连接器能够转换各种协议,支持不同的消息交换模式,如轮询、事件驱动、请求/回复、转换消息格式,甚至能够执行自定义的错误恢复过程和安全机制。

在不涉及一次性用例的情况下,以上内容代表了创建良好分布式系统所需的公共原语的良好集合。如今,许多平台都提供了这样的功能,但我们在本文中要寻找的是,我们使用这些功能的方式在过去十年中发生了怎样的变化,以及下一个十年的情况。为了进行比较,让我们看看过去十年,看看基于Java的中间件是如何满足这些需求的。

传统中间件的局限性

满足上述老一代需求的著名传统解决方案之一是企业服务总线(ESB)及其变体,如面向消息的中间件、较轻的集成框架等。ESB是一种中间件,使用面向服务的架构(即经典SOA)实现异构环境之间的互操作性。

虽然ESB可以为您提供一个很好的功能集,但ESB的主要挑战是业务逻辑和平台之间的整体架构和紧密的技术耦合,这导致了技术和组织的集中化。当一个服务被开发并部署到这样的系统中时,它与分布式系统框架深度耦合,这反过来限制了服务的发展。这通常只有在软件生命的后期才变得明显。

以下是每一类需求的一些问题和限制,这些问题和限制使ESB在现代没有用处。

生命周期

在传统中间件中,通常有一个受支持的语言运行时(如Java),它规定了软件的打包方式、可用的库、修补的频率等。业务服务必须使用这些库,这些库将其与用同一语言编写的平台紧密耦合。在实践中,这导致了协调服务和平台升级,从而阻止了独立和定期的服务和平台发布。

网络

虽然传统的中间件有一个高级功能集,主要集中在与其他内部和外部服务的交互上,但它也有一些主要缺点。网络功能以一种主要语言及其相关技术为中心。对于Java语言,即JMS、JDBC、JTA等。更重要的是,网络问题和语义也深深地刻在了业务服务中。有些库使用抽象来处理网络问题(例如曾经流行的Hystrix项目),但库的抽象“泄漏”到服务中——它的编程模型、交换模式和错误处理语义,以及库本身。虽然在一个位置编码和阅读混合了网络方面的整个业务逻辑很方便,但这将两个问题紧密地耦合到一个单独的实现中,并最终形成一个联合的进化路径。

状态

为了进行可靠的服务编排、业务流程管理和实现模式,如Saga模式和其他缓慢运行的流程,平台需要在后台保持持久状态。类似地,临时操作(如启动计时器和cron作业)是建立在状态之上的,需要数据库在分布式环境中集群化并具有弹性。这里的主要约束是,与状态交互的库和接口没有完全抽象,也没有与服务运行时解耦。通常,这些库必须使用数据库详细信息进行配置,并且它们位于将语义和依赖关系泄漏到应用程序域的服务中。

结合

使用集成中间件的主要驱动因素之一是能够使用不同的协议、数据格式和消息交换模式连接到各种其他系统。然而,这些连接器必须与应用程序一起使用,这意味着依赖关系必须与业务逻辑一起更新和修补。这意味着数据类型和数据格式必须在服务中来回转换。这意味着代码必须根据消息交换模式进行结构化和流设计。以下是一些示例,说明了即使是抽象端点也会如何影响传统中间件中的服务实现。

云原生趋势

传统的中间件功能强大。它具有所有必要的技术特征,但缺乏快速变化和扩展的能力,这是现代数字业务需求所要求的。这就是微服务架构及其设计现代分布式应用程序的指导原则正在解决的问题。

微服务背后的思想及其技术要求促成了容器和Kubernetes的普及和广泛使用。这开启了一种新的创新方式,将在未来几年影响我们处理分布式应用程序的方式。让我们看看Kubernetes和相关技术如何影响每一组需求。

生命周期

容器和Kubernetes将我们打包、分发和部署应用程序的方式发展为独立于语言的格式。有很多关于Kubernetes模式和Kubernete对开发人员的影响的文章,我将在这里简短地介绍一下。不过,请注意,对于Kubernetes来说,要管理的最小原语是容器,它专注于在容器级别和流程模型上交付分布式原语。这意味着它在管理应用程序的生命周期、健康检查、恢复、部署和扩展方面做得很好,但在容器内的分布式应用程序的其他方面(如灵活的网络、状态管理和绑定)改进得不太好。

您可能会指出,Kubernetes具有有状态工作负载、服务发现、cron作业和其他功能。的确如此,但所有这些基元都在容器级别,在容器内部,开发人员仍然必须使用特定于语言的库来访问我们在本文开头列出的更细粒度的功能。这就是Envoy、Linkerd、Consul、Knative、Dapr、Camel-K等项目的驱动力。

网络

事实证明,Kubernetes提供的围绕服务发现的基本网络功能是一个很好的基础,但对于现代应用程序来说还不够。随着微服务数量的增加和部署速度的加快,对更先进的发布策略的需求变得越来越有吸引力,这些策略包括在不接触服务的情况下管理安全、度量、跟踪、从错误中恢复、模拟错误等,并创造了一种新的软件类别,称为服务网格。

这里更令人兴奋的是,将与网络相关的关注点从包含业务逻辑的服务转移到外部,并转移到单独的运行时,无论是sidecar还是节点级代理。如今,服务网格可以进行高级路由、帮助测试、处理某些方面的安全,甚至可以使用特定于应用程序的协议(例如Envoy支持Kafka、MongoDB、Redis、MySQL等)。虽然服务网格作为一种解决方案可能还没有被广泛采用,但它触及了分布式系统中的一个真正的痛点,我相信它会找到自己的存在形式。

除了典型的服务机制外,还有其他项目,如Skupper,证实了将网络功能放入外部运行时代理的趋势。Skupper通过第7层虚拟网络解决了多集群通信挑战,并提供了高级路由和连接功能。但是,它并没有将Skupper嵌入到业务服务运行时,而是在每个Kubernetes命名空间中运行一个实例,该实例充当共享的sidecar。

总之,容器和Kubernetes在应用程序的生命周期管理方面迈出了重要的一步。服务网格和相关技术触及了真正的痛点,并为将应用程序之外的更多职责转移到代理中奠定了基础。让我们看看接下来会发生什么。

状态

我们前面列出了依赖于状态的主要集成原语。管理状态很困难,应该委托给专门的存储软件和托管服务。这不是这里的主题,但在语言无关的抽象中使用状态来帮助集成用例才是。今天,许多努力试图在语言无关抽象背后提供有状态的原语。有状态工作流管理是基于云的服务中的一项强制性功能,例如AWS Step Functions、Azure Durable Functions等。在基于容器的部署中,CloudState和Dapr都依赖于sidecar模型来更好地解耦分布式应用程序中的有状态抽象。

我所期待的也是将上面列出的所有有状态特性抽象到一个单独的运行时中。这意味着工作流管理、单重、幂等性、事务管理、cron作业触发器和有状态错误处理都可靠地发生在sidecar(或主机级代理)中,而不是生活在服务中。业务逻辑不需要在应用程序中包括这样的依赖关系和语义,并且它可以声明性地从绑定环境请求这样的行为。例如,sidecar可以充当cron作业触发器、幂等消费者和工作流管理器,自定义业务逻辑可以作为回调调用,也可以在工作流、错误处理、临时调用或唯一幂等请求的某些阶段插入。

另一个有状态的用例是缓存。无论是由服务网格层执行的请求缓存,还是使用Infinispan、Redis、Hazelcast等进行的数据缓存,都有将缓存功能推出应用程序运行时的例子。

结合

虽然我们讨论的主题是将所有分布式需求与应用程序运行时解耦,但绑定的趋势也在继续。连接器、协议转换、消息转换、错误处理和安全中介都可能移出服务运行时。我们还没有达到目标,但Knative和Dapr等项目正在朝着这个方向进行尝试。将所有这些职责从应用程序运行时中移出将导致更小的、以业务逻辑为中心的代码。这样的代码将生活在一个独立于分布式系统需求的运行时中,这些需求可以作为预打包功能来使用。

Apache Camel-K项目采用了另一种有趣的方法。该项目不是使用代理运行时来伴随主应用程序,而是依赖于一个智能的Kubernetes Operator,该Operator使用Kubernete和Knative的额外平台功能构建应用程序运行时。这里,单个代理是负责包含应用程序所需的分布式系统原语的操作员。不同之处在于,一些分布式原语被添加到应用程序运行时,而一些则在平台中启用(平台也可能包括sidecar)。

未来架构趋势

从广义上看,我们可以得出结论,通过将功能转移到平台级别,分布式应用程序的商品化达到了新的前沿。除了生命周期之外,现在我们还可以观察到网络、状态抽象、声明性事件和端点绑定,这些都是现成的,EIP是下一个。有趣的是,商品化是使用进程外模型(sidecars)进行功能扩展,而不是运行库或纯平台功能(如新的Kubernetes功能)。

我们现在正通过将所有传统中间件功能(也称为ESB)转移到其他运行时来接近完整的循环,很快,我们在服务中所要做的就是编写业务逻辑。

传统中间件平台和云原生平台概述

与传统的ESB时代相比,这种架构更好地将业务逻辑与平台解耦,但尚未完全解耦。许多分布式原语,如经典的企业集成模式(EIP):拆分器、聚合器、过滤器、基于内容的路由器;以及流处理模式:映射、过滤、折叠、连接、合并、滑动窗口;仍然必须包含在业务逻辑运行时中,并且许多其他依赖于多个不同且重叠的平台附加组件。

如果我们把在不同领域创新的各种云原生项目叠加起来,我们最终会得到如下图片:

多运行时微服务

这里的图表仅用于说明目的,它有目的地选择有代表性的项目,并将它们映射到一类分布式原语中。在实践中,您不会同时使用所有这些项目,因为其中一些项目是重叠的且不兼容的工作负载模型。如何解释这个图表?

Kubernetes和容器在多语言应用程序的生命周期管理方面实现了巨大的飞跃,并为未来的创新奠定了基础。

服务网格技术在Kubernetes上进行了改进,具有先进的网络功能,并开始利用应用程序问题。

虽然Knative主要通过快速扩展专注于无服务器工作负载,但它也解决了服务编排和事件驱动的绑定需求。

Dapr以Kubernetes、Knative和Service Mesh的思想为基础,深入应用程序运行时,以解决有状态工作负载、绑定和集成需求,充当现代分布式中间件。

这个图表可以帮助您直观地看到,在未来,我们很可能会使用多个运行时来实现分布式系统。多个运行时,不是因为多个微服务,而是因为每个微服务都将由多个运行时间组成,很可能是两个——自定义业务逻辑运行时间和分布式原语运行时间。

引入多运行时微服务

以下是开始形成的多运行时微服务架构的简要描述。

你还记得电影《阿凡达》和科学家们为探索潘多拉而开发的“机械套装”吗?这种多运行时架构类似于这些赋予人形驾驶员超能力的机械套装。在电影中,人类穿上西装以获得力量并获得破坏性武器。在这个软件架构中,您的业务逻辑(称为微逻辑)构成了应用程序的核心,sidecar meca组件提供了强大的开箱即用分布式原语。微逻辑与机械功能相结合,形成了一个多运行时微服务,该服务使用进程外功能来满足其分布式系统的需求。最棒的是,《阿凡达2》即将问世,以帮助推广这一架构。我们终于可以在所有软件会议上用令人敬畏的机甲图片取代老式的边车摩托车了;-)。接下来让我们看一下这个软件架构的细节。

这是一个类似于客户端-服务器架构的双组件模型,其中每个组件都是独立的运行时。它与纯客户端-服务器架构的不同之处在于,在这里,两个组件都位于同一台主机上,它们之间有可靠的网络连接,这不是一个问题。这两个组件的重要性相同,它们可以在任一方向启动操作,并充当客户端或服务器。其中一个组件被称为Micrologic,它包含了几乎所有分布式系统关注的最小业务逻辑。另一个附带的组件是Mecha,它提供了我们在文章中讨论的所有分布式系统功能(除了作为平台功能的生命周期)。

多运行时(进程外)微服务架构

Micrologic和Mecha可能有一对一的部署(称为侧车模型),也可以是一个共享的Mecha和几个Micrologic运行时。第一种模型最适用于环境,如Kubernetes,后者适用于边缘部署。

Micrologic运行时特性

让我们简要探讨一下Micrologic运行时的一些特性:

  • Micrologic组件本身并不是一个微服务。它包含微服务所具有的业务逻辑,但该逻辑只能与Mecha组件结合使用。另一方面,微服务是自包含的,不具有分散到其他运行时的整体功能或部分处理流。Micrologic和它的机械对应物的结合形成了一个微服务。
  • 这也不是一个功能或无服务器架构。无服务器主要以其管理式快速扩展和零扩展功能而闻名。在无服务器架构中,函数实现单个操作,因为这是可伸缩性的单位。在这方面,函数不同于实现多个操作的Micrologic,但实现不是端到端的。最重要的是,操作的实现分布在Mecha和Micrologic运行时。
  • 这是客户端-服务器架构的一种特殊形式,针对众所周知的分布式原语的使用进行了优化,而无需编码。此外,如果我们假设Mecha扮演服务器角色,那么必须将每个实例专门配置为与单个客户端一起工作。它不是一个通用的服务器实例,旨在与典型的客户端-服务器架构同时支持多个客户端。
  • 微逻辑中的用户代码不与其他系统直接交互,也不实现任何分布式系统原语。它通过事实上的标准(如HTTP/gRPC、CloudEvents规范)与Mecha进行交互,Mecha使用丰富的功能并在配置的步骤和机制的指导下与其他系统进行通信。
  • 虽然Micrologic只负责实现从分布式系统中剥离出来的业务逻辑,但它仍然必须至少实现一些API。它必须允许Mecha和平台通过预定义的API和协议与其交互(例如,通过遵循Kubernetes部署的云原生设计原则)。

Mecha运行时特性

以下是Mecha运行时的一些特性:

  1. Mecha是一种通用的、高度可配置的、可重复使用的组件,提供分布式原语作为现成的功能。
  2. Mecha的每个实例都必须配置为与一个Micrologic组件(侧车模型)一起工作,或者配置为与几个组件共享。
  3. Mecha没有对Micrologic运行时做出任何假设。它使用开放协议和格式(如HTTP/gRPC、JSON、Protobuf、CloudEvents)与多语言微服务甚至单片系统协同工作。
  4. Mecha是用简单的文本格式(如YAML、JSON)以声明方式配置的,这些格式规定了要启用哪些功能以及如何将它们绑定到Micrologic端点。对于专门的API交互,Mechan可以额外提供规范,如OpenAPI、AsyncAPI、ANSI-SQL等。对于由多个处理步骤组成的有状态工作流,可以使用规范,如Amazon State Language。对于无状态集成,企业集成模式(EIP)可以与类似于Camel-K YAML DSL的方法一起使用。这里的关键点是,所有这些都是简单的、基于文本的、声明性的、多语言的定义,Meca可以在不编码的情况下完成这些定义。请注意,这些都是未来的预测,目前还没有用于有状态编排或EIP的机制,但我预计现有的机制(Envoy、Dapr、Cloudstate等)很快就会开始添加此类功能。Mecha是一个应用级分布式原语抽象层。
  5. 与其依赖多个代理用于不同的目的,例如网络代理、缓存代理、绑定代理,不如由一个Mecha提供所有这些功能。一些功能的实现,如存储、消息持久性、缓存等,将由其他云或内部部署服务插入并支持。
  • 一些围绕生命周期管理的分布式系统关注点由管理平台(如Kubernetes或其他云服务)提供,而不是由使用通用开放规范(如开放应用程序模型)的Mecha运行时提供。

这种架构的主要优点是什么?

好处是业务逻辑和日益增多的分布式系统关注点之间的松散耦合。软件系统的这两个元素具有完全不同的动态特性。业务逻辑始终是唯一的、自定义的代码,由内部编写。它经常变化,这取决于您的组织优先级和执行能力。另一方面,分布式原语是解决本文中列出的问题的原语,它们是众所周知的。这些由软件供应商开发,并作为库、容器或服务使用。这些代码会根据供应商的优先级、发布周期、安全补丁、开源管理规则等而变化。这两个组对彼此几乎没有可见性和控制权。

业务逻辑和分布式系统关注不同架构中的耦合

微服务原则有助于通过有限的上下文将不同的业务领域解耦,在有限的上下文中,每个微服务都可以独立发展。但微服务架构并没有解决将业务逻辑与中间件问题耦合所带来的困难。对于某些轻集成用例的微服务来说,这可能不是一个大因素。但是,如果您的领域涉及复杂的集成(这对每个人来说都越来越普遍),遵循微服务原则将无法帮助您避免与中间件耦合。即使中间件被表示为包含在微服务中的库,当您开始迁移和更改这些库时,这种耦合也会变得明显。您需要的分布式原语越多,就越能耦合到集成平台中。将中间件作为预定义API上的单独运行时/进程而不是库使用有助于松耦合,并支持每个组件的独立演化。

这也是为供应商分发和维护复杂中间件软件的更好方法。只要与中间件的交互是通过涉及开放API和标准的进程间通信进行的,软件供应商就可以按照自己的速度免费发布补丁和升级。消费者可以自由使用他们喜欢的语言、库、运行时、部署方法和流程。

这种架构的主要缺点是什么?

进程间通信。分布式系统的业务逻辑和中间件机制(您可以看到名称的来源)处于不同的运行时,这需要HTTP或gRPC调用,而不是进程内方法调用。不过,请注意,这不是一个应该转到不同机器或数据中心的网络呼叫。Micrologic运行时和Mecha应该位于同一台主机上,具有低延迟和最小的网络问题可能性。

复杂性下一个问题是,它是否值得开发的复杂性,以及为了获得的利益而维护这样的系统。我认为答案会越来越倾向于肯定。分布式系统的需求和发布周期的速度都在增加。这个架构为此进行了优化。我前段时间写道,未来的开发人员必须具备混合开发技能。这种架构进一步证实并强化了这一趋势。应用程序的一部分将用更高级的编程语言编写,部分功能将由必须声明配置的现成组件提供。这两个部分不是在编译时相互连接,也不是在启动时通过进程内依赖注入相互连接,而是在部署时通过进程间通信相互连接。该模型实现了更高的软件重用率和更快的变化速度。

微服务之后不是服务(FaaS)

微服务架构有一个明确的目标。它针对变化进行优化。通过将应用程序划分为业务域,该架构通过解耦、由独立团队管理并以独立的速度发布的服务,为软件进化和可维护性提供了最佳的服务边界。

如果我们看一下无服务器架构的编程模型,它主要是基于函数的。功能针对可扩展性进行了优化。通过功能,我们将每个操作拆分为一个独立的组件,以便它能够快速、独立和按需扩展。在这个模型中,部署粒度是一个函数。选择该函数是因为它是具有输入的代码结构,其速率与缩放行为直接相关。这是一种针对极端可扩展性而优化的架构,而不是复杂系统的长期可维护性。

Serverless的另一个方面是什么?这源于AWS Lambda的流行及其完全管理的操作性质?在这方面,“AWS Serverless”以缺乏控制和锁定为代价,优化了供应速度。但完全管理的方面不是应用程序架构,而是软件消费模型。它是一个正交的功能,类似于消费一个基于SaaS的平台,在理想的世界中,该平台应该适用于任何类型的架构,无论是单片、微服务、机制还是功能。在许多方面,AWS Lambda类似于一个完全管理的Mecha架构,但有一个很大的区别:Mecha不强制执行功能模型,而是允许围绕业务领域构建更具凝聚力的代码,与所有中间件无关。

架构优化

另一方面,Mecha架构优化了微服务以实现中间件的独立性。虽然微服务是相互独立的,但它们在很大程度上依赖于嵌入式分布式原语。Mecha架构将这两个关注点划分为单独的运行时,允许独立团队独立发布。这种解耦改进了第二天的操作(如修补和升级)以及业务逻辑的内聚单元的长期可维护性。在这方面,Mecha架构是微服务架构的自然发展,基于引起最大摩擦的边界拆分软件。与功能模型相比,这种优化以软件重用和进化的形式提供了更多的好处,功能模型以过度分发代码为代价,优化了极大的可扩展性。

结论

分布式应用程序有许多要求。创建有效的分布式系统需要多种技术和良好的集成方法。虽然传统的单片中间件提供了分布式系统所需的所有必要技术功能,但它缺乏业务所需的快速更改、调整和扩展能力。这就是为什么基于微服务的架构背后的思想促成了容器和Kubernetes的快速普及;随着云原生空间的最新发展,我们现在正通过将所有传统中间件功能转移到平台和现成的辅助运行时中来实现完整的循环。

这种应用程序特性的商品化主要是使用进程外模型进行特性扩展,而不是使用运行库或纯平台特性。这意味着在未来,我们很可能会使用多个运行时来实现分布式系统。多个运行时,不是因为多个微服务,而是因为每个微服务都会由多个运行时间组成;用于自定义微业务逻辑的运行时,以及用于分布式原语的现成的可配置运行时。

本文地址

知识星球

微信公众号

视频号