本系列文章的第一部分探讨了微服务、无服务器和容器化等技术趋势的架构演变和战略性架构模式。在技术平台发生重大变化时,遵循松耦合、可扩展、基于接口设计等原则的架构会更有弹性。设计良好的解决方案会将业务逻辑与技术组件(会随着时间推移而变得过时)隔离开来,经得起时间的考验。
第二部分重点探讨架构稳定性缺陷和反模式(如分布式单体、“死星架构”),以此说明平衡架构和技术稳定性的必要性。
第三部分探讨微服务架构中服务交互的重要性,分布式系统的典型挑战,以及像服务编排和服务网格这样的先进架构模式如何帮助我们克服这些挑战。
微服务架构带来了不少好处,但也带来了一些挑战。我们会将这些挑战视为架构机会,探讨如何通过服务编排来克服它们。
本文讨论的设计模式适用于像 AWS、VMware Tanzu Application Service(之前的 Pivotal Cloud Foundry)或 Kubernetes 这样的云平台。但是,你也可以把它们用在非云基础设施里。
在不少组织中,Kubernetes 已经成为事实上的云平台。服务网格框架提供了一种让我们得以在云上充分利用 Kubernetes 的卓越方法,不管你现在已经在使用 Kubernetes,还是计划将来对它做些研究。本文将介绍服务网格如何帮助我们管理生产环境的服务部署。
首先看下我们(作为程序员或架构师)在分布式应用程序架构中所遇到的挑战。
挑战 1:筒仓式开发
下面是一个分布式系统的例子,说明了在基于服务的架构和云平台出现之前传统应用程序面临的挑战。
图 1、筒仓式开发示例
这个例子中有两个客户端应用程序(App 1 和 App2)和三个业务服务(Service 1、Service 2 和 Service 3)。App 1 和 Service 1 以及 Service 2 交互。App 2 只和 Service 2 交互。Service 2 还会与 Service 3 交互。
让我们仔细分析下 App 1,看看这个应用程序里发生了什么。
这个例子说明了微服务出现之前典型的应用程序开发和部署方式,即使是在今天,有些应用程序仍然在使用这种设计。
图 2、应用程序和后台的服务功能
上面的示例应用程序包含了关键的业务逻辑,也混合了非业务逻辑任务,所有功能都嵌在了应用程序代码里:
- 非功能性的:验证、授权、通知等。
- 常见的平台任务:服务路由、服务发现、服务重试 / 断路器、跟踪等。
通常,非功能性需求被硬编码到应用程序中,与业务逻辑搅合在一起,尽管那是诸多应用程序的共同关注点。
这个例子显示了一个传统的应用程序包含多少真正的业务代码,相比之下,非功能性任务应该在应用程序之外处理,并供多个业务应用使用。
8 个功能中仅有 1 个是应用特有的逻辑,其他 7 个功能不应该和应用程序逻辑耦合在一起。
如果仔细看下其他的应用程序,很可能也会发现同样的问题。
从 Service 1、2、3 中可以看到类似的模式。每个服务的业务逻辑都混合了非功能性的平台任务,和我们前面看到的一样,它们不应该包含在服务里。我们看到了一些共有的非功能性服务,如身份验证、授权、客户通知等。还有一些所有业务应用程序共有的平台服务,如路由、服务发现、服务监控和跟踪,这些服务托管在云平台上。当我们仔细查看每个组件时,这个包含 2 个应用、3 个服务的分布式系统的简单例子似乎看上去就不那么简单了,如下所示:
图 3、分布式系统示例,这些应用和服务之间有一些共有的重复的功能
所有的应用程序和服务都包含了所有的非功能代码。这种设计存在很多缺点。
在这些应用程序和服务中,同样的功能有大量重复的实现,导致应用程序开发周期变长(上市时间),而且维护成本呈指数增长。
这些共有的功能嵌入到了每个应用和服务中,每个功能都使用了特有的技术和框架,如分别使用 Spring Cloud Gateway 和 Zipkin 或 Jaeger 实现路由和跟踪。底层技术的任何升级都需要修改应用程序,重新构建和部署,导致停机和中断。
由于存在这些挑战,分布式系统变得复杂。这些应用程序需要重新设计和重构,以避免筒仓式开发和一次性解决方案的泛滥。
由于网络越来越稳定和可靠,可以开始将“进程内”调用(如图 3 所示)转换为“通过网络”通信了。
经过重新设计后,借助“公共服务”这一设计模式,这些复杂的系统可以使用共有的功能,但又不必将它们嵌入到每个应用程序和服务中。
公共服务
对于嵌入在每个应用程序里的代码,我们的处理方式是将每个公共功能封装到自己的服务里,并托管在云中一个中央服务器(也许是 VM)上或容器中。
图 4、封装在独立微服务中的公共功能
当应用需要执行公共功能时,客户端应用程序就会调用这些远程服务。如图 4 所示,公共逻辑不再嵌入到每个应用程序或业务服务中。
公共服务应该是无状态的,最好是按照 12 要素应用最佳实践开发或重构,这样就可以在消费者应用程序中重用,提升其价值。
我们可以使用开源框架来开发这些公共服务,如用于 Java 应用程序的 Spring Boot 和 Spring Cloud,用于 ASP.NET 应用程序的 Asp.Net Core中间件。在理想情况下,这些服务都是 12 要素应用程序,都可以比较容易地部署到任何云平台上。在生产环境中管理和监控也比较容易。
这种架构有若干优点:
- 开发速度更快,交付时间更短,这也意味着上市时间更短。
- 单个部署,独立于其他应用程序和服务,因此,组件之间的依赖性较小。
- 可以对每个服务进行扩展。
- 天然符合安全和技术标准。
- 更小、更快、更简单的在线维护让我们不必在升级共享功能时重新构建和部署所有应用程序和服务。
- 长期来看,技术债务更少。
这些公共服务可以托管在云平台上(如 AWS、Azure、Kubernetes 或 VMWare Tanzu Application Service,之前叫 Pivotal Cloud Foundry)。这些平台对自动扩展、监控、快捷部署等特性提供了良好的支持。
然而,公共服务只是云原生之旅的过渡架构,并非最终目标。即便如此,基于公共服务的架构也带来了不少好处,当然也带来了一些挑战,包括紧耦合的服务交互,以及在路由、发现、断路器策略方面缺少中心化的策略执行机制。
关于那些新出现的挑战,本文稍后会专门介绍。
随着微服务在组织里的采用和推广,客户端应用程序和业务服务以及服务与服务之间的通信变得非常重要。如果处理不好这种通信复杂性,可能会导致服务性能退化以及系统可用性问题。
现在,让我们更详细地看下应用和服务之间的通信挑战。
挑战 2:应用与服务以及服务与服务之间的通信
随着我们将大型应用程序分解成细粒度的服务,部署的组件总量在增加,这使得这些组件之间的交互变得越来越复杂。图 5 说明了这种复杂性。
图 5、应用程序与服务之间通信的挑战
即使是对于从每个客户端应用程序中提取出来单独部署的公共功能,服务之间的相互依赖以及服务之间的调用方式对于这种架构也是一个很大的威胁。
让我们扩展下前面的例子,增加一些业务服务,看看对服务通信有什么影响。
从图 5 服务之间的通信可以看出,不同的服务之间仍然是紧耦合的,当整个系统变慢或是出现中断时,还是很难准确定位哪个服务有问题。
我们可能会想,其他采用微服务的公司同样也会面临交互模型复杂这样的挑战。他们确实遇到了。像 Netflix、Amazon、Twitter 这些公司都经历过同样的挑战,原因是任何应用程序或服务调用其他的服务时缺少一个有效的服务通信模型或治理流程。正如本系列文章第二部分 指出的那样,这种架构挑战在行业里相当普遍,它被定义成一个反模式“死星架构“。
这些公司是通过服务编排来克服这一架构挑战的。
服务编排
图 6、利用服务编排改进服务间通信
在图 6 所示的服务编排模型中,我们对于公共服务的处理还是像之前的架构一样,在客户端应用程序之外,它们会单独部署,有自己的生命周期和扩展性要求。
最主要的改进是,我们将路由服务移到了所有公共服务的前面。
客户端应用程序应该只调用路由服务。路由服务将根据用例以及来自客户端应用程序的请求的上下文信息,按照预定义的顺序调用一个或多个公共服务和应用服务。
该架构有许多好处:
- 首先,客户端应用程序和公共服务之间是松耦合的,可提供灵活的流量管理和中心化的策略执行机制。
- 这些策略可以是安全相关的,如身份验证和授权,可以是 SLA 相关的,如服务重试和断路器规则,还可以是可观测性和监控相关的。
- 该架构提供系统的端到端监控。
该架构还为其中不同部分(客户端应用程序、后端公共服务或者是路由器本身)的交互提供了很大的灵活性:
- 客户端应用程序可以是 Web 应用程序、移动 App、IoT 设备或其他服务。
- 后端服务可以是单体应用、微服务或无服务器函数。
- 路由服务可以用于提供不同的能力,如路由 / 分流、生产应用零停机的金丝雀部署。
- 客户端和服务之间的通信可以使用基于请求 / 响应机制的事务性同步方式,也可以使用基于消息发布 / 订阅的异步方式。
在服务编排架构中,消费者应用程序团队只需要关注用户界面和应用程序特有的服务,保护业务逻辑和 IP 不受技术变化和公共基础能力(非应用程序特有的能力)的影响。所有的公共服务——不管是业务服务,非功能性服务,还是平台服务——都托管在云平台上供路由服务调用,路由服务充当了服务编排器的角色。
如图 7 所示,公共服务中使用的所有技术和框架完全是从消费者应用程序中抽象出来的。
技术抽象
图 7、基于服务编排的解决方案从客户端应用程序中抽象出来的技术
我们来看下服务编排如何从客户端应用程序中把技术抽象出来。
使用图 7 所示的中心化服务编排器,客户端应用程序不需要知道这些技术中的任何一项。此外,这些技术中的任何一项升级都不会影响客户端应用。
与本文迄今为止所讨论的基于服务的架构类似,虽然服务编排架构看上去同样不错,但它也面临着一些挑战:
- 路由服务会成为一个单点故障。
- 会有一些性能开销,因为路由服务需要通过网络调用场景中涉及的每个服务。
- 没有服务的本地调用。
- 没有去中心化的策略执行机制。
截至目前,我们前面讨论的 3 种不同的架构(传统的分布式系统、基于微服务的架构和基于服务编排的应用程序)都面临一些挑战。现在,让我们探讨下两种新兴的云原生设计模式:服务网格和挎斗(sidecar)。
服务网格和挎斗
在本文最后,我们将探讨基于服务网格和挎斗设计模式的架构模型。Kubernetes 对挎斗模式提供了开箱即用的支持,所以这个模型很适合 Kubernetes 平台。
该架构模型如图 8 所示,和基于服务编排的解决方案一样,其中仍有一个名为“控制平面”的中心化组件,定义和管理不同的策略。
挎斗容器(数据平面的一部分)会在运行时自动注入业务服务。这些挎斗代理会执行在控制平面中定义的策略,并将其复制到数据平面。
基于服务网格的解决方案有助于改善分布式系统架构的安全性、可观测性和流量管理功能。
基于服务网格的解决方案所遵循的基本原则是:集中式的策略管理,非集中式的策略执行(两全其美)。
图 8、面向 Kubernetes 托管应用的服务网格和挎斗
服务网格功能
如下所示,与传统的微服务架构相比,基于服务网格的解决方案在连接性、可靠性、安全性和可观测性方面提供了诸多好处。
连接性:
- 流量控制(路由、分流)
- 网关(入口、出口)
- 服务发现
- A/B 测试、金丝雀
- 服务超时、重试
可靠性:
- 断路器
- 故障注入 / 混沌测试
安全性:
- 服务间身份验证(mTLS)
- 证书管理
- 用户身份验证(JSON Web Tokens)
- 用户授权(基于角色的访问控制)
- 数据加密
可观测性:
- 监控
- 遥测、仪表盘、指标
- 分布式跟踪
- 服务图
近年来,服务网格技术吸引了许多人的注意,已经有一些实现,如 Istio、Linkerd、Consul Connect 等。由于本文的重点是探讨成功的微服务架构所采用的架构模式,所以我们没有深入探讨服务网格的特性和实现。
如果想了解更多关于服务网格技术的内容,请阅读 InfoQ 迷你书《服务网格终极指南》。
总结
微服务之间的交互和通信有多种不同的实现方式。服务编排可以通过将 API 网关作为架构核心组件来实现。如果我们需要的能力超出了 API 网关的范围,那么我们可以使用服务网格和挎斗来应对那些额外的云原生架构需求。
在云原生应用程序架构中,一件很重要的事情是设计层次之间的交互,包括如何在建模工作中把数据、服务和事件作为一等公民。
在本系列文章的第 4 部分中,我们将讨论最后一部分内容:云原生 DevOps。我们将看下 DevOps 实践(CI/CD、容器华、Kubernetes 云平台、微服务和服务编排模式)如何帮助组织上云。
本文转自 公众号:infoQ ,作者Srini Penchikala、Marcio Esteves,点击阅读原文