本教程是一个学习路线中的第二个教程,将介绍如何将单体式应用模块化和容器化。
此学习路线包含以下教程:
- 概览
- 了解单体式应用
- 将单体式应用模块化(本教程)
- 准备好模块化应用以实现容器化
- 将模块化应用容器化
- 将应用部署到 GKE 集群
在上一个教程了解单体式应用中,您了解了一个名为 Cymbal Books 的单体式应用。您在本地机器上运行了单体式应用,并了解到单体式应用的不同部分通过其端点相互通信。
在本教程中,您将了解如何将单体式应用划分为模块,以便为容器化做好准备。您无需自行执行模块化步骤,因为代码已为您更新。您的任务是按照本教程操作,并探索仓库中应用的模块化版本,以查看它与原始单体式应用有何不同。
费用
您可以免费完成本教程。不过,按照本系列最后一个教程中的步骤操作会导致您的Google Cloud 账号产生费用。当您启用 GKE 并将 Cymbal Books 应用部署到 GKE 集群时,费用开始产生。这些费用包括按集群收取的 GKE 费用(如价格页面中所述)以及运行 Compute Engine 虚拟机的费用。
为避免产生不必要的费用,请务必在完成本教程后停用 GKE 或删除项目。
准备工作
在开始学习本教程之前,请确保您已完成第一个教程了解单体应用。在本教程中,您将在本地机器上运行模块化版本的 Cymbal Books。为此,您需要已设置您的环境。如果您已完成第一个教程,则表示克隆了 GitHub 仓库。Cymbal Books 应用的所有三个版本都位于该仓库中的以下文件夹内:
monolith/
modular/
containerized/
请先检查您的机器上是否有这些文件夹,然后再继续。此外,确保虚拟环境 book-review-env
处于活跃状态。如果您需要有关如何激活该环境的提醒,请参阅第一个教程中的创建并激活虚拟环境。激活环境可确保模块化版本的应用具备运行所需的一切条件。
什么是模块化?
在本教程中,您将学习如何将单体式应用模块化,以便为容器化做好准备。模块化是将单体式应用转变为模块化应用的过程。如上一教程中所述,单体式应用的显著特征是其组件无法独立运行或扩缩。模块化应用则不同:其功能划分为可独立运行和扩缩的模块。
虽然模块化和容器化通常同时完成,但在本系列教程中将它们视为单独的步骤,以帮助您清楚地了解每个概念。本教程介绍了如何将单体应用模块化,后续教程将介绍如何将模块化应用容器化。
增量模块化
在生产环境中,您通常一次模块化一个组件。您需要将该组件模块化,将模块与单体式应用集成,并确保一切正常运行,然后再处理下一个组件。这种混合状态称为“微块”,其中一些组件已模块化,而其他组件仍属于单体式应用。不过,在本教程中,应用的所有组件都同时模块化,以便提供有关如何模块化应用的完整示例。
如何将单体式应用模块化
在本部分中,您将学习 Cymbal Books 单体式应用如何拆分为单独的模块。我们提供了相关步骤,帮助您了解模块化流程,以便您将其应用于自己的应用。不过,在本教程中您无需执行这些步骤,因为克隆的仓库已包含模块化版本的应用:
确定应用的不同功能
将 Cymbal Books 单体应用模块化的第一步是确定其主要功能。在 Cymbal Books 示例应用中,单体式应用具有以下四种不同的功能:
- 提供首页
- 提供图书详情
- 提供图书评价
- 提供图书封面图片
创建模块
正如您在上一个教程中看到的,单体式应用是一个 Flask 应用,可实现上一部分中确定的四个功能作为路由处理程序。如需将应用模块化,您需要将每个路由处理程序都放入自己的 Flask 应用中。这样一来,您就拥有了四个 Flask 应用,每个应用中都包含一个路由处理程序,而不是一个包含四个路由处理程序的 Flask 应用。
下图展示了如何将单个 Flask 应用转换为四个单独的 Flask 应用:
在模块化应用中,每个 Flask 应用都独立运行,并监听不同的端口(8080、8081、8082、8083),如图所示。之所以需要此设置,是因为在本教程后面测试模块化应用时,您将在同一个机器上运行所有模块。每个应用都需要不同的端口号,以避免冲突。
首页模块有两项职责:提供首页并与其他模块通信,以收集需要在网页中显示的数据。其他每个模块都专注于一项功能:提供评价、详情或图片。这些模块不会相互通信,只会响应来自首页模块的请求。
虽然首页模块具有额外的协调作用,但应用仍然是真正的模块化应用,因为您可以更新任何模块,而不会影响其他模块。单个大型 Flask 应用已拆分为四个部分,每部分负责处理应用功能的特定部分。
实现模块之间的通信
创建模块后,下一步是确保它们可以相互通信。在 Cymbal Books 应用中,系统已为您实现此通信逻辑。在您下载的代码的 modular/
文件夹中,您可以看到应用的每个主要功能(提供首页、图书详情、评价和图片)都作为单独的 Flask 应用实现。每个应用都定义了自己的 HTTP 端点,模块通过向这些端点发送 HTTP 请求进行通信。
将 Cymbal Books 单体式应用模块化很简单。单体式应用具有明确定义的组件,这些组件作为路由处理程序实现,并且每个路由处理程序都具有明确定义的端点。将这些路由处理程序放入单独的 Flask 应用中时,它们仍能通过端点进行通信。将路由处理程序放入单独的 Flask 应用中的简单做法是创建模块并让模块相互通信。
模块间通信的一种常用方法是实现 REST API,该 API 允许模块相互发送 HTTP 请求。这就是它在 Cymbal Books 中的工作原理:每个模块都使用 Flask 的内置工具定义 REST 端点。另一种常用方法是 gRPC,它允许模块直接调用彼此的函数。
为什么 Cymbal Books 中的通信如此简单
模块化应用中的每个模块都是一个在 Web 服务器内运行的单独 Flask 应用。例如,首页模块用于提供首页,图书详情模块用于提供图书详情。由于 Web 服务器旨在处理 HTTP 请求和响应,因此模块之间的通信非常简单。每个模块都会公开其他模块可用于请求数据的端点。
仅向每个模块授予其所需数据的访问权限
为了正确将单体式应用模块化,您需要确保每个模块仅具有其所需数据的访问权限。这一原则称为数据隔离,是构建真正模块化架构的关键要素。
在模块化过程中,人们经常犯的一个错误是允许多个模块访问相同的数据,例如单个数据库。此类实现会造成以下问题:
- 紧密耦合:如果共享数据的结构发生变化(例如,数据库表被重命名或添加了列),则依赖于该数据的每个模块都必须更新。适当的模块化可以避免这个问题。
- 容错问题:当多个模块使用同一数据源时,一个模块中的运行时故障(例如无效查询或流量过载)可能会中断其他模块。系统的一部分发生故障可能会蔓延到系统的其他部分。
- 性能瓶颈:单个共享数据源可能会成为瓶颈,这意味着当多个模块尝试与其交互时,可能会拖慢整个应用的运行速度。
为避免这些问题,每个模块都应有自己的数据源。
如果 Cymbal Books 使用数据库来存储其数据,则您需要复制数据库或对其进行分区,以强制执行数据隔离,并确保每个模块仅访问所需的数据。复制涉及为每个模块维护单独的数据库副本,而分区则限制对特定表或行的访问。这两种方法都可以防止模块干扰彼此的数据。
下图比较了单体式图书应用的架构与图书应用的模块化架构:
单体式应用的实现不遵循数据隔离原则,因为单体式应用的功能会访问单个 data/
目录。
相比之下,模块化应用通过将数据拆分为单独的目录来实现一定程度的数据隔离,并确保每个模块仅与其指定的数据进行交互:
- 图书详情模块仅从
details_data/
目录获取数据。 - 图书评价模块仅从
reviews_data/
目录获取数据。 - 图片模块仅从
images/
目录获取数据。
在后续教程中,您将了解容器化应用如何进一步增强数据隔离。
您刚才看到的内容
在软件开发行业中,您经常遇到“微服务”和“分布式系统”这两个术语。本部分介绍了这些术语与 Cymbal Books 的模块化实现之间的关系。
微服务
微服务是执行特定任务的自主模块。这些模块通过端点等接口与其他模块通信。
Cymbal Books 的模块化版本中的每个模块都符合此定义,因此可以称为微服务。在后续教程中对模块化应用进行容器化时,在容器内运行的代码也可以称为微服务,因为它是模块内运行的同一代码。
分布式系统
分布式系统由独立模块组成,这些模块通过网络进行通信,以实现共同目标。这些模块可以在不同的机器上运行,但它们作为一个系统协同工作。
模块化 Cymbal Books 应用也符合此定义:其模块独立运行并通过 HTTP 交换数据,但它们共同作为一个系统。在下一部分中,为简单起见,您将在单个机器上运行所有模块,但这并非必需。每个模块都可以轻松在不同的服务器上运行,因此模块化版本的 Cymbal Books 应用可以归类为分布式系统。
测试模块化实现
现在,您已经了解 Cymbal Books 单体式应用是如何转换为模块为 Flask 应用的模块化应用,接下来您可以测试该应用并看到每个模块独立运行。
在本教程中,您将在同一机器上运行模块。不过,您也可以在单独的服务器上运行每个模块:由于每个模块都是自主的,因此可以通过端点与其他模块通信。
设置环境
请按照以下步骤准备测试:
在终端中,前往克隆仓库中的
modular
目录:cd modular
确保虚拟环境
book-review-env
处于活跃状态。如果您需要有关激活步骤的提醒,请参阅创建和激活虚拟环境。
启动 Flask 应用
/modular
文件夹包含一个 bash 脚本,用于同时启动所有 Flask 应用。该应用的每个模块都会监听唯一的端口,例如 8080 或 8081:
- 首页 Flask 应用 (home.py):端口 8080
- 图书详情 Flask 应用 (book_details.py):端口 8081
- 图书评价 Flask 应用 (book_reviews.py):端口 8082
- 图片 Flask 应用 (images.py):端口 8083
每个模块都需要监听唯一的端口号,因为所有模块都在同一机器上运行。如果每个模块位于不同的服务器上,则每个模块都可以监听相同的端口号,而不会造成端口冲突。
使用以下命令运行 bash 脚本:
bash ./start_services.sh
该脚本会为每个 Flask 应用(例如 home.py.log
、book_details.py.log
)创建一个单独的日志文件,以帮助您发现任何启动问题。脚本成功完成后,您会看到以下消息:
All services have been started. Access the app at http://localhost:8080/
测试每个 Flask 应用
通过在浏览器中访问以下网址来测试模块:
- 首页:
http://localhost:8080/
显示模块化 Cymbal Books 应用的首页。此页面通过向其他模块发出请求来检索图书详情、评价和图片。 - 图书详情:
http://localhost:8081/book/1
返回 ID 为 1 的图书的详细信息。此响应是 JSON 数据,应用随后会对其进行格式设置,并以更易于用户理解的方式显示。 - 图书评价:
http://localhost:8082/book/1/reviews
会提取并返回 ID 为 1 的图书的评价。评价采用 JSON 格式。首页模块会请求此数据,并将其集成到图书详情页面中。 - 图片:
http://localhost:8083/images/fungi_frontier.jpg
用于提供 Fungi Frontier 的图书封面图片。如果网址正确,图片应会直接在浏览器中加载。
停止 Flask 应用
完成测试后,使用以下命令停止所有 Flask 应用:
kill $(cat home.py.pid book_details.py.pid book_reviews.py.pid images.py.pid)
摘要
本教程介绍了如何将 Cymbal Books 单体应用模块化。此过程包含以下步骤:
- 确定应用的各个不同组件
- 创建模块
- 确保每个模块只能访问其所需的数据
然后,您在本地机器上测试了模块化实现。
后续步骤
在下一个教程准备好模块化应用以实现容器化中,您将学习如何通过更新端点以使用 Kubernetes Service 名称(而非 localhost
)来准备模块化应用以实现容器化。