微服务简介

NGINX | 2015年5月19日

Microservices March 2023现已开放注册。查看议程并在此注册。

编辑注:本系列共七篇文章,现已全部发布:

  1. 微服务简介(本文)
  2. 构建微服务:使用API网关
  3. 构建微服务:微服务架构中的进程间通信
  4. 微服务架构中的服务发现
  5. 微服务的事件驱动型数据管理
  6. 选择微服务部署策略
  7. 将单体应用重构为微服务

还可以下载电子书,其中收录了完整系列文章,以及使用NGINX Plus实施微服务的相关信息:《微服务:从设计到部署》。

另请查看我们的微服务解决方案页面。

微服务目前备受关注,相关文章、博客、社交媒体讨论和会议演讲层出不穷。微服务正在迅速接近Gartner技术成熟度曲线中的期望膨胀期峰值。与此同时,软件社区中也有一些质疑者,认为微服务并无新意。反对者声称,这一理念只是对SOA的重新包装。然而,尽管微服务既受到热捧也面临质疑,微服务架构模式仍具有显著优势,尤其有助于实现复杂企业应用的敏捷开发和交付。

本篇博文是微服务设计、构建和部署七篇系列文章中的第一篇。您将了解这种方法,以及它与更传统的单体架构模式有何不同。本系列将介绍微服务架构的各个要素。您将了解微服务架构模式的优势和不足、它是否适合您的项目,以及如何应用该模式。

先来看看为什么应考虑使用微服务。

构建单体应用

假设您要开始构建一款全新的网约车应用,与Uber和Hailo竞争。经过初步会议和需求收集后,可以手动创建新项目,也可以使用Rails、Spring Boot、Play或Maven附带的生成器。这个新应用将采用模块化的六边形架构,如下图所示:

作为示例微服务应用基础的模块化单体架构

应用的核心是业务逻辑,由定义服务、领域对象和事件的模块实现。核心外围是与外部环境交互的适配器,例如数据库访问组件、生成和使用消息的消息传递组件,以及公开API或实现UI的Web组件。

尽管应用在逻辑上采用模块化架构,但仍以单体形式打包和部署。实际格式取决于应用所使用的语言和框架。例如,许多Java应用会打包为WAR文件,并部署在Tomcat或Jetty等应用服务器上。其他Java应用则会打包为自包含的可执行JAR文件。同样,Rails和Node.js应用会打包为目录层次结构。

采用这种方式编写的应用程序非常常见。由于IDE和其他工具主要用于构建单个应用程序,因此这类应用程序开发起来很简单,也易于测试。只需启动应用程序并使用Selenium测试UI,即可实施端到端测试。单体应用程序也易于部署,只需将打包后的应用程序复制到服务器即可。还可以在负载均衡器后运行多个应用程序副本,以实现扩展。这种方式在项目早期阶段效果良好。

走向单体架构困境

遗憾的是,这种简单的方法存在很大的局限性。成功的应用往往会随着时间推移不断扩展,最终变得极其庞大。在每个冲刺周期中,开发团队都会再实现几个用户故事,这自然意味着要增加许多行代码。几年后,原本小巧、简单的应用将发展成一个庞大臃肿的单体应用。举个极端的例子,我最近与一位开发人员交流过。他当时正在编写一个工具,用于分析一个拥有数百万行代码(LOC)的应用中数千个JAR之间的依赖关系。可以想见,这样一个庞然大物是大量开发人员历经多年共同努力的结果。

一旦应用变成庞大而复杂的单体,开发组织很可能会陷入困境。任何敏捷开发和交付的尝试都将难以推进。一个主要问题是,应用复杂得令人难以应对。它规模过于庞大,任何一名开发人员都无法完全理解。因此,正确修复缺陷和实现新功能会变得困难且耗时。更糟的是,这往往会形成恶性循环。如果代码库难以理解,就很难正确进行更改。最终,应用会变成一个庞大、混乱且难以理解的大泥球。

应用规模过大也会拖慢开发进度。应用越大,启动时间就越长。例如,在最近的一项调查中,一些开发人员表示,应用启动时间长达12分钟。我还听说,有些应用甚至需要40分钟才能启动。如果开发人员经常需要重启应用服务器,他们每天将有大量时间耗费在等待上,工作效率也会受到影响。

大型复杂单体应用的另一个问题是会阻碍持续部署。如今,SaaS应用的先进实践是每天多次将变更部署到生产环境。对于复杂的单体应用,这极难实现,因为即使只更新其中一个部分,也必须重新部署整个应用。前面提到的漫长启动时间也会让情况更加棘手。此外,由于变更的影响通常难以充分掌握,很可能需要进行大量手动测试。因此,持续部署几乎无法实现。

