第 1 章:为什么选择微服务?
本章内容概览:
- 本书的学习方法
- 微服务的定义及其重要性
- 微服务的优缺点
- 单体架构存在的问题
- 微服务设计基础
- 我们将构建的应用程序概述
随着软件的规模和复杂性不断增加,我们需要更好的方法来管理和减轻这些复杂性。业务的发展要求我们能够分解软件,以便多个团队可以协同工作。
随着客户需求的增长,我们的应用程序也需要具备容错能力,并能快速扩展以应对高峰期的需求。那么,在开发和迭代应用程序的同时,如何满足现代业务的需求呢?
微服务架构模式在当前的软件开发中扮演了重要角色。由微服务组成的分布式应用程序能够解决这些挑战,尽管它们通常比传统的单体应用程序更难设计、更复杂且耗时更长。如果你还不熟悉微服务、分布式应用程序和单体应用程序等术语,不用担心,我们会很快为你解释这些。
过去人们常认为微服务过于复杂。一般建议是先构建单体应用程序,只有在需要扩展时才重构为微服务。然而,我认为这种看法并没有简化应用程序的构建工作!应用程序总是会变得越来越复杂,最终你将不得不进行扩展。当你决定需要变更时,你将面临一个极其艰巨的任务——在员工和客户已经依赖现有系统的情况下,安全地将单体应用程序转换为微服务。
现在是构建微服务的最佳时机。可获取且成本低廉的云基础设施、不断改进的工具和增加的自动化机会——这些因素正在推动整个行业向更小的服务(即微服务)发展。随着时间的推移,尽管应用程序变得越来越复杂,微服务为我们提供了更好的管理复杂性的方法。现在是采用“微服务优先”策略的最好时机。
在本书中,我将展示采用微服务优先的方法已经不再令人望而却步。我相信我们正朝着微服务的方向稳步前进。剩下的挑战是,学习微服务仍然很困难。学习曲线陡峭,这阻碍了许多开发人员构建微服务的尝试。我们将一起克服这个学习障碍。我们将向单体应用程序说“不”,从零开始构建一个简单但完整的视频流应用程序,使用微服务架构。
架构选择范围
还在犹豫微服务是否是正确的选择吗?现实是,我们的选择不仅限于单体架构和微服务之间。事实上,我们面前有一系列的选择。因此,这本书不仅仅关于微服务;最终,它关于在这个选择范围内确定我们的位置。对于任何特定项目,我们可能会选择单体架构、微服务,或介于两者之间的某种形式。但如果你不知道如何使用微服务构建工具,那么你的选择将受到限制——你将被迫为每个项目采用单体架构。这本书将提供解锁完整单体 - 微服务范围的工具,并赋予你自由选择任何位置的能力。你如何利用这些工具,取决于你自己。
1.1 这本书的实用性
为什么要读这本书?因为你想或需要构建一个微服务应用程序,这对于现代开发人员来说是一项重要的技能,但它不易掌握,你需要指导。你可能已经阅读了其他关于微服务的书籍,但还是不知道从哪里开始。我理解你的困惑。
微服务难以学习。你不仅需要掌握复杂的工具,还必须学会构建分布式应用程序。这需要掌握新的技术、技巧和通信协议。不过,在这本书中,我们将突破看似无法克服的学习障碍。我们将从最基础的开始,逐步构建一个更复杂的微服务应用程序,并将其部署到生产环境。
这本书旨在打破学习障碍并启动一个可以长期运行的工作应用程序,我们可以持续更新和构建,以满足客户和用户不断变化的需求。图 1.1 展示了这种切穿学习曲线的理念。尽管我们的示例应用程序小而简单,但从一开始,我们就将构建可扩展的路径,以后可以将其发展成一个庞大的分布式应用程序。

这本书与其他关于微服务的书籍的不同之处在于,其他书籍更加理论化。这对希望扩展知识的经验丰富的开发人员或架构师来说是好的,但这种方式获得实际技能具有挑战性,也无法帮助你在启动新应用程序时避免踩坑。在项目开始时做出的技术选择可能会长期困扰你。
本书不同;它不是理论性的。我们将采取一种实用的方法来学习。整个过程中将涉及少量的理论,我们将真正构建一个实质性的微服务应用程序。从无到有地构建我们的应用程序并将其投入生产。我们将在开发计算机上构建和测试应用程序,最终将其部署到云端。
这本书不会教你所有东西;没有哪本书能做到。相反,我们将采用不同的方法:实际学习启动新应用程序并将其展现给客户所需的最少量知识。
我们将一起启动我们的微服务应用程序,而不必深入学习任何工具或技术的复杂细节。这本书的学习模式如图 1.2 所示。

