https://www.infoq.com/articles/multi-runtime-microservice-architecture/
我将把现代分布式应用的需求分为四种类型(生命周期,网络,状态,绑定)并简要分析它们在最近几年中的发展情况。
Mecha 这个词之所以出现在这里,主要是因为 Bilgin Ibryam 的这个博客文章 “Multi-Runtime Microservices Architecture”
https://developer.aliyun.com/article/761321
https://www.infoq.com/articles/multi-runtime-microservice-architecture/?spm=a2c6h.12873639.0.0.39c0741baw4ZeW
在介绍完 Mecha/Multiple Runtime 的理念之后,我们来看看目前微软新推出来的 Dapr 项目 —— 这应该是业界第一个 Multiple Runtime 的开源实践项目。
Dapr 的详细介绍是:
Dapr 是一种可移植的、Serverless 的、事件驱动的运行时,它使开发人员可以轻松构建弹性,无状态和有状态微服务,这些服务运行在云和边缘上,并包含多种语言和开发框架。
Dapr 整理了构建微服务应用为开放,独立的构建块的最佳实践,使您能够使用自己选择的语言和框架来构建可移植的应用程序。 每个构建块都是独立的,您可以在应用中使用其中的一个或多个。
https://github.com/dapr/dapr?spm=a2c6h.12873639.0.0.39c0741baw4ZeW
https://asksendai.com/multi-runtime-microservices-architecture/
生命周期(Lifecycle)
让我们从基础开始。当我们编写功能时,编程语言会指定生态系统中的可用库,打包格式和运行时。例如,Java使用.jar格式,用作生态系统的所有Maven依赖项,还有JVM用作运行时。如今,随着发布周期的缩短,生命周期中更重要的是自动化能力:能够部署,从错误中恢复以及扩展服务。这组功能广泛地代表了我们的应用生命周期需求。
网络(Networking)
从某种意义上讲,今天几乎每个应用都是分布式应用,因此需要网络。但是现代分布式系统需要从更广泛的角度掌握网络。从服务发现和错误恢复开始,到启用现代软件发布技术,以及各种跟踪和遥测。为了我们的目的,我们甚至将不同的消息交换模式,点对点和发布/订阅方法,以及智能路由机制包括在此类别中。
状态(State)
当我们谈论状态时,通常是关于服务状态以及为什么最好是无状态的。但是管理我们服务的平台本身是需要状态的。这是执行可靠的服务编排和工作流,分布式单例,临时调度(cron作业),幂等,有状态错误恢复,缓存等所需的。这里列出的所有功能都依赖于底层的状态。尽管实际的状态管理不在本文讨论范围之内,但依赖状态的分布式原语及其抽象却令人关注。
捆绑(Binding)
分布式系统的组件不仅必须彼此对话,而且还必须与现代或旧式外部系统集成。这就要求连接器能够转换各种协议,支持不同的消息交换模式,例如轮询,事件驱动,请求/答复,转换消息格式,甚至能够执行自定义错误恢复过程和安全机制。
在不涉及一次性使用案例的情况下,以上内容代表了创建良好的分布式系统所需的通用原语的良好集合。今天,许多平台都提供了这样的功能,但是我们在本文中探寻的是过去十年中我们使用这些功能的方式是如何变化的,以及在下一个十年中它将如何变化。为了进行比较,让我们看一下过去的十年,看看基于Java的中间件如何满足这些需求。
传统中间件限制
在满足上述需求的上一代传统解决方案,众所周知的是企业服务总线(ESB)及其变体(例如面向消息的中间件,更轻量级的集成框架等)。ESB是一种中间件,可以使用面向服务的架构(即经典SOA)在异构环境之间实现互操作性。
虽然ESB将为您提供良好的功能集,但ESB的主要挑战是单体架构以及业务逻辑和平台之间紧密的技术耦合,从而导致了技术和组织的中心化。开发并部署服务到这样的系统中时,与分布式系统框架紧密耦合,从而限制了服务的发展。这通常只会在软件生命周期的后期才变得明显。
以下是每种需求的问题和局限性,这些导致ESB在现在不再有用。
生命周期(Lifecycle)
在传统的中间件中,通常只有一个受支持的语言运行时(例如Java),它规定了软件的打包方式,可用的库,需要修补的频率等。业务服务必须使用这些库。使其与以相同语言编写的平台紧密结合。实际上,这导致服务和平台升级被关联起来,从而阻止了独立和常规的服务和平台发布。
网络(Networking)
尽管传统中间件也具有围绕与其他内部和外部服务交互的高级功能集,但它有一些主要缺点。网络功能集中于一种主要语言及其相关技术。对于Java语言,即JMS,JDBC,JTA等。更重要的是,网络问题和语义也深深地刻在业务服务中。有一些具有抽象的库来处理网络问题(例如曾经很受欢迎的Hystrix项目),但是该库的抽象也“泄漏”到了服务的编程模型,交换模式和错误处理语义以及库本身中。虽然可以方便地在一个位置编写和读取与网络方面混合的整个业务逻辑,但是这将两个问题紧密地耦合到实现中,最终形成了共同的演进路径。
状态(State)
为了进行可靠的服务编排,业务流程管理以及实现模式(例如Saga模式和其他运行缓慢的流程),平台需要在幕后持久化状态。同样,临时动作(例如触发计时器和cron作业)建立在状态之上,并且需要在分布式环境中对数据库进行集群和恢复。这里的主要约束是以下事实:与状态交互的库和接口没有完全抽象出来,也没有与服务运行时解耦。通常,这些库必须配置有数据库详细信息,并且它们存在于服务中,从而将语义和依赖关系泄漏到应用域中。
绑定(Binding)
使用集成中间件的主要驱动力之一是能够使用不同的协议,数据格式和消息交换模式连接到其他各种系统。但是,这些连接器必须与应用程序一起使用,这意味着必须将依赖关系与业务逻辑一起更新和修补。这意味着必须在服务中来回转换数据类型和数据格式。这意味着必须根据消息交换模式来构造代码并设计流程。即使是抽象的端点也会影响传统中间件中的服务实现,有很多这方面的例子。
云原生趋势
传统的中间件功能强大。它具有所有必要的技术功能,但缺乏现代数字业务需求所要求的快速更改和扩展的能力。这就是微服务架构及其设计现代分布式应用的指导原则所要解决的问题。
微服务背后的思想及其技术要求促进了容器和Kubernetes的普及和广泛使用。这开始了一种新的创新方式,这种方式将影响我们今后几年处理分布式应用程序的方式。让我们看看Kubernetes和相关技术如何影响每种需求。
生命周期(Lifecycle)
容器和Kubernetes将打包,分发和部署应用的方式发展为与语言无关的格式。关于Kubernetes模式和Kubernetes影响开发人员的文章有很多,在这里我将简短介绍。但是请注意,对于Kubernetes,要管理的最小原语是容器,它专注于在容器级别和流程模型上交付分布式原语。这意味着它在管理应用的生命周期,健康检查,恢复,部署和扩展方面做得很出色,但是在容器内的分布式应用的其他方面却没有做得很好,例如灵活的网络,状态管理和绑定。
您可能会指出,Kubernetes具有有状态工作负载,服务发现,cron作业和其他功能。的确如此,但是所有这些原语都是在容器级别的,并且在容器内部,开发人员仍然必须使用特定于语言的库来访问我们在本文开头列出的更详细的功能。这就是推动诸如Envoy,Linkerd,Consul,Knative,Dapr,Camel-K等项目的原因。
网络(Networking)
事实证明,Kubernetes提供的围绕服务发现的基本网络功能是一个很好的基础,但对于现代应用来说还不够。随着微服务数量的增加和部署速度的加快,在不改动服务的情况下,对更高级的发布策略,管理安全性,指标,跟踪,从错误中恢复,模拟错误等等方面的需求变得越来越具有吸引力,并产生了一种新的软件类别,称为服务网格。
这里更令人兴奋的是,趋势是将与网络相关的问题从包含业务逻辑的服务中移出,放到单独的运行时(无论是sidecar还是节点级代理)。如今,服务网格可以执行高级路由,帮助测试,处理某些方面的安全性,甚至可以使用特定于应用的协议(例如,Envoy支持Kafka,MongoDB,Redis,MySQL等)。尽管服务网格作为一种解决方案可能尚未得到广泛采用,但它触及了分布式系统中的真正痛点,我相信它将找到其形状和存在形式。
除了典型的服务网格外,还有其他项目,例如Skupper,这些项目证实了将网络功能放入外部运行时代理的趋势。Skupper通过7层虚拟网络解决了多集群通信难题,并提供了高级的路由和连接能力。但是,它没有将Skupper嵌入到业务服务运行时中,而是在每个Kubernetes命名空间中运行一个实例,该实例充当共享的Sidecar。
综上所述,容器和Kubernetes在应用生命周期管理方面迈出了重要的一步。服务网格和相关技术遇到了真正的痛点,并为将更多职责从应用程序移到代理中奠定了基础。让我们看看下一步。
状态(State)
我们在前面列出了依赖状态的主要集成原语。管理状态非常困难,应将其委派给专门的存储软件和托管服务。这不是今天的主题,而是在语言无关的抽象中使用状态来帮助集成的用例。今天,许多努力试图在语言无关的抽象后面提供有状态的原语。有状态的工作流管理是基于云的服务中的强制性功能,例如AWS Step Functions,Azure Durable Functions等示例。在基于容器的部署中,CloudState和Dapr都依赖于sidecar模型来提供对分布式应用的状态抽象的更好的解耦。
我也期待将上面列出的所有有状态功能抽象到一个单独的运行时中。这意味着工作流管理,单例,幂等,事务管理,cron作业触发器和有状态错误处理都可靠地发生在Sidecar(或主机级代理)中,而不是存在于服务中。业务逻辑不需要在应用中包含此类依赖关系和语义,并且可以从绑定环境中声明性地请求此类行为。例如,Sidecar可以充当cron作业触发器,幂等消费者和工作流管理器,而自定义业务逻辑可以作为回调调用或作为某些阶段插入到工作流,错误处理,临时调用或唯一幂等需求中。
另一个有状态用例是缓存。无论是通过服务网格层执行请求缓存,还是使用Infinispan,Redis,Hazelcast等之类的数据缓存,都有一些将缓存功能推到应用运行时之外的示例。
绑定(Binding)
尽管我们的主题是将所有分布式需求与应用运行时解耦,但这种趋势也伴随着绑定。连接器,协议转换,消息转换,错误处理和安全中介都可以移出服务运行时。我们还没有到那里,但是在诸如Knative和Dapr之类的项目中已经在朝这个方向进行尝试。将所有这些职责移出应用运行时将导致更小,更专注于业务逻辑的代码。这样的代码将在运行时中运行,运行时独立于分布式系统需求,而分布式系统需求可以作为预先打包好的功能使用。
Apache Camel-K项目采用了另一种有趣的方法。该项目没有使用代理运行时来伴随主应用,而是依靠智能的Kubernetes Operator,用Operator来构建具有Kubernetes和Knative的附加平台功能的应用运行时。在这里,单个代理是Operator,负责包含应用所需的分布式系统原语。不同之处在于,某些分布式原语已添加到应用运行时中,而某些在平台中启用(也可能包括Sidecar)。
https://skyao.io/post/202003-multi-runtime-microservice-architecture/
https://searchapparchitecture.techtarget.com/tip/A-quick-rundown-of-multi-runtime-microservices-architecture
https://walkingtree.tech/introduction-multi-runtime-microservices-architecture/
https://www.pinterest.com/pin/771452611167980454/
https://chowdera.com/2021/04/20210401012423904d.html