如果不同模块的资源需求相互冲突,单体应用也可能难以扩展。例如,一个模块可能实现CPU密集型图像处理逻辑,理想情况下应部署在Amazon EC2计算优化型实例上。另一个模块可能是内存数据库,更适合部署在EC2内存优化型实例上。但是,由于这些模块部署在一起,只能在硬件选择上作出折中。

单体应用面临的另一个问题是可靠性。由于所有模块都在同一进程中运行,任何模块中的错误(例如内存泄漏)都可能导致整个进程崩溃。此外,由于应用的所有实例都完全相同,该错误将影响整个应用的可用性。

最后,单体应用也让采用新框架和新语言变得极其困难。例如,假设您有200万行使用XYZ框架编写的代码。即使较新的ABC框架明显更好,重写整个应用以改用该框架也需要付出极高的时间和资金成本。因此,采用新技术面临巨大障碍,只能继续沿用项目初期选择的技术。

总而言之:您有一个成功的业务关键型应用,但它已发展成一个庞大臃肿的单体应用,几乎没有开发人员能够理解。该应用采用过时且效率低下的技术编写,因此很难招聘到优秀的开发人员。它难以扩展,可靠性也不足。因此,无法实现应用的敏捷开发和交付。

那么,该如何解决这些问题?

微服务:应对复杂性

许多组织(如Amazon、eBay和Netflix)采用了如今称为微服务架构模式的方法,解决了这一问题。其思路不是构建一个庞大臃肿的单体应用,而是将应用拆分为一组规模更小、相互连接的服务。

服务通常会实现一组不同的特性或功能,例如订单管理、客户管理等。每个微服务都是一个微型应用程序,拥有自己的六边形架构,由业务逻辑和各种适配器组成。有些微服务会公开API,供其他微服务或应用程序客户端调用;另一些微服务则可能实现Web UI。运行时,每个实例通常是云虚拟机或Docker容器。

例如,下图展示了前述系统的一种可能拆分方式:

示例网约车应用的微服务架构,每个微服务均提供RESTful API

现在,应用的每个功能领域都由各自的微服务实现。此外,Web应用被拆分为一组更简单的Web应用(例如,在出租车叫车应用示例中,分别为乘客和司机提供一个应用)。这样可以更轻松地为特定用户、设备或专门用例部署不同的体验。

每个后端服务都公开一个REST API,而大多数服务会调用其他服务提供的API。例如,Driver Management使用Notification服务器向有空的司机发送潜在行程通知。UI服务通过调用其他服务来呈现网页。服务之间也可能采用基于消息的异步通信。本系列后续文章将更详细地介绍服务间通信。

部分REST API也会向司机和乘客使用的移动应用开放。不过,这些应用无法直接访问后端服务,而是通过一个称为API网关的中间层进行通信。API网关负责负载均衡、缓存、访问控制、API计量和监控等任务,并且可以使用NGINX有效实现。本系列后续文章将介绍API网关。

“扩展立方体”,Y轴表示按功能拆分为微服务

微服务架构模式对应于扩展立方体的Y轴扩展。扩展立方体是优秀著作《可扩展性艺术》中介绍的可扩展性3D模型。另外两个扩展轴分别是X轴扩展和Z轴扩展。X轴扩展是指在负载均衡器后运行应用程序的多个相同副本;Z轴扩展(即数据分区)则使用请求的某个属性(例如某一行的主键或客户身份)将请求路由到特定服务器。

应用通常会结合使用这三种扩展方式。Y 轴扩展将应用分解为微服务,如本节上方第一幅图所示。运行时,X 轴扩展会在负载均衡器后运行每项服务的多个实例,以提高吞吐量和可用性。某些应用还可能使用 Z 轴扩展对服务进行分区。下图展示了如何使用在 Amazon EC2 上运行的 Docker 部署 Trip Management 服务。

网约车服务的微服务示例应用,部署在Docker容器中,前端设有负载均衡器

运行时,行程管理服务由多个服务实例组成。每个服务实例都是一个Docker容器。为实现高可用性,这些容器运行在多台云虚拟机上。服务实例前端部署了NGINX等负载均衡器,用于在各实例之间分发请求。负载均衡器还可能处理其他事项,例如缓存、访问控制、API计量和监控。

微服务架构模式会显著影响应用与数据库之间的关系。每个服务都有自己的数据库模式,而不是与其他服务共享同一个数据库模式。一方面,这种方法与企业级统一数据模型的理念相悖,而且往往会造成部分数据重复。不过,要发挥微服务的优势,每个服务都必须拥有自己的数据库模式,因为这样可以确保松耦合。下图展示了示例应用的数据库架构。