这本书是关于从零开始构建一个微服务应用程序的。有些人可能会问,为什么我没有写一本书来展示如何将单体应用程序转换为微服务应用程序。这是很多人想要学习的。
我选择这种方式写这本书,因为从头开始学习如何编写应用程序比学习如何重构现有应用程序要容易得多。我也认为这些技能很有用,因为随着时间的推移,越来越多的应用程序将采用微服务优先的方式编写。
无论如何,重构现有应用程序比构建新应用程序要复杂得多。这是一个高度复杂的过程,严重依赖于遗留代码库的具体情况。我假设,一旦你知道如何创建一个全新的微服务应用程序,找到将单体转换微服务的策略将会更加容易。
我可以向你保证,当你能够构建一个微服务优先的应用程序时,你将更好地看到从现有单体到微服务的路径。毫无疑问,从单体到微服务的旅程仍将是艰难的,所以请继续关注。在第 12 章中,我们将谈论更多关于如何从单体转换到微服务的问题。
在整本书中,你将学习到启动微服务应用程序的具体实用的技术。当然,有许多不同的方法可以做到这一点,可以使用许多不同的工具。我将教你一种单一的方法和一套工具(尽管是流行的工具集)。你还会从我这里得到很多意见,我敢肯定你不会同意我说的每一点。这没关系,因为这本书不是微服务的圣经——它只是一个起点。毫无疑问,你会找到许多改进途径,添加你自己的技术,去掉不喜欢的部分,增强你喜欢的部分以适应自己的情况。
当然,有些有经验的开发者已经有自己的看法和方法。我想说的是,这是我的方法,只是众多有效方法之一;然而,我可以证明,我已经在实际项目中尝试了书中的每一种技术,发现这些技术通常效果很好。那么,事不宜迟,让我们开始我们的微服务之旅吧。
1.2 你将学到什么?
虽然仅通过阅读一本书你不可能掌握所有关于微服务的知识,但本书将帮助你大幅前进,尤其是当你尝试运行和调整代码示例时。
我们的旅程将从零开始,首先在本地计算机上创建并运行一个单一的微服务以进行开发和测试。随后,我们将扩展至在本地运行多个微服务。最终,我们将创建一个 Kubernetes 集群并将我们的微服务部署到云端,从而完成旅程,把应用呈现给客户。在此过程中,你将学习到数据管理、微服务间的通信、测试方法以及如何创建自动化部署管道。
虽然内容繁多,我们将从易到难逐步进行。在这 12 个章节中,我们将一步步构建一个更复杂的应用及其支撑基础设施,确保你在每一步都不会迷失方向。通过阅读本书并实践其中的技能,你应能够:
- 创建单个微服务
- 使用 Docker 打包和发布微服务
- 使用 Docker Compose 在开发计算机上开发微服务应用
- 使用 Jest 和 Playwright 测试你的代码和应用
- 集成第三方服务器(如 MongoDB 和 RabbitMQ)
- 使用 HTTP 和 RabbitMQ 实现微服务间的消息通信
- 存储微服务所需的数据和文件
- 将微服务部署到生产级 Kubernetes 集群
- 使用 Terraform 创建生产基础设施
- 创建一个持续部署管道,自动部署 GitHub 库中的应用更改
1.3 你需要知道什么?
你可能在想,在开始阅读这本书前需要准备哪些知识。我已尽量减少对你先前知识的假设。我们将从基础概念一直探讨到一些更为复杂的概念。不管你有多少开发经验,这本书都能为你提供有价值的内容。
如果你具备一些编程基础,这本书将非常适合你。不要求你有先验知识,只需能理解代码即可。无需担心,我会解释代码中的所有关键事件。
如果你有编程背景会很容易理解本书中的示例。如果你在阅读本书的同时学习编程,可能会有些挑战,但这并非不可能,只是需要多花些努力。
本书使用 Node.js 作为微服务的示例语言,但你无需事先了解 JavaScript 或 Node.js。你将在阅读中学到足够的内容来跟上进度。本书还使用 Microsoft Azure 进行生产部署示例,同样,你无需事先了解 Azure。
请放心,本书的重点不在于 Node.js 或 Azure;而是关于如何使用现代工具(如 Docker、Kubernetes 和 Terraform)构建微服务应用。你将学到的大多数技能都可以迁移到其他编程语言和其他云提供商。尽管如此,为了示范本书中的技术,我选择了 Node.js 和 Azure,因为这是我在自己的软件产品中广泛使用的组合。
如果 Node.js 和 Azure 不是你的首选,通过自己的额外研究和实验,你将能够了解如何用你偏好的编程语言和云提供商替换它们。实际上,我选择使用 Docker、Kubernetes 和 Terraform 的主要原因是这些工具提供了自由选择编程语言和避免云提供商锁定的自由。
1.4 管理复杂性
就像任何应用程序一样,一个微服务应用程序会随着时间的推移变得越来越复杂——但它不必从一开始就那么复杂。本书采取的策略是从一个简单的起点开始,并确保每次迭代开发同样简单。此外,每个微服务都应保持小而简单。微服务的构建被认为比单体应用更困难,但我希望本书能帮你找到一条更简单的路径。
微服务提供了一种在细粒度级别上管理复杂性的方法,我们几乎每天都在这个级别上操作——即单个微服务的级别。在这个级别上,微服务并不复杂。实际上,为了符合微服务的定义,它们必须保持小巧和简单。一个单一的微服务应该由一个开发人员或一个小团队来管理。
我们将使用微服务将复杂的应用程序分解成简单、清晰边界的小部分。我们也可以采用类似方法分解单体架构,但保持这些部分的独立性很困难——它们往往会随着时间的推移而相互纠缠。单体和微服务的这种区别如图 1.3 所示。