乘车服务示例微服务应用中的数据库架构

每项服务都有自己的数据库。此外,服务可以使用最符合自身需求的数据库类型,这就是所谓的多语言持久化架构。例如,Driver Management用于查找潜在乘客附近的司机,因此必须使用支持高效地理空间查询的数据库。

从表面上看,微服务架构模式与面向服务的架构(SOA)相似。这两种方法的架构都由一组服务组成。不过,也可以将微服务架构模式理解为一种SOA,但没有Web服务规范(WS‑*)和企业服务总线(ESB)的商业化色彩及其被认为带来的负担。基于微服务的应用更倾向于使用REST等更简单的轻量级协议,而不是WS‑*。这类应用也会尽量避免使用ESB,转而在微服务本身中实现类似ESB的功能。微服务架构模式还摒弃了SOA的其他部分,例如规范模式的概念。

微服务的优势

微服务架构模式具有许多重要优势。首先,它解决了复杂性问题,将原本庞大臃肿的单体应用分解为一组服务。在整体功能不变的情况下,应用被拆分为易于管理的模块或服务。每项服务都通过RPC驱动或消息驱动的API形成明确的边界。微服务架构模式实现了一定程度的模块化,而这在单体代码库中很难做到。因此,各项服务的开发速度更快,也更易于理解和维护。

其次,这种架构支持由专注于特定服务的团队独立开发各项服务。只要服务遵守API契约,开发人员就可以自由选择适合的技术。当然,大多数组织都希望避免技术选型完全失控,因此会限制可选技术。不过,这种自由意味着开发人员不必再受制于项目启动时采用的、可能已经过时的技术。开发新服务时,他们可以选择使用当前技术。此外,由于服务规模相对较小,使用当前技术重写旧服务也变得切实可行。

第三,微服务架构模式支持独立部署每个微服务。开发人员无需协调部署仅涉及其服务的更改。这类更改经过测试后即可部署。例如,UI团队可以开展A/B测试,并快速迭代UI更改。微服务架构模式使持续部署成为可能。

最后,微服务架构模式支持各项服务独立扩展。可以仅部署满足各项服务容量和可用性约束所需数量的实例。此外,还可以使用最符合服务资源需求的硬件。例如,可以在EC2计算优化型实例上部署CPU密集型图像处理服务,并在EC2内存优化型实例上部署内存数据库服务。

微服务的缺点

正如Fred Brooks近30年前所写,没有什么灵丹妙药。与其他所有技术一样,微服务架构也有缺点。其中一个缺点就是它的名称。“微服务”一词过分强调了服务的大小。事实上,一些开发人员主张构建粒度极细、仅有10–100行代码(LOC)的服务。虽然小型服务更可取,但务必记住,它们只是实现目标的手段,而不是首要目标。微服务的目标是充分拆分应用,以促进敏捷的应用开发和部署。

微服务的另一个主要缺点是,微服务应用属于分布式系统,因此会带来复杂性。开发人员需要选择并实现基于消息传递或RPC的进程间通信机制。此外,由于请求的目标端可能响应缓慢或不可用,还必须编写代码来处理局部故障。这些工作虽然并非高深莫测,但与单体应用相比要复杂得多。在单体应用中,各模块通过语言级的方法或过程调用相互调用。

微服务面临的另一个挑战是分区数据库架构。更新多个业务实体的业务事务相当常见。由于单体应用只有一个数据库,实现这类事务非常简单。但在基于微服务的应用中,需要更新由不同服务分别拥有的多个数据库。通常无法使用分布式事务,原因不仅在于CAP定理。如今,许多高度可扩展的NoSQL数据库和消息代理根本不支持分布式事务。因此,只能采用基于最终一致性的方法,这对开发人员而言更具挑战性。

测试微服务应用也要复杂得多。例如,使用Spring Boot等现代框架,可以轻松编写一个测试类,用于启动单体Web应用并测试其REST API。相比之下,为某项服务编写类似的测试类时,需要启动该服务及其依赖的所有服务(或至少为这些服务配置桩)。同样,这并非难以掌握,但切勿低估其复杂性。

微服务架构模式面临的另一项重大挑战,是实施涉及多个服务的变更。例如,假设要实现一个用户故事,需要更改服务A、B和C,其中A依赖B,B依赖C。在单体应用中,只需更改相应模块、集成这些变更,然后一次性完成部署。相比之下,在微服务架构模式中,需要仔细规划并协调各项服务变更的发布。例如,需要先更新服务C,再更新服务B,最后更新服务A。所幸,大多数变更通常只影响一项服务,需要协调的多服务变更相对少见。