尽管实际上通过持续的开发和演进,复杂的系统会逐渐出现。不可否认,微服务应用程序会变得复杂。但这种复杂性不是一蹴而就的;它需要时间。尽管我们的应用程序趋向于复杂性,但微服务本身是这种复杂性的解药,而不是其原因。通过开发和运营,我们可以使用微服务架构来管理应用程序日益增长的复杂性,而不是将其变成负担。
也许是因为我们现在必须更频繁地处理基础设施的复杂性,这使我们注意到它。过去,我们有一个运维团队,可能还有一个构建或测试团队来处理大部分工作并隐藏复杂性。不过,越来越多的情况下,微服务将权力交还给开发人员,让我们清楚地看到开发、测试和运营基础设施中一直存在的复杂性。
微服务增加的任何复杂性必须通过其优势来抵消。像所有设计或架构模式一样,要从微服务中获得价值,我们必须确保它们的优势超过使用成本。这是我们必须根据项目具体情况做出的艰难决定,但在越来越多的情况下,微服务的优势远远超过了它们的成本。
微服务应用程序是一种复杂的适应性系统,其复杂性自然地从其组成部分的交互中产生。即使整个系统变得太复杂以至于任何个人都无法理解,但其每个组件仍然小而易于管理且易于理解。这就是微服务帮助我们应对复杂性的方式:通过将复杂性分解成小而简单且易于管理的块。不过不用担心,我们在本书中构建的示例应用程序并不复杂。
通过工具和自动化的帮助,使用微服务进行开发使我们能够构建极大且可扩展的应用程序,而不会被复杂性所压倒。读完这本书后,你将能够放大任何最复杂的微服务应用程序的任何部分,发现其组件是简单明了且易于理解的。
1.5 什么是微服务?
在深入理解微服务应用程序之前,我们需要先明确什么是微服务。
定义 微服务是一个小型独立的软件进程,它按照自己的部署计划运行,并可以独立于其他服务进行更新。 这个定义告诉我们,微服务是一个小型、独立的软件进程,具有自己的部署周期,这意味着它可以独立于其他微服务进行更新。
微服务可以由一个开发人员或一个团队负责。在小公司或初创公司(比如我的工作环境)或在学习过程中(就像你在本书中所做的),我们经常需要独立管理多个微服务甚至整个微服务应用程序。
微服务可能对外公开,供客户直接交互,也可能仅限内部使用,不对外开放。它们通常可以访问数据库、文件存储或其他形式的状态持久化资源。图 1.4 显示了这些内部和外部的关系。
单个微服务本身可能不起眼,但一个设计良好的系统可以由这样的简单服务组成。这些服务必须相互协作,共同提供整个应用的功能和特性。这就引出了微服务应用程序的概念。

1.6 什么是微服务应用程序?
微服务应用程序传统上被视为分布式应用程序,由多个分离的进程组成,这些进程通过网络进行通信。每个服务或组件通常驻留在逻辑上独立的(虚拟)计算机上,有时甚至是物理上独立的机器。
定义 微服务应用程序是由多个小服务组成的分布式程序,这些服务协作实现整个项目的功能和特性。
微服务应用程序通常包含一个或多个对外的服务,使用户能够与系统互动。图 1.5 展示了两个这样的服务,分别作为网页和手机用户的入口。你还可以看到许多服务在集群中协同工作。它被称为集群,因为对开发者来说,它表现为一个单一的计算资源,可以根据需要进行管理。我们还有一个数据库服务器,在图中它位于集群外部,但也可以部署在集群内。我们将在第 4 章详细讨论这一点。
集群托管在一个集群编排平台上;在本书中,我们使用 Kubernetes 实现此目的。编排是自动管理我们服务的过程。这正是 Kubernetes 为我们提供的服务——帮助我们部署和管理服务。
集群本身、数据库和其他虚拟基础设施都托管在我们选择的云提供商平台上。我们将学习如何将这些基础设施部署到 Microsoft Azure,但通过你的努力,你可以将本书中的示例修改为部署到 Amazon Web Services (AWS) 或 Google Cloud Platform (GCP)。

微服务应用程序具有多种形态,灵活性极高,可根据多种业务需求进行配置。任何特定的应用程序可能具有熟悉的整体结构,但其包含的服务将根据客户需求和业务领域执行不同的任务。
1.7 单体架构的问题是什么?
在讨论为什么想要转向微服务之前,我们需要理解单体架构是什么。尽管分布式计算已经存在了几十年,但在云革命和微服务流行之前,大多数应用程序都是以单体形式构建的。图 1.6 展示了一个简单的视频流应用程序的服务架构,并对比了单体和微服务应用程序。
定义 单体架构指的是整个应用程序在单一进程中运行。
构建单体架构比构建微服务应用程序简单得多,因为它需要较少的技术和架构知识。这为新应用程序提供了一个很好的起点,尤其是在业务模型尚未经过市场验证前,避免了高额的技术成本。

单体架构是对早期一次性原型或小规模应用的理想选择。如果你的应用程序规模较小,或很快就稳定下来并且不需要进一步开发或扩展,那么单体架构可能完全满足需求。如果你的应用程序始终维持较小规模,单体架构则是合适的选择。
选择先构建单体架构还是直接采用微服务通常需要权衡,历史上单体架构通常更受青睐。然而,在本书中,我会展示,在现代工具的帮助下以及考虑到廉价且便捷的云基础设施,优先考虑微服务或至少朝微服务区间的一端发展的重要性。
大多数产品通常需要发展和迭代,随着单体架构不断膨胀并丰富其功能,越来越难以抛弃原型。因此,可能会出现一种情况,你发现自己被旧的单体架构限制,而实际上需要的是微服务应用程序所提供的灵活性、安全性和可扩展性。
单体架构存在多个潜在问题。它们从小规模开始,我们总希望保持代码的整洁和组织有序。一支优秀的开发团队可以在多年时间里保持单体架构的优雅和有序。然而,随着时间的推移,这种理想状态可能逐渐消失,有时甚至一开始就缺乏一个清晰的愿景。所有代码都在同一进程中运行,没有任何隔离层,这无法阻止我们创建几乎无法在未来拆分的庞大而混乱的代码库。
员工流动性也大大影响了这种架构。当开发人员离开时,他们带走了关键的知识,新来的开发人员需要构建自己对应用的理解,这很容易与原始愿景相冲突。随着时间推移,代码经手人多,这种负面影响共同导致代码库逐渐退化成一个所谓的“泥球”状态。这个术语用来描述当应用的架构变得模糊不清时的混乱局面。
更新单体架构的代码风险极高——要么一次性更新全部,要么不更新。当你推送一个破坏性的代码更改时,整个应用可能会停摆,导致客户服务中断,公司因此遭受损失。我们可能只需要更改一行代码,但仍需部署整个单体应用,并冒着使其崩溃的风险。这种风险激发了部署恐惧,从而减缓了开发速度。
此外,随着单体架构逐渐老化,我们无意中破坏它的风险增加。测试变得更加困难,引发更多的部署恐惧。实验和创新因此而停滞。这是否已经说服你尝试微服务了?还有更多理由!
由于单体架构的庞大规模,测试成为一大难题,由于其极低的粒度,它很难扩展。最终,单体架构会扩展到其运行的物理机器的极限。随着老化的单体架构占用越来越多的物理资源,运行成本显著增加。我亲眼见证了这种情况!公平地说,这可能是对任何单体架构的遥远未来,但即使经过几年的增长,单体架构也可能导致一种我们不愿面对的局面。
尽管单体架构最终面临许多挑战,但它仍然是启动新应用程序的简单方法。我们不应该总是先构建单体架构,然后在需要扩展时重构吗?我的回答是:这取决于具体情况。
许多应用程序将始终保持小规模。在外面有很多小型单体应用,它们运行良好,不需要扩展或演进。它们没有增长,因此不会遇到增长问题。如果你认为你的应用将保持小规模并且不需要演进,那么绝对应该选择构建单体架构。
然而,对于许多我们可以预见将从微服务优先方法中受益的应用,尤其是那些我们知道将持续演进多年的应用,或那些从一开始就需要灵活性、可扩展性或具有安全限制的应用,使用微服务从头开始构建会更容易,因为从现有的单体架构转换是非常困难且有风险的。
当然,如果你需要首先验证你的商业想法,可以通过构建单体架构来实现。但即便如此,我仍然认为,使用合适的工具,开发微服务原型并不比开发单体架构更困难。毕竟,单体架构本质上是什么?如果不是一个庞大的单一服务?
你甚至可以考虑使用本书中介绍的技术,将你的单体应用作为 Kubernetes 集群中的一个服务来启动。这样当需要分解为微服务时,你已经处于一个有利位置,可以轻松开始将微服务从单体中剥离出来。随着现代工具提供的自动化部署便利,重构和重新创建应用或创建开发和测试的副本环境变得很容易。即使选择先创建单体架构,你仍然可以从本书中介绍的技术中受益。
如果你从单体架构开始,从理智方面考虑,请尽早将其抛弃并重构为微服务。在第 12 章中,我们将讨论更多关于拆分现有单体架构的内容。
1.8 为什么微服务如此受欢迎?
微服务为何如此受欢迎,这仅是一时的热潮吗?绝非如此。尽管分布式计算已存在多年并常常提供比单体应用更多的优势,但过去这类应用的构建成本很高且相当复杂,只有在确信投入成本得到足够回报时,开发者才会采用这种复杂的架构。
然而,随着云技术和虚拟化的普及,以及自动化工具的发展,构建这些分布式系统的成本显著降低。微服务作为分布式应用的最小单元,因成本降低和技术便利性增加而变得普及。
这就是微服务目前流行的原因。它们不仅是构建现代复杂应用的有效方式,而且相对成本更低。分布式计算的门槛降低,使更多开发者能够利用这些技术。目前,微服务正逐步成为主流。
1.9 微服务的优势
微服务架构带来多项优势。每个服务都可以单独配置自己的 CPU、内存和其他资源。虽然我们通常在多个服务间共享物理基础设施,但必要时可以单独分配资源,使得资源最密集的服务能拥有独立的资源配置。这种架构使我们能够细粒度地控制应用性能,优化各部分的扩展性。微服务的主要优势包括:
- 精细的控制能力:微服务允许我们以精确控制的方式扩展应用功能。
- 降低部署风险:微服务通过加快开发速度的同时降低了部署风险。
- 技术栈的灵活性:微服务使我们可以根据不同任务的需求选择最适合的技术栈,不再受限于单一技术栈。
微服务提高了应用的可靠性并减少了部署风险。当我们需要更新特定服务时,可以独立进行,而不会影响整个应用程序。如果出现问题,只影响小部分系统,且容易进行回滚,比整个系统宕机要容易管理得多。这种降低风险的策略促进了频繁的部署,对保持敏捷开发和快速迭代至关重要。
这些优势并非新鲜事。我们构建分布式应用已有相当长时间,但如今成本更低,工具更易用。现在,构建应用不仅更简单,回报也更快。成本的降低和部署的便捷性使我们的服务更加微观,带来了显著的好处。
小型服务的启动速度快,系统易于扩展,因为我们可以快速复制任何负载较重的服务。小型服务也更容易进行测试和故障排除。虽然整体微服务系统的测试仍具挑战性,但验证其各个组成部分的功能更为简单。
一个由许多小而独立的部分组成的应用程序更易于扩展、演变和重新组织。这种灵活性和稳健性的组合鼓励了创新,这对企业非常有利。由于我们在组件间设置了清晰的边界,避免了编写混乱代码。即便我们偶尔写出了不理想的代码,其影响也是局限和可控的,因为每个微服务都足够小,可以在几周甚至几天内被重写或替换。从这个角度看,我们在设计代码时已经预设了其可能的替换性。我们的架构设计鼓励随时间的推移进行迭代和替换,这对于适应不断变化的商业环境是必需的。
另一个微服务让开发者兴奋的优点是,不再受限于单一技术栈。我们的应用中的每个服务都可以使用任何适合的技术栈。对于大公司而言,这意味着不同团队可以根据他们的专长或任务需求选择合适的技术栈。不同的技术栈可以在同一个集群中共存,通过共享的协议和通信机制共同工作。
在技术栈之间能够自如切换对应用的长期健康至关重要。随着技术环境的不断演变,旧的技术栈可能会过时并需要被新技术取代。微服务架构为我们提供了一个结构,使我们能够逐步过渡到新的技术栈。作为开发者,我们不再需要被束缚于过时的技术之中。
技术栈
技术栈是构建每个微服务所需的工具、软件和框架的集合,可以视为构建应用程序所需的基础元素。
一些常见的技术栈有特定的名称,如 MEAN(MongoDB、Express、Angular、Node.js)或 LAMP(Linux、Apache、MySQL、PHP)。然而,技术栈仅是你选择使用的工具组合,无需特定的名称就可以发挥作用。
在构建单体架构时,我们通常需要选定并坚持使用一个技术栈。相比之下,微服务架构的一大优势是它允许在同一应用程序中使用多种技术栈。这种灵活性使我们可以根据应用程序的演进需要逐步更换或调整技术栈。
1.10 微服务的挑战
探讨微服务的优势同时,我们也不能忽视它们面临的挑战:
- 对技术的要求较高。
- 分布式应用程序的构建难度大。
- 微服务的可扩展性问题。
- 复杂性带来的普遍忧虑。
1.10.1 较高的技术技能需求
微服务的首要挑战是陡峭的学习曲线。掌握微服务所需的技术集合复杂且深奥。虽然学习微服务构建仍具挑战性,但本书旨在帮助你更快地克服这些困难。
注意 我理解你可能对即将面对的挑战感到畏惧。幸运的是,分布式应用程序开发工具最近取得了巨大的进展。这些工具现在不仅更加先进和易用,而且更易于自动化,比以往任何时候都来得更实用。
通常,独自克服这种学习曲线可能需要几个月甚至更长时间,因为掌握任何一种工具都需要时间。本书采用了不同的方法,我们将共同学习仅足以启动并运行生产环境中应用程序的知识,最终实现一个简单但功能完备的微服务应用程序。
1.10.2 分布式应用程序构建的复杂性
我们不仅需要学习构建微服务的新技术,还必须掌握构建分布式应用所需的新技术、原则、模式和权衡。分布式应用的构建、运行和完全理解都是复杂的过程。在采用任何新技术时,我们都必须衡量成本与收益。微服务虽带来显著优势,但必须自问为了这些优势是否负担得起。
为更深入了解构建这种软件的难度,请参考《分布式计算的八大谬误》( https://nighthacks.com/jag/res/Fallacies.html ),了解我们在构建这类应用时可能犯的错误。
本书将介绍分布式应用的基础,帮助你理解并应对构建和维护这类系统的实际挑战,专注于如何将应用交付给客户并优化其性能和稳定性。
1.10.3 微服务的扩展性挑战
微服务并非解决所有问题的灵丹妙药。实际上,它们可能会放大现有的问题。微服务不仅带来了性能和开发的可扩展性挑战,还增加了管理复杂性的挑战。微服务可以放大单体应用程序中存在的任何问题。
构建微服务应用程序比构建单体应用程序更为复杂。这不仅需要专业技能(见 1.10.1 节),还需要在自动化方面进行显著投资。此外,我们需要有效的工具来帮助管理系统。随着应用中服务数量的增加,仅理解微服务间如何通信就是一个日益复杂的问题。
拥有合适的技能、强大的自动化和有效的工具可以极大地简化微服务管理,使之看似容易。然而,如果你不愿或无法在这些方面进行投资,可能会遇到意想不到的困难。即使在微服务应用程序的早期阶段你可能还能应对,但随着微服务数量的增加,管理它们的难度也会大幅上升。
1.10.4 对复杂性的普遍恐惧
构建微服务应用程序或任何分布式应用程序比构建相应的单体应用程序要复杂得多,这一点无可争辩。
我的第一点建议是,虽然一开始构建单体架构确实更简单,并且在许多情况下这是合适的决定,但如果你的应用程序是那种最终需要转换或重构为微服务的类型,那么你应该考虑这种转变的最终成本。
主要的建议是不要让复杂性吓到你;无论我们喜欢与否,复杂性总是存在的。幸运的是,微服务提供了一种实际的方法来管理这种复杂性。
如果你考虑了成本与收益,可能会发现在某些情况下,使用微服务构建实际上比构建单体应用程序更简单。考虑这样一个事实:任何重要的应用程序最终都会变得复杂。如果不是在开始时,就是随着时间的推移。在现代软件开发中,你无法逃避复杂性;它终将到来。相反,我们应该主动接受这一挑战。我们需要更好的工具来帮助管理复杂性,而微服务作为一种架构模式,正是我们需要的。
1.10.5 提前应对挑战
可以将微服务视为一种提前应对问题的策略。确实,与单体架构相比,使用微服务更加困难,但有时候,提前面对问题(如复杂性)可以在整个项目生命周期中带来好处。
我们的投入带来了什么回报?微服务帮助我们管理应用程序的复杂性,提供了清晰的边界,防止代码混乱。微服务使我们能够更容易地重新配置、扩展、升级和替换应用程序的各个部分。微服务还促使我们采用更好的设计。我们无法完全避免复杂性,但我们可以管理它,而现代的分布式应用程序工具足以应对。
1.11 现代微服务工具
本书深入探讨了多种工具。我们将从基础开始,首先必须能够创建一个微服务。为此,我们会使用 JavaScript 和 Node.js,这些内容将在下一章详细介绍。
我们选择使用 Node.js,因为它是我的个人首选。不过,对于微服务来说,使用哪种技术栈并不至关重要。同样可以用 Python、Ruby、Java、Go 或几乎任何其他语言来构建微服务。
在我们的学习旅程中,我们将接触许多工具,以下是最关键的几个:
- Docker —— 用于打包和发布服务。
- Docker Compose —— 在开发环境中测试微服务。
- Kubernetes —— 托管云中的应用程序。
- Terraform —— 构建云基础设施。
- GitHub Actions —— 实现持续部署。
技术环境不断变化,工具也随之更新。那么,为什么还要学习一套可能很快过时的工具呢?原因在于,优秀的工具可以提高我们的工作效率,无论是做得更好还是更高效,都是提升生产力的关键。
我选择这些工具是因为它们能显著简化和加速微服务应用程序的构建过程。这些工具虽然会随时间更新,但目前它们非常流行,是我们的最佳选择,并在我们的工具箱中占据了重要位置。
当然,这些工具最终可能会被更新的版本取代,但在此期间,我们希望从中获得巨大价值并构建许多高质量的应用程序。当工具发生变化时,它们往往会被更高效的工具所取代,这将帮助我们将工作自动化到更高的层次,从而使任务变得更加简单和高效。
在所有这些工具中,Docker 可以说或是最流行的一个,它似乎凭空出现并迅速占领了我们的行业。Kubernetes 虽然不如 Docker 那样普及,但它展现出了巨大的潜力,很有希望成为微服务计算的首选平台。
Kubernetes 允许我们跨云提供商移动应用程序。如果你曾因绑定到特定的云提供商而感到束缚,那么这将是一个利好消息。我们可以在几乎任何云平台上运行基于 Kubernetes 的应用程序,并在必要时自由迁移。
Terraform 是相对较新的工具,但它已经成为游戏规则的改变者。它是一种声明式配置语言,允许我们在云中以代码的形式定义基础设施。Terraform 的一个显著特点是它的通用性,能够与任何云提供商配合使用。无论你现在或将来选择哪个云供应商,Terraform 都有潜力提供支持,而无需重新学习新工具。
想一想:Terraform 让我们能够轻松编写云基础设施的代码,这是一个重大突破。在过去,我们可能会劳力地手动配置基础设施,但现在我们可以通过代码来创建,这就是所谓的“基础设施即代码”,我们将在第 7 章中详细探讨这一点。
1.12 不仅仅是微服务
值得注意的是,本书中使用的工具并不专门用于构建微服务——我们同样可以使用 Docker、Kubernetes、Terraform 和 GitHub Actions 来构建单体应用程序。尽管可能对于托管单个进程而言,创建一个 Kubernetes 集群看起来过于复杂。
正如前所述,微服务之所以受到青睐,部分原因是这些工具使得构建微服务及其底层基础设施变得更加简单。然而,你实际上可以使用这些工具构建任何规模的分布式应用程序,而不仅仅是微服务。我们不应该过分强调微服务,实际上,一个术语“合适大小的服务”可能更加恰当,它强调服务的大小应根据实际需求来确定,而不是一味追求小。
未来你如何使用这些工具——无论是单体、微服务还是介于两者之间的结构——完全取决于你。我们应该承认,现实世界的解决方案从来都不像理论中的单体与微服务那样清晰分明。
许多开发人员并不关心他们的软件是如何构建的,只要能构建有用的软件并享受良好的开发体验就行。你的客户肯定不在乎他们使用的软件是如何构建的:他们只关心良好的用户体验,而不关心开发人员如何组织代码。
1.13 可能性范围
在软件架构的世界里,选择不只限于单体架构与微服务之间的二分法。实际上,我们面对的是一整套的可能性,如图 1.7 所展示的那样。

虽然这是一本关于微服务的书,但我并不主张你必须在这个范围上选择一个特定的点。想要单体架构?那完全可行。需要微服务?那也非常好,因为微服务确实能带来许多优势。
更有可能的情况是,你的选择会处于这个范围的中间。实际上,大多数现实世界的应用程序都处于这两者之间的某个地方,可能是偏向左边,拥有一个主体架构和一些辅助服务;或者偏向右边,拥有多个小服务和一些较大的服务。没有唯一的正确答案。你需要找到最适合自己、团队、公司,以及最重要的——客户的那个点。
在本书中,我倾向于推崇一种理想化的微服务开发方式,我喜欢称之为“微服务开发者的乌托邦”。这种方式最适合于启动全新(绿地)项目时采用,即使如此,也只有在你未来能持续维持这种模式时才真正可行。我相信这是一种理想的工作方式,条件是我们能保持足够的纪律。但也请不要因为实际开发情况往往没有那么理想而感到压力。
软件开发的本质是复杂和无序的。本书中的技术可以帮助你重新获得一些控制权,但将它们应用于实践中可能很有挑战,尤其是当你处理遗留代码和不断变化的业务需求时。
好消息是,我们不需要完美地实施微服务,它们就能开始为我们的应用和开发流程带来益处。我们可以逐步向微服务范围的一端移动,任何朝这个方向的改变都是有益的。我们将在第 12 章中再次探讨这种可能性范围。
1.14 设计微服务应用程序
本书不是关于理论的讨论,也不专注于软件设计的原理,但在深入探讨具体内容之前,有必要简要讨论一下软件设计。
1.14.1 软件设计
设计微服务应用程序在本质上与设计任何类型的软件类似。你可以借鉴任何一本优秀的软件设计书籍的原则和技巧应用于微服务。虽然没有严格的规则,但以下几点我认为尤其重要:
- 避免过度设计或过早优化。始终从简单的设计开始。
- 持续进行重构,以维持设计的简洁性。
- 良好的设计在开发过程中自然形成。
我认为最后一点对于微服务尤其适用。你无法为一个复杂的微服务应用程序进行全面的预先设计。架构需要在开发过程和应用的生命周期中逐渐演化。
这并不意味着你不应该做任何规划。你当然应该在每次迭代中做好规划,但要准备好应对计划的变化。你应该能够迅速适应变化,这也是微服务所支持的灵活性。
1.14.2 设计原则
特别适用于微服务的一些设计原则包括:
- 单一职责原则
- 职责分离
- 松耦合
- 高内聚
理想的微服务应该尽可能地小而简单。每个服务应只关注单一的业务领域,这就是单一职责原则的应用。自然地,这导致了职责的清晰分离,避免了职责的交叉,从而使每个微服务更易于理解和维护。
微服务应当实现松耦合和高内聚。松耦合意味着最小化服务间的直接依赖关系,服务间尽量不共享信息,除非必要。这样可以简化服务的单独升级,并减少对整个应用的潜在影响。高内聚则确保服务内部的功能密切相关,专注于解决特定的问题。
1.14.3 领域驱动设计
领域驱动设计(DDD)是一个特别适合微服务的设计范式。它是一种出色的方法,用于理解业务领域并将这些业务逻辑转化为软件模型。这种方法基于 Eric Evans 在 2003 年出版的《领域驱动设计》一书。我个人在多个项目中应用过它,发现它非常适合设计分布式系统,尤其是在定义服务边界方面。
图 1.8 展示了如何将视频流领域中的业务概念边界映射到微服务架构中。例如,用户、喜欢和视频等概念在不同微服务中得以实现,而某些概念,如视频,在不同的服务(如推荐和视频存储)中可能共享类似的表示。

限界上下文是 DDD 中的一个关键概念,它定义了一个模型内部的一致性边界。这有助于我们确定每个微服务应包含哪些功能和责任。
1.14.4 不要重复自己
在微服务架构中,我们对代码重复的容忍度可能比在单体架构中更高。虽然“不重复自己”(DRY)是一个广泛接受的编程原则,但在微服务中,避免过度共享代码比避免重复更为重要。这是因为错误的抽象比重复代码的潜在成本要高。
微服务的硬边界确实使得代码共享更具挑战性。尽管如此,DDD 鼓励概念上的重复,而非代码级的重复。当微服务由不同团队管理时,跨团队共享代码的挑战变得更加显著。
然而,还是存在在微服务之间有效共享代码的方法,我们不应完全放弃 DRY 原则。我们应该在适当的情况下在服务之间共享代码,同时避免创建复杂和脆弱的依赖关系。
1.14.5 服务的适当规模
在设计微服务时,确定每个服务应包含多少代码是一个富有引起争议的问题。没有固定的答案。虽然“微服务”这一术语可能误导人们认为服务越小越好,但事实并非如此。实际上,我认为“微服务”一词可能误导了很多人,使他们错误地追求服务的极小化。能不能重新将“微服务”称为“适度服务”呢?服务的大小应当适应你的具体需求,而不是盲目追求小。
过小的服务可能会导致系统间通信的增加,完成相同的工作需要更多的交互,这反而增加了系统的整体复杂性。因此,我们应该基于领域中的概念和自然的服务规模来构建服务,而不是单纯追求大小的最小化(参考 1.14.3 节的领域驱动设计)。
“每个微服务中应该包含多少代码?”这个问题与软件开发中的其他常见问题类似,例如“每个函数应包含多少代码?”或“每个类或模块应包含多少代码?”这些问题同样没有一个明确的答案,但在 1.14.2 节中提到的设计原则可以为我们提供指导。
1.14.6 深入了解设计知识
本书聚焦于构建微服务的实践方面,而不深入讨论设计理论。因此,后续内容将侧重于实践而非理论。无论你偏好哪种开发风格(如面向对象或函数式编程),市面上都有众多优秀的软件设计书籍可供选择。挑选一本你感兴趣的书籍进行阅读会是个好主意。
具体来说,如果你想深入了解微服务设计,我推荐阅读 S. Ramesh 的《设计微服务》(Manning,印刷中)。另一本非常适合与本书相配合阅读的关于设计分布式系统的好书是 Michael Geers 的《微前端实战》(Manning,2020)。若想获取关于本书的最新资讯,请加入邮件列表:www.bootstrapping-microservices.com。
1.15 示例应用程序
在本书结束前,我们会一起构建一个简单但功能完整的微服务应用程序。本节将介绍我们预期完成的产品概况。
我们计划构建的是一个视频流应用程序。每个优秀的产品都需要一个响亮的名字,在经过一系列的头脑风暴后,我选择了“FlixTube”作为我们未来视频流媒体平台的名称。毕竟,一切总得有个起点,不是吗?
为什么选择视频流作为示例?因为它是一个有趣的例子,并且相对容易实现(至少在基本层面上)。视频流也是微服务的经典用例,如 Netflix 的成功案例所示,据说他们运行着数百甚至数千个微服务。
通过 FlixTube 示例应用程序,我们将展示构建微服务应用程序的全过程。它将包括少数几个微服务,但构建的方式将支持未来的可扩展性,包括添加更多服务器、复制服务以增强可扩展性和冗余,并为各个服务设置独立的部署策略。
我们的应用将包括一个基于浏览器的前端,用户可以通过它浏览视频列表,选择视频并播放。我们将在第 3 章中构建并发布我们微服务的 Docker 镜像。在开发过程中,我们会使用 Docker Compose 来启动应用程序,这将在第 4 章和第 5 章中讨论。第 6 章、第 7 章和第 8 章将涉及将应用程序部署到生产环境并设置持续部署的内容。第 9 章我们将返回到开发中,进行一些自动化测试。
我们的应用程序将涵盖视频流、存储和上传的服务,以及一个面向客户的前端网关。第 10 章将部署完整的应用程序(参见图 1.9)。第 11 章和第 12 章将探讨这种架构如何随着应用程序的增长而扩展。你准备好开始构建微服务了吗?

总结
- 我们采用实践而非理论的方法学习构建微服务应用程序。
- 微服务是专注于单一功能的小型独立进程。
- 微服务应用程序由多个小进程组成,这些进程共同工作,实现应用程序的功能。
- 单体架构是由一个单独的大型服务组成的应用程序。
- 虽然构建微服务应用程序比构建单体应用程序复杂,但它并非不可达成。
- 微服务应用程序比单体应用程序更具灵活性、可扩展性、可靠性和容错性。
- 所有应用程序,无论是单体还是微服务,最终都会变得复杂——使用微服务是管理这种复杂性的一种方法,而不是被它压倒。
- 现代工具如 Docker、Kubernetes、Terraform 和 GitHub Actions 极大地简化了微服务应用程序的构建过程。
- 领域驱动设计(DDD)是微服务应用程序设计的有效方法。
- DDD 中的限界上下文概念与微服务的边界紧密相关。
- 单一职责原则、松耦合和高内聚是微服务设计的关键原则。
- 我们在本书中构建的示例应用程序 FlixTube 是一个基于微服务的视频流媒体平台。