部署基于微服务的应用也复杂得多。单体应用只需部署在传统负载均衡器后的一组相同服务器上。每个应用实例都配置了数据库和消息代理等基础设施服务的位置(主机和端口)。相比之下,微服务应用通常由大量服务组成。例如,据Adrian Cockcroft称,Hailo有160种不同的服务,而Netflix有600多种服务。[编者注:Hailo已被MyTaxi收购。]每项服务都有多个运行时实例。这意味着需要配置、部署、扩缩和监控的组件多得多。此外,还需要实施服务发现机制(将在后续文章中讨论),使服务能够发现需要与之通信的任何其他服务的位置(主机和端口)。基于传统故障工单和人工操作的运维方式无法应对如此高的复杂度。因此,要成功部署微服务应用,开发人员需要对部署方法拥有更大的控制权,并实现高度自动化。

一种自动化方法是使用现成的平台即服务(PaaS),例如Cloud Foundry。PaaS可帮助开发人员轻松部署和管理微服务,无需操心IT资源的采购和配置等问题。同时,负责配置PaaS的系统和网络专业人员可以确保其符合最佳实践和公司政策。另一种实现微服务部署自动化的方法是开发一个实质上属于自己的PaaS。通常可以从集群解决方案入手,例如Kubernetes,并结合Docker等技术使用。在本系列后续文章中,我们将探讨基于软件的应用交付方法(如NGINX Plus)如何帮助解决这一问题。NGINX Plus可以轻松处理微服务级别的缓存、访问控制、API计量和监控。

总结

构建复杂应用本身就很困难。单体架构只适用于简单的轻量级应用。如果将其用于复杂应用,将会带来诸多难题。尽管微服务架构模式存在缺点和实施挑战,但对于复杂且不断演进的应用而言,它仍是更合适的选择。

在后续博文中,我将深入介绍微服务架构模式的各个方面,并探讨服务发现、服务部署选项,以及将单体应用重构为多个服务的策略等主题。

敬请关注……

编者:本系列共7篇文章,现已全部发布:

  1. 微服务简介(本文)
  2. 构建微服务:使用API网关
  3. 构建微服务:微服务架构中的进程间通信
  4. 微服务架构中的服务发现
  5. 面向微服务的事件驱动型数据管理
  6. 选择微服务部署策略
  7. 将单体应用重构为微服务

还可以下载电子书,其中收录了完整系列文章,以及使用NGINX Plus实施微服务的相关信息:《微服务:从设计到部署》。

客座博主Chris Richardson是早期网站CloudFoundry.com的创始人。该网站是面向Amazon EC2的早期Java平台即服务(PaaS)。目前,他为各组织提供咨询服务,帮助改进应用开发和部署方式。


分享

相关博客文章

NGINX Plus R26 正式发布
NGINX | 2022/02/15

NGINX Plus R26 正式发布

NGINX Plus R26通过缓存JSON Web密钥集加快JWT验证,并强化TLS握手,从而提升安全性。NGINX JavaScript的新功能包括增强对异步函数的支持,以及实现WebCrypto API。

使用NGINX App Protect DoS保护gRPC应用,抵御严重的DoS攻击
NGINX | 2022/02/10

使用NGINX App Protect DoS保护gRPC应用,抵御严重的DoS攻击

gRPC具备开发人员设计和部署现代应用所需的速度与灵活性,但其开放性也使其极易受到第7层DoS攻击。NGINX App Protect DoS可保护gRPC应用免受此类破坏性攻击。

Ingress Controller 选型指南(第 2 部分):风险与面向未来的规划
NGINX | 2021/09/13

Ingress Controller 选型指南(第 2 部分):风险与面向未来的规划

了解选择错误的Ingress控制器可能带来的风险,以及如何从关键方面着手,让您的选择更好地适应未来需求。

NGINX Plus R19 正式发布
NGINX | 2019/09/03

NGINX Plus R19 正式发布

NGINX Plus R19扩展了监控功能,不仅提供更多指标,还新增了指标分析方式;增加试运行模式,用于测试限速效果;增强键值存储,以支持IP地址范围;并使限速更加动态。

NGINX现已正式加入F5
NGINX | 2019/05/09

NGINX现已正式加入F5

NGINX现已正式成为F5的一部分。Gus Robertson现任F5 NGINX业务部门负责人。他阐述了两家公司在人才、文化和愿景方面融合的价值,以及双方将如何携手增强NGINX的开源产品和商业产品。

使用NGINX和NGINX Plus实现高性能缓存
NGINX | 2016/08/24

使用NGINX和NGINX Plus实现高性能缓存

我将介绍NGINX在内容缓存和性能方面的工作原理,并分享一些控制NGINX内容缓存方式的技巧。