原文:
zh.annas-archive.org/md5/1722062224f06acf668c638d8a7630bb译者:飞龙
前言
本书是关于使用 Spring Boot 3 和 Spring Cloud 构建生产就绪微服务的。十年前,当我开始探索微服务时,我正在寻找这样的书籍。
本书是在我学习并掌握了用于开发、测试、部署和管理协作微服务景观的开源软件之后开发的。
本书主要涵盖 Spring Boot、Spring Cloud、Docker、Kubernetes、Istio、EFK 堆栈、Prometheus 和 Grafana。这些开源工具各自都非常出色,但理解如何以有利的方式将它们结合使用可能具有挑战性。在某些领域,它们相互补充,但在其他领域它们存在重叠,对于特定情况选择哪一个并不明显。
这是一本实践性书籍,逐步描述了如何将这些开源工具结合使用。这是我十年前开始学习微服务时一直在寻找的书籍,但现在它涵盖了开源工具的更新版本。
本书面向的对象
本书面向的是希望学习如何从头开始构建微服务景观并在本地或云中部署它们的 Java 和 Spring 开发人员以及架构师,使用 Kubernetes 作为容器编排器,Istio 作为服务网格。开始阅读本书不需要对微服务架构有任何了解。
本书涵盖的内容
第一章,微服务简介,将帮助您理解本书的基本前提——微服务——以及与之相关的必要概念和设计模式。
第二章,Spring Boot 简介,将向您介绍 Spring Boot 3 以及本书第一部分将使用的其他开源项目:用于开发 RESTful API 的 Spring WebFlux、用于生成基于 OpenAPI 的 API 文档的 springdoc-openapi、用于在 SQL 和 NoSQL 数据库中存储数据的 Spring Data、用于基于消息的微服务的 Spring Cloud Stream 以及用于将微服务作为容器运行的 Docker。
第三章,创建一组协作微服务,将教会您从头开始创建一组协作微服务。您将使用 Spring Initializr 根据 Spring Framework 6.0 和 Spring Boot 3.0 创建基于 Spring 框架的骨架项目。
目标是创建三个核心服务(将处理自己的资源)和一个复合服务,该服务使用三个核心服务来聚合复合结果。在章节的末尾,您将学习如何添加基于 Spring WebFlux 的非常基本的 RESTful API。在接下来的章节中,将向这些微服务添加更多功能。
第四章, 使用 Docker 部署我们的微服务,将教会您如何使用 Docker 部署微服务。您将学习如何添加 Dockerfile 和 docker-compose 文件,以便使用单个命令启动整个微服务景观。然后,您将学习如何使用多个 Spring 配置文件来处理有和无 Docker 的配置。
第五章, 使用 OpenAPI 添加 API 描述,将使您熟悉使用 OpenAPI 记录微服务暴露的 API。您将使用 springdoc-openapi 工具注解服务,以动态创建基于 OpenAPI 的 API 文档。关键亮点将是如何使用 Swagger UI 在网页浏览器中测试 API。
第六章, 添加持久性,将向您展示如何向微服务的数据中添加持久性。您将使用 Spring Data 为两个核心微服务设置和访问 MongoDB 文档数据库中的数据,并为剩余的微服务访问 MySQL 关系数据库中的数据。在运行集成测试时,将使用 Testcontainers 启动数据库。
第七章, 开发响应式微服务,将向您讲解为什么以及何时响应式方法很重要,以及如何开发端到端响应式服务。您将学习如何开发和测试非阻塞同步 RESTful API 和异步事件驱动服务。您还将学习如何使用 MongoDB 的响应式非阻塞驱动程序,以及如何使用传统的阻塞代码来处理 MySQL。
第八章, Spring Cloud 简介,将向您介绍 Spring Cloud 以及本书中将使用的 Spring Cloud 组件。
第九章, 使用 Netflix Eureka 添加服务发现,将向您展示如何在 Spring Cloud 中使用 Netflix Eureka 来添加服务发现功能。这将通过向系统景观中添加一个基于 Netflix Eureka 的服务发现服务器来实现。然后,您将配置微服务使用 Spring Cloud LoadBalancer 来查找其他微服务。您将了解微服务是如何自动注册的,以及当新实例可用时,通过 Spring Cloud LoadBalancer 自动负载均衡到新实例的流量。
第十章, 使用 Spring Cloud Gateway 在边缘服务器后面隐藏微服务,将指导您如何使用 Spring Cloud Gateway 在边缘服务器后面隐藏微服务,并且只向外部消费者公开选择 API。您还将学习如何从外部消费者隐藏微服务的内部复杂性。这将通过向系统景观中添加一个基于 Spring Cloud Gateway 的边缘服务器并配置它只公开公共 API 来实现。
第十一章,保护 API 访问安全,将解释如何使用 OAuth 2.0 和 OpenID Connect 保护暴露的 API。你将学习如何将基于 Spring Authorization Server 的 OAuth 2.0 授权服务器添加到系统架构中,以及如何配置边缘服务器和组合服务以要求由该授权服务器签发的有效访问令牌。
你将学习如何通过边缘服务器暴露授权服务器,并使用 HTTPS 确保其与外部消费者的通信安全。最后,你将学习如何用 Auth0 的外部 OpenID Connect 提供者替换内部的 OAuth 2.0 授权服务器。
第十二章,集中式配置,将处理如何从所有微服务中收集配置文件到一个中央存储库,并使用配置服务器在运行时将配置分发到微服务。你还将学习如何将 Spring Cloud Config Server 添加到系统架构中,并配置所有微服务使用 Spring Config Server 来获取其配置。
第十三章,使用 Resilience4j 提高弹性,将解释如何使用 Resilience4j 的能力来防止例如“失败链”反模式。你将学习如何向组合服务添加重试机制和断路器,如何配置断路器在电路打开时快速失败,以及如何利用回退方法创建尽力而为的响应。
第十四章,理解分布式跟踪,将展示如何使用 Zipkin 收集和可视化跟踪信息。你还将使用 Micrometer Tracing 将跟踪 ID 添加到请求中,以便可视化协作微服务之间的请求链。
第十五章,Kubernetes 简介,将解释 Kubernetes 的核心概念以及如何执行一个示例部署。你还将学习如何使用 Minikube 在本地设置 Kubernetes 以用于开发和测试目的。
第十六章,将我们的微服务部署到 Kubernetes,将展示如何在 Kubernetes 上部署微服务。你还将学习如何使用 Helm 打包和配置微服务以便在 Kubernetes 上部署。Helm 将被用于部署针对不同运行环境(如测试和生产环境)的微服务。最后,你将学习如何用 Kubernetes 内置的服务发现支持替换 Netflix Eureka,这基于 Kubernetes 服务对象和 kube-proxy 运行时组件。
第十七章,通过实现 Kubernetes 功能简化系统架构,将解释如何使用 Kubernetes 功能作为替代上一章中介绍的 Spring Cloud 服务的方案。你将了解为什么以及如何用 Kubernetes Secrets 和 ConfigMaps 替换 Spring Cloud Config Server,以及为什么以及如何用 Kubernetes Ingress 对象替换 Spring Cloud Gateway,并学习如何添加 cert-manager 以自动为外部 HTTPS 端点提供和轮换证书。
第十八章,使用服务网格提高可观察性和管理,将介绍服务网格的概念,并解释如何使用 Istio 在 Kubernetes 中运行时实现服务网格。你将学习如何使用服务网格进一步改进微服务景观的弹性、安全性、流量管理和可观察性。
第十九章,使用 EFK Stack 进行集中日志记录,将解释如何使用 Elasticsearch、Fluentd 和 Kibana(EFK Stack)来收集、存储和可视化来自微服务的日志流。你将学习如何在 Minikube 中部署 EFK Stack,以及如何使用它来分析收集到的日志记录并找到涉及跨多个微服务的请求处理的微服务的日志输出。你还将学习如何使用 EFK Stack 进行根本原因分析。
第二十章,监控微服务,将展示如何使用 Prometheus 和 Grafana 监控在 Kubernetes 中部署的微服务。你将学习如何使用 Grafana 中的现有仪表板来监控不同类型的指标,并学习如何创建自己的仪表板。最后,你将学习如何在 Grafana 中创建警报,当配置的阈值被选定的指标超过时,这些警报将被用来发送带有警报的电子邮件。
第二十一章,macOS 安装说明,将展示如何在 Mac 上安装本书中使用的工具。涵盖了基于 Intel 和 Apple 硅(ARM64)的 Mac。
第二十二章,使用 WSL 2 和 Ubuntu 的 Microsoft Windows 安装说明,将展示如何使用 Windows Subsystem for Linux (WSL) v2 在 Windows PC 上安装本书中使用的工具。
第二十三章,原生编译的 Java 微服务,将展示如何创建编译为原生代码的基于 Spring 的微服务。你将学习如何使用 Spring Framework 6 和 Spring Boot 3 中的新原生映像支持以及底层的 GraalVM Native Image 编译器。与使用常规的 Java 虚拟机相比,这将导致可以几乎立即启动的微服务。
每章结束时,你将找到一些简单的问题,这些问题将帮助你回顾章节中涵盖的一些内容。“评估” 是一个可以在 GitHub 仓库中找到的文件,其中包含这些问题的答案。
为了充分利用本书
推荐具备 Java 和 Spring 的基本理解。
要运行本书中的所有内容,您需要拥有一台基于 Mac Intel 或 Apple 硅的电脑或至少 16GB 内存的 PC,尽管建议您至少有 24GB,因为随着本书末尾微服务领域变得更加复杂和资源密集,所需的资源也更多。
要获取软件要求的完整列表以及设置环境以便能够跟随本书的详细说明,请参阅第二十一章(针对 macOS)和第二十二章(针对 Windows)。
下载示例代码文件
本书代码包托管在 GitHub 上,网址为github.com/PacktPublishing/Microservices-with-Spring-Boot-and-Spring-Cloud-Third-Edition。我们还有其他丰富的图书和视频代码包,可在github.com/PacktPublishing/找到。请查看它们!
下载彩色图像
我们还提供了一份包含本书中使用的截图/图表彩色图像的 PDF 文件。您可以从这里下载:packt.link/XHJmq。
使用的约定
本书使用了多种文本约定。
CodeInText: 表示文本中的代码单词、数据库表名、文件夹名、文件名、文件扩展名、路径名、虚拟 URL、用户输入和 Twitter 昵称。例如:“java插件将 Java 编译器添加到项目中。”
代码块设置如下:
package se.magnus.microservices.core.product;
@SpringBootApplication
public class ProductServiceApplication {
当我们希望您注意代码块中的特定部分时,相关的行或项目将以粗体显示:
package se.magnus.microservices.core.product;
**@SpringBootTest**
class ProductServiceApplicationTests {
任何命令行输入或输出都按以下方式编写:
mkdir some-temp-folder
cd some-temp-folder
粗体: 表示新术语、重要单词或屏幕上看到的单词。例如,菜单或对话框中的单词在文本中显示如下:“使用Spring Initializr为每个微服务生成骨架项目。”
警告或重要注意事项看起来像这样。
技巧和窍门看起来像这样。
联系我们
我们始终欢迎读者的反馈。
一般反馈: 请通过feedback@packtpub.com发送电子邮件,并在邮件主题中提及本书的标题。如果您对本书的任何方面有疑问,请通过questions@packtpub.com与我们联系。
勘误: 尽管我们已经尽最大努力确保内容的准确性,但错误仍然可能发生。如果您在这本书中发现了错误,如果您能向我们报告,我们将不胜感激。请访问www.packtpub.com/submit-errata,点击提交勘误,并填写表格。
盗版: 如果您在互联网上以任何形式遇到我们作品的非法副本,如果您能向我们提供位置地址或网站名称,我们将不胜感激。请通过copyright@packtpub.com与我们联系,并提供材料的链接。
如果您想成为一名作者:如果您在某个领域有专业知识,并且您有兴趣撰写或为书籍做出贡献,请访问authors.packtpub.com。
分享您的想法
一旦您阅读了《使用 Spring Boot 3 和 Spring Cloud 的微服务,第三版》,我们很乐意听听您的想法!请点击此处直接进入此书的亚马逊评论页面并分享您的反馈。
您的评论对我们和科技社区非常重要,并将帮助我们确保我们提供高质量的内容。
下载此书的免费 PDF 副本
感谢您购买此书!
您喜欢在路上阅读,但无法携带您的印刷书籍到处走?您的电子书购买是否与您选择的设备不兼容?
别担心,现在每购买一本 Packt 图书,您都可以免费获得该书的 DRM 免费 PDF 版本。
在任何地方、任何设备上阅读。从您最喜欢的技术书籍中直接搜索、复制和粘贴代码到您的应用程序中。
优惠远不止这些,您还可以获得独家折扣、时事通讯和丰富的免费内容,每天直接发送到您的邮箱。
按照以下简单步骤获取好处:
- 扫描下面的二维码或访问以下链接
packt.link/free-ebook/9781805128694
-
提交您的购买证明
-
那就结束了!我们将直接将您的免费 PDF 和其他好处发送到您的电子邮件。
目录
-
前言
-
本书面向的对象
-
本书涵盖的内容
-
联系我们
-
分享您的想法
-
-
微服务简介
-
技术要求
-
我的微服务之路
-
自主软件组件的优势
-
自主软件组件的挑战
-
进入微服务
-
一个示例微服务景观
-
-
定义微服务
-
微服务面临的挑战
-
微服务的设计模式
-
服务发现
-
问题
-
解决方案
-
解决方案需求
-
-
边缘服务器
-
问题
-
解决方案
-
解决方案需求
-
-
响应式微服务
-
问题
-
解决方案
-
解决方案需求
-
-
集中配置
-
问题
-
解决方案
-
解决方案需求
-
-
集中日志分析
-
问题
-
解决方案
-
解决方案需求
-
-
分布式追踪
-
问题
-
解决方案
-
解决方案需求
-
-
断路器
-
问题
-
解决方案
-
解决方案需求
-
-
控制循环
-
问题
-
解决方案
-
解决方案需求
-
-
集中监控和警报
-
问题
-
解决方案
-
解决方案需求
-
-
-
软件使能器
-
其他重要注意事项
-
总结
-
-
Spring Boot 简介
-
技术要求
-
Spring Boot
-
约定优于配置和胖 JAR 文件
-
设置 Spring Boot 应用程序的代码示例
-
神奇的 @SpringBootApplication 注解
-
组件扫描
-
基于 Java 的配置
-
-
Spring Boot 3.0 的新特性
-
迁移 Spring Boot 2 应用程序
-
-
Spring WebFlux
-
设置 REST 服务的代码示例
-
启动依赖项
-
属性文件
-
示例 RestController
-
-
-
springdoc-openapi
-
Spring 数据
-
实体
-
存储库
-
-
Spring Cloud Stream
- 发送和接收消息的代码示例
-
Docker
-
总结
-
问题
-
-
创建一组协作的微服务
-
技术要求
-
介绍微服务景观
-
由微服务处理的信息
-
产品服务
-
审查服务
-
推荐服务
-
产品组合服务
-
与基础设施相关的信息
-
-
临时替换服务发现
-
-
生成骨架微服务
-
使用 Spring Initializr 生成骨架代码
-
在 Gradle 中设置多项目构建
-
-
添加 RESTful API
-
添加 API 和 util 项目
-
api 项目
-
util 项目
-
-
实现我们的 API
-
-
添加复合微服务
-
API 类
-
属性
-
集成组件
-
复合 API 实现
-
-
添加错误处理
-
全局 REST 控制器异常处理器
-
API 实现中的错误处理
-
API 客户端中的错误处理
-
-
手动测试 API
-
在隔离中添加自动微服务测试
-
添加微服务景观的半自动化测试
- 尝试测试脚本
-
总结
-
问题
-
-
使用 Docker 部署我们的微服务
-
技术要求
-
Docker 简介
- 运行我们的第一个 Docker 命令
-
在 Docker 中运行 Java
-
限制可用的 CPU
-
限制可用的内存
-
-
使用 Docker 与单个微服务
-
源代码中的更改
-
构建 Docker 镜像
-
启动服务
-
以分离模式运行容器
-
-
使用 Docker Compose 管理微服务景观
-
源代码中的更改
-
启动微服务架构
-
-
自动化测试协作微服务
- 调试测试运行
-
总结
-
问题
-
-
使用 OpenAPI 添加 API 描述
-
技术要求
-
Springdoc-openapi 使用介绍
-
将 springdoc-openapi 添加到源代码中
-
向 Gradle 构建文件添加依赖项
-
向 ProductCompositeService 添加 OpenAPI 配置和通用 API 文档
-
向 ProductCompositeService 接口添加 API 特定文档
-
-
构建和启动微服务架构
-
尝试 OpenAPI 文档
-
总结
-
问题
-
-
添加持久性
-
技术要求
-
章节目标
-
向核心微服务添加持久层
-
添加依赖项
-
使用实体类存储数据
-
在 Spring Data 中定义仓库
-
-
编写关注持久性的自动化测试
-
使用 Testcontainers
-
编写持久性测试
-
-
在服务层中使用持久层
-
记录数据库连接 URL
-
添加新的 API
-
从服务层调用持久层
-
声明 Java Bean 映射器
-
更新服务测试
-
-
扩展组合服务 API
-
向组合服务 API 添加新操作
-
向集成层添加方法
-
实现新的组合 API 操作
-
更新组合服务测试
-
-
向 Docker Compose 架构添加数据库
-
Docker Compose 配置
-
数据库连接配置
-
MongoDB 和 MySQL CLI 工具
-
-
对新 API 和持久层进行手动测试
-
更新微服务景观的自动化测试
-
总结
-
问题
-
-
开发反应式微服务
-
技术要求
-
在非阻塞同步 API 和事件驱动异步服务之间选择
-
开发非阻塞同步 REST API
-
Project Reactor 简介
-
使用 Spring Data for MongoDB 进行非阻塞持久化
- 测试代码的变更
-
核心服务中的非阻塞 REST API
-
API 变更
-
服务实现变更
-
测试代码的变更
-
处理阻塞代码
-
-
组合服务中的非阻塞 REST API
-
API 变更
-
服务实现变更
-
集成层的变更
-
测试代码的变更
-
-
-
开发事件驱动异步服务
-
处理消息挑战
-
消费者组
-
重试和死信队列
-
保证顺序和分区
-
-
定义主题和事件
-
Gradle 构建文件的变更
-
在核心服务中消费事件
-
声明消息处理器
-
服务实现变更
-
为消费事件添加配置
-
测试代码的变更
-
-
在组合服务中发布事件
-
在集成层发布事件
-
为发布事件添加配置
-
测试代码的变更
-
-
-
运行反应式微服务景观的手动测试
-
保存事件
-
添加健康 API
-
不使用分区使用 RabbitMQ
-
使用分区与 RabbitMQ
-
使用每个主题两个分区的 Kafka
-
-
运行反应式微服务架构的自动化测试
-
总结
-
问题
-
-
Spring Cloud 简介
-
技术要求
-
Spring Cloud 的演变
-
使用 Netflix Eureka 进行服务发现
-
使用 Spring Cloud Gateway 作为边缘服务器
-
使用 Spring Cloud Config 进行集中配置
-
使用 Resilience4j 提高弹性
- Resilience4j 中熔断器的示例用法
-
使用 Micrometer Tracing 和 Zipkin 进行分布式跟踪
-
总结
-
问题
-
-
使用 Netflix Eureka 添加服务发现
-
技术要求
-
介绍服务发现
-
基于 DNS 的服务发现的问题
-
服务发现挑战
-
在 Spring Cloud 中使用 Netflix Eureka 进行服务发现
-
-
设置 Netflix Eureka 服务器
-
将微服务连接到 Netflix Eureka 服务器
-
设置开发使用的配置
-
Eureka 配置参数
-
配置 Eureka 服务器
-
配置 Eureka 服务器的客户端
-
-
尝试发现服务
-
扩大规模
-
缩小规模
-
使用 Eureka 服务器进行破坏性测试
-
停止 Eureka 服务器
-
启动产品服务的额外实例
-
-
-
再次启动 Eureka 服务器
-
总结
-
问题
-
-
使用 Spring Cloud Gateway 在边缘服务器后面隐藏微服务
-
技术要求
-
将边缘服务器添加到我们的系统架构中
-
设置 Spring Cloud Gateway
-
添加组合健康检查
-
配置 Spring Cloud Gateway
- 路由规则
-
-
尝试使用边缘服务器
-
检查 Docker 引擎外部暴露的内容
-
尝试路由规则
-
通过边缘服务器调用产品组合 API
-
通过边缘服务器调用 Swagger UI
-
通过边缘服务器调用 Eureka
-
基于主机头进行路由
-
-
-
总结
-
问题
-
-
保护 API 访问
-
技术要求
-
OAuth 2.0 和 OpenID Connect 简介
-
介绍 OAuth 2.0
-
介绍 OpenID Connect
-
-
保护系统景观
-
使用 HTTPS 保护外部通信
- 在运行时替换自签名证书
-
保护发现服务器的访问
-
Eureka 服务器的变化
-
Eureka 客户端的变化
-
-
添加本地授权服务器
-
使用 OAuth 2.0 和 OpenID Connect 保护 API
-
边缘服务器和产品组合服务的变化
-
仅针对产品组合服务的变化
- 更改以允许 Swagger UI 获取访问令牌
-
测试脚本的变化
-
-
使用本地授权服务器进行测试
-
构建和运行自动化测试
-
测试受保护发现服务器
-
获取访问令牌
-
使用客户端凭据授权流程获取访问令牌
-
使用授权码授权流程获取访问令牌
-
-
使用访问令牌调用受保护 API
-
使用 OAuth 2.0 测试 Swagger UI
-
-
使用外部 OpenID Connect 提供者进行测试
-
在 Auth0 中设置和配置账户
-
应用必要的更改以使用 Auth0 作为 OpenID 提供者
-
更改 OAuth 资源服务器中的配置
-
更改测试脚本以从 Auth0 获取访问令牌
-
-
使用 Auth0 作为 OpenID Connect 提供者运行测试脚本
-
使用客户端凭据授权流程获取访问令牌
-
使用授权代码授权流程获取访问令牌
-
使用 Auth0 访问令牌调用受保护的 API
-
获取有关用户的额外信息
-
-
摘要
-
问题
-
-
集中式配置
-
技术要求
-
Spring Cloud Config Server 简介
-
选择配置存储库的存储类型
-
决定初始客户端连接
-
保护配置
-
保护传输中的配置
-
保护静态配置
-
-
介绍配置服务器 API
-
-
设置配置服务器
-
在边缘服务器上设置路由规则
-
为与 Docker 一起使用配置配置服务器
-
-
配置配置服务器的客户端
- 配置连接信息
-
结构化配置存储库
-
尝试使用 Spring Cloud Config Server
-
构建和运行自动化测试
-
使用配置服务器 API 获取配置
-
加密和解密敏感信息
-
-
摘要
-
问题
-
-
使用 Resilience4j 改进弹性
-
技术要求
-
介绍 Resilience4j 弹性机制
-
介绍断路器
-
介绍时间限制器
-
介绍重试机制
-
-
将弹性机制添加到源代码中
-
添加可编程延迟和随机错误
-
API 定义中的更改
-
产品组合微服务中的更改
-
产品微服务中的更改
-
-
添加断路器和时间限制器
-
将依赖项添加到构建文件中
-
在源代码中添加注解
-
添加快速失败回退逻辑
-
添加配置
-
-
添加重试机制
-
添加重试注解
-
添加配置
-
-
添加自动化测试
-
-
尝试断路器和重试机制
-
构建和运行自动化测试
-
验证在正常操作下电路是否闭合
-
在出错时强制打开断路器
-
再次关闭断路器
-
尝试由随机错误引起的重试
-
-
总结
-
问题
-
-
理解分布式跟踪
-
技术要求
-
使用 Micrometer Tracing 和 Zipkin 介绍分布式跟踪
-
将分布式跟踪添加到源代码中
-
将依赖项添加到构建文件中
-
添加 Micrometer Tracing 和 Zipkin 的配置
-
将 Zipkin 添加到 Docker Compose 文件中
-
添加对缺乏支持反应式客户端的解决方案
-
向现有跨度添加自定义跨度自定义标签
-
添加自定义跨度
-
向现有跨度添加自定义标签
-
-
-
尝试分布式跟踪
-
启动系统景观
-
发送成功的 API 请求
-
发送失败的 API 请求
-
发送触发异步处理的 API 请求
-
-
总结
-
问题
-
-
Kubernetes 简介
-
技术要求
-
介绍 Kubernetes 概念
-
介绍 Kubernetes API 对象
-
介绍 Kubernetes 运行时组件
-
使用 Minikube 创建 Kubernetes 集群
-
使用 Minikube 配置文件
-
使用 Kubernetes CLI,kubectl
-
使用 kubectl 管理上下文
-
创建 Kubernetes 集群
-
-
尝试一个示例部署
-
管理本地 Kubernetes 集群
-
休眠和恢复 Kubernetes 集群
-
终止 Kubernetes 集群
-
-
总结
-
问题
-
-
将我们的微服务部署到 Kubernetes
-
技术要求
-
用 Kubernetes 服务替换 Netflix Eureka
-
介绍 Kubernetes 的使用方法
-
使用 Spring Boot 对优雅关闭和存活性/就绪性探测的支持
-
介绍 Helm
-
运行 Helm 命令
-
查看 Helm 图表
-
Helm 模板和值
-
通用库图表
-
ConfigMap 模板
-
秘密模板
-
服务模板
-
部署模板
-
-
组件图表
-
环境图表
-
-
将应用部署到 Kubernetes 进行开发和测试
-
构建 Docker 镜像
-
解决 Helm 图表依赖关系
-
将应用部署到 Kubernetes
-
用于 Kubernetes 的测试脚本更改
-
测试部署
- 测试 Spring Boot 对优雅关闭和存活性/就绪性探测的支持
-
-
将应用部署到 Kubernetes 进行预发布和生产
-
源代码中的更改
-
将应用部署到 Kubernetes
-
清理
-
-
总结
-
问题
-
-
实现 Kubernetes 功能以简化系统架构
-
技术要求
-
替换 Spring Cloud Config Server
- 替换 Spring Cloud Config Server 所需的更改
-
替换 Spring Cloud Gateway
- 替换 Spring Cloud Gateway 所需的更改
-
自动化证书提供
-
使用 Kubernetes ConfigMaps、Secrets、Ingress 和 cert-manager 进行测试
-
轮换证书
-
部署到 Kubernetes 进行预演和生产
-
-
验证微服务在没有 Kubernetes 的情况下工作
-
Docker Compose 文件中的更改
-
使用 Docker Compose 进行测试
-
-
摘要
-
问题
-
-
使用服务网格提高可观察性和管理
-
技术要求
-
使用 Istio 介绍服务网格
-
介绍 Istio
-
将 Istio 代理注入到微服务中
-
介绍 Istio API 对象
-
-
简化微服务景观
-
用 Istio 入口网关替换 Kubernetes Ingress 控制器
-
用 Istio 的 Jaeger 组件替换 Zipkin 服务器
-
-
在 Kubernetes 集群中部署 Istio
- 设置访问 Istio 服务的权限
-
创建服务网格
-
源代码更改
-
在 _istio_base.yaml 模板中的内容
-
在 _istio_dr_mutual_tls.yaml 模板中的内容
-
-
运行命令创建服务网格
-
记录跟踪和跨度 ID 的传播
-
-
观察服务网格
-
保护服务网格
-
使用 HTTPS 和证书保护外部端点
-
使用 OAuth 2.0/OIDC 访问令牌验证外部请求
-
使用双向身份验证(mTLS)保护内部通信
-
-
确保服务网格具有弹性
-
通过注入故障测试弹性
-
通过注入延迟测试弹性
-
-
执行零停机更新
-
源代码更改
-
虚拟服务和目标规则
-
部署和服务
-
在 prod-env Helm 图表中整合所有内容
-
-
部署微服务的 v1 和 v2 版本,并通过路由到 v1 版本
-
验证所有流量最初都流向微服务的 v1 版本
-
运行金丝雀测试
-
运行蓝绿部署
-
kubectl patch 命令的简要介绍
-
执行蓝绿部署
-
-
-
使用 Docker Compose 运行测试
-
总结
-
问题
-
-
使用 EFK 堆栈进行集中式日志记录
-
技术要求
-
介绍 Fluentd
-
Fluentd 概述
-
配置 Fluentd
-
-
在 Kubernetes 上部署 EFK 堆栈
-
构建和部署我们的微服务
-
部署 Elasticsearch 和 Kibana
-
manifest 文件的概述
-
运行部署命令
-
-
部署 Fluentd
-
manifest 文件的概述
-
运行部署命令
-
-
-
尝试 EFK 堆栈
-
初始化 Kibana
-
分析日志记录
-
从微服务中发现日志记录
-
执行根本原因分析
-
-
总结
-
问题
-
-
监控微服务
-
技术要求
-
使用 Prometheus 和 Grafana 进行性能监控简介
-
修改源代码以收集应用程序度量
-
构建和部署微服务
-
使用 Grafana 仪表板监控微服务
-
为测试安装本地邮件服务器
-
配置 Grafana
-
启动负载测试
-
使用 Kiali 内置仪表板
-
导入现有的 Grafana 仪表板
-
开发您自己的 Grafana 仪表板
-
检查 Prometheus 指标
-
创建仪表板
-
尝试新的仪表板
-
-
导出和导入 Grafana 仪表板
-
-
在 Grafana 中设置警报
-
设置基于邮件的通知通道
-
在断路器上设置警报
-
尝试断路器警报
-
-
总结
-
问题
-
-
macOS 安装说明
-
技术要求
-
安装工具
-
安装 Homebrew
-
使用 Homebrew 安装工具
-
不使用 Homebrew 安装工具
-
在基于 Intel 的 Mac 上安装工具
-
在基于 Apple 硅的 Mac 上安装工具
-
-
安装后的操作
-
验证安装
-
-
访问源代码
-
使用 IDE
-
代码结构
-
-
总结
-
-
带有 WSL 2 和 Ubuntu 的 Microsoft Windows 安装说明
-
技术要求
-
安装工具
-
在 Windows 上安装工具
-
与默认 Ubuntu 服务器一起安装 WSL 2
-
在 WSL 2 上安装新的 Ubuntu 22.04 服务器
-
安装 Windows Terminal
-
安装 Docker Desktop for Windows
-
安装 Visual Studio Code 及其远程 WSL 扩展
-
-
在 WSL 2 的 Linux 服务器上安装工具
-
使用 apt install 安装工具
-
使用 SDKman 安装 Java 和 Spring Boot CLI
-
使用 curl 和 install 安装剩余的工具
-
验证安装
-
-
-
访问源代码
- 代码结构
-
总结
-
-
原生编译的 Java 微服务
-
技术要求
-
何时原生编译 Java 源代码
-
介绍 GraalVM 项目
-
介绍 Spring 的 AOT 引擎
-
处理原生编译问题
-
源代码中的更改
-
Gradle 构建文件的更新
-
提供可达性元数据和自定义提示
-
在 application.yml 文件中构建时启用 Spring Bean
-
更新运行时属性
-
配置 GraalVM 原生镜像跟踪代理
-
test-em-all.bash 验证脚本的更新
-
-
测试和编译原生镜像
-
安装 GraalVM 及其原生镜像编译器
-
运行跟踪代理
-
运行原生测试
-
为当前操作系统创建原生镜像
-
将原生镜像作为 Docker 镜像创建
-
-
使用 Docker Compose 进行测试
-
禁用 AOT 模式测试基于 Java VM 的微服务
-
启用 AOT 模式测试基于 Java VM 的微服务
-
测试原生编译的微服务
-
-
使用 Kubernetes 进行测试
-
摘要
-
问题
-
-
你可能喜欢的其他书籍
-
索引
标记
-
封面
-
索引
第一章:微服务简介
本书并不盲目赞扬微服务。相反,它讲述了我们如何利用其优势,同时能够处理构建可扩展、弹性好且可管理的微服务的挑战。
作为本书的介绍,本章将涵盖以下主题:
-
我进入微服务之路
-
什么是基于微服务的架构?
-
微服务的挑战
-
应对挑战的设计模式
-
可以帮助我们处理这些挑战的软件赋能者
-
本书未涵盖的其他重要考虑因素
技术要求
本章不需要安装。然而,你可能对查看 C4 模型规范感兴趣,c4model.com,因为本章的插图灵感来源于 C4 模型。
本章不包含任何源代码。
我进入微服务之路
当我在 2014 年首次了解到微服务的概念时,我意识到我多年来一直在不知情的情况下开发微服务(好吧,有点像)。我参与了一个 2009 年开始的项目,我们基于一组分离的特性开发了一个平台。该平台被交付给多个客户,他们在本地部署了它。为了使客户能够轻松选择他们想要从平台中使用的功能,每个特性都被开发为一个自主软件组件;也就是说,它有自己的持久化数据,并且仅通过定义良好的 API 与其他组件进行通信。
由于我无法讨论项目中具体的功能,我已经将组件的名称进行了泛化,这些组件被标记为从组件 A到组件 F。平台的组成作为一组组件的图示如下:
https://github.com/OpenDocCN/freelearn-java-zh/raw/master/docs/msvc-spbt3-spcld/img/B19825_01.png
图 1.1:平台组成
从图中我们还可以看到,每个组件都有自己的持久化数据存储,并且不与其他组件共享数据库。
每个组件都是使用 Java 和 Spring 框架开发的,打包为 WAR 文件,并在 Java EE Web 容器中作为 Web 应用部署,例如 Apache Tomcat。根据客户的具体要求,平台可以部署在单个或多个服务器上。一个双节点部署可能如下所示:
https://github.com/OpenDocCN/freelearn-java-zh/raw/master/docs/msvc-spbt3-spcld/img/B19825_02.png
图 1.2:双节点部署场景
自主软件组件的好处
从这个项目中,我了解到将平台的职能分解为一系列自主软件组件提供了许多好处:
-
客户可以在自己的系统环境中部署平台的部分,通过其定义良好的 API 将其与其现有系统集成。
以下是一个示例,其中一位客户决定从平台部署组件 A、组件 B、组件 D和组件 E,并将它们与客户系统环境中的两个现有系统系统 A和系统 B集成:
https://github.com/OpenDocCN/freelearn-java-zh/raw/master/docs/msvc-spbt3-spcld/img/B19825_03.png
图 1.3:平台的局部部署
-
另一位客户可以选择用客户系统环境中已有的实现来替换平台功能的一部分,这可能会要求在平台 API 中采用一些现有功能。
以下是一个示例,其中客户已将平台中的组件 C和组件 F替换为自己的实现:
https://github.com/OpenDocCN/freelearn-java-zh/raw/master/docs/msvc-spbt3-spcld/img/B19825_04.png
图 1.4:替换平台的部分
-
平台中的每个组件都可以单独交付和升级。得益于使用定义良好的 API,一个组件可以升级到新版本,而无需依赖于其他组件的生命周期。
以下是一个示例,其中组件 A已从版本v1.1升级到v1.2。调用组件 A的组件 B不需要升级,因为它使用的是定义良好的 API;也就是说,升级后它仍然是相同的(或者至少是向后兼容的):
https://github.com/OpenDocCN/freelearn-java-zh/raw/master/docs/msvc-spbt3-spcld/img/B19825_05.png
图 1.5:升级特定组件
- 由于使用了定义良好的 API,平台中的每个组件也可以独立于其他组件扩展到多个服务器。扩展可以是满足高可用性要求或处理更高请求量的需要。在这个特定项目中,这是通过在运行 Java EE Web 容器的多个服务器前手动设置负载均衡器来实现的。组件 A已扩展到三个实例的示例如下:
https://github.com/OpenDocCN/freelearn-java-zh/raw/master/docs/msvc-spbt3-spcld/img/B19825_06.png
图 1.6:扩展平台
自主软件组件的挑战
我的团队还了解到,将平台分解引入了许多新的挑战,这些挑战在我们开发更传统的、单体式应用时并未遇到(至少没有达到同样的程度):
-
向组件添加新实例需要手动配置负载均衡器并手动设置新节点。这项工作既耗时又容易出错。
-
平台最初容易受到它所通信的其他系统引起的错误的影响。如果一个系统未能及时响应平台发送的请求,平台很快就会耗尽关键资源,例如,操作系统线程,尤其是在面对大量并发请求时。这导致平台中的组件挂起甚至崩溃。由于平台中的大多数通信都是基于同步通信,一个组件崩溃可能导致级联故障;也就是说,崩溃组件的客户端也可能在一段时间后崩溃。这被称为故障链。
-
保持所有组件实例的配置一致并更新迅速成为一个问题,导致大量手动和重复性的工作。这有时会导致质量问题。
-
监控平台在延迟问题和硬件使用(例如,CPU、内存、磁盘和网络的使用)方面的状态,与监控单体应用的单一实例相比,要复杂得多。
-
从多个分布式组件收集日志文件并关联相关日志事件也是困难的,但由于组件数量固定且事先已知,因此是可行的。
随着时间的推移,我们通过内部开发的工具和针对手动处理这些挑战的详细说明,解决了前面提到的大多数挑战。操作的规模通常在可以接受的水平上,即使它们不是理想的,手动发布组件新版本和处理运行时问题也是可行的。
进入微服务时代
2014 年了解基于微服务的架构让我意识到,其他项目也一直在努力应对类似的挑战(部分原因不同于我之前描述的原因,例如,大型云服务提供商满足 Web 规模需求)。许多微服务先驱已经发布了他们所学到经验教训的细节。从这些经验教训中学习非常有趣。
许多先驱最初开发了单体应用,从商业角度来看,它们非常成功。但随着时间的推移,这些单体应用变得越来越难以维护和演进。它们也变得难以扩展到最大机器的能力之外(也称为垂直扩展)。最终,先驱们开始寻找将单体应用拆分成更小组件的方法,这些组件可以独立发布和扩展。可以通过水平扩展来实现小组件的扩展,即在多个较小的服务器上部署组件,并在其前面放置一个负载均衡器。如果在云中执行,扩展能力可能是无限的——这仅仅是一个关于你将多少虚拟服务器引入的问题(假设你的组件可以在大量实例上扩展,但关于这一点稍后还会详细说明)。
在 2014 年,我还了解到了一些新的开源项目,它们提供了工具和框架,简化了微服务的开发,并可用于处理基于微服务架构带来的挑战。
其中一些如下:
-
Pivotal 发布了Spring Cloud,它封装了Netflix OSS的部分功能,以提供动态服务发现、配置管理、分布式跟踪、断路器等功能。
-
我还了解到了Docker和容器革命,这对于缩小开发和生产之间的差距非常有帮助。能够将组件打包不仅作为可部署的运行时工件(例如,Java
war或jar文件),而且作为一个完整的镜像,准备好在运行 Docker 的服务器上启动为容器,这对于开发和测试来说是一个巨大的进步。
现在,将容器视为一个隔离的过程。我们将在第四章,使用 Docker 部署我们的微服务中了解更多关于容器的内容。
-
容器引擎,例如 Docker,仅靠它本身还不足以在生产环境中使用容器。需要某种东西来确保所有容器都处于运行状态,并且能够在多台服务器上扩展容器,从而提供高可用性和增加计算资源。
-
这些类型的产品被称为容器编排器。在过去几年中,许多产品已经发展起来,例如 Apache Mesos、Swarm 模式下的 Docker、Amazon ECS、HashiCorp Nomad 和Kubernetes。Kubernetes 最初是由 Google 开发的。当 Google 在 2015 年发布 v1.0 版本时,它还将 Kubernetes 捐赠给了CNCF (
www.cncf.io/)。在 2018 年,Kubernetes 成为了一种事实上的标准,既可以作为本地部署的预包装版本使用,也可以作为大多数主要云服务提供商的服务。
如kubernetes.io/blog/2015/04/borg-predecessor-to-kubernetes/中所述,Kubernetes 实际上是一个基于开源的重写,名为Borg的内部容器编排器,在 Kubernetes 项目成立之前,Google 已经使用了十多年。
- 2018 年,我开始了解服务网格的概念以及服务网格如何补充容器编排器,进一步减轻微服务的责任,使其可管理和有弹性。
一个示例微服务景观
由于本书无法涵盖我刚才提到的所有技术方面,我将专注于自 2014 年以来我在客户项目中证明有用的部分。我将描述它们如何一起使用来创建可管理、可扩展和有弹性的协作微服务。
本书中的每一章都将解决一个特定的问题。为了展示事物是如何相互关联的,我将使用一组协作的微服务,我们将在整本书中逐步发展这些微服务。微服务景观将在第三章,创建一组协作微服务中描述;目前,只需知道它看起来是这样的:
https://github.com/OpenDocCN/freelearn-java-zh/raw/master/docs/msvc-spbt3-spcld/img/B19825_07.png
图 1.7:本书中使用的基于微服务的系统景观
注意,这是一个非常小的协作微服务系统景观。我们将在接下来的章节中添加的周围支持服务对于这些微服务来说可能看起来过于复杂。但请记住,本书中提出的解决方案旨在支持一个更大的系统景观。
现在我们已经介绍了微服务的潜在利益和挑战,让我们开始探讨如何定义微服务。
定义微服务
微服务架构是将单体应用程序拆分为更小的组件,这实现了两个主要目标:
-
更快的开发,实现持续部署
-
更容易扩展,手动或自动
微服务本质上是一个独立的软件组件,它可以独立升级、替换和扩展。为了能够作为一个自主组件,它必须满足以下某些标准:
-
它必须符合无共享架构;也就是说,微服务之间不共享数据库中的数据!
-
它必须仅通过定义良好的接口进行通信,无论是使用 API 和同步服务,还是最好通过异步发送消息。使用的 API 和消息格式必须是稳定的、有良好文档的,并遵循定义的版本策略。
-
它必须作为独立的运行时进程部署。每个微服务的实例都在一个独立的运行时进程中运行,例如,一个 Docker 容器。
-
微服务实例是无状态的,因此进入微服务的请求可以由其任何实例处理。
使用一组协作的微服务,我们可以部署到多个较小的服务器上,而不是被迫部署到单个大型服务器上,就像部署单体应用程序时必须做的那样。
由于已经满足了上述标准,将单个微服务扩展到更多实例(例如,使用更多虚拟服务器)比扩展大型单体应用程序要容易得多。
利用云中可用的自动扩展功能也是一种可能性,但对于大型单体应用程序来说通常不可行。与升级大型单体应用程序相比,升级或甚至替换单个微服务也更容易。
以下图表说明了这一点,其中单体应用程序已被分割成六个微服务,所有这些微服务都已部署到单独的服务器上。其中一些微服务也独立于其他微服务进行了扩展:
https://github.com/OpenDocCN/freelearn-java-zh/raw/master/docs/msvc-spbt3-spcld/img/B19825_08.png
图 1.8:将单体分解为微服务
我经常从客户那里收到的一个非常频繁的问题是:
微服务应该有多大?
我尝试使用以下经验法则:
-
足够小,可以放入开发者的脑海中
-
足够大,不会危及性能(即延迟)和/或数据一致性(存储在不同微服务中的数据之间的 SQL 外键不再是你可以理所当然的事情)
因此,总结来说,微服务架构本质上是一种架构风格,我们将单体应用程序分解成一组协作的自主软件组件。其动机是使开发更快,并使扩展应用程序更容易。
在更好地理解如何定义微服务之后,我们可以继续详细说明与微服务系统景观相关的挑战。
微服务的挑战
在自主软件组件的挑战部分,我们已经看到了自主软件组件可能带来的某些挑战(它们同样适用于微服务),如下所示:
-
许多使用同步通信的小型组件可能会引起连锁故障问题,尤其是在高负载下
-
保持许多小型组件的配置更新可能具有挑战性
-
跟踪一个涉及许多组件且正在处理中的请求是很困难的,例如,在进行根本原因分析时,每个组件都本地存储日志记录
-
在组件级别上分析硬件资源的使用也可能具有挑战性
-
手动配置和管理许多小型组件可能会变得成本高昂且容易出错
将应用程序分解成一组自治组件的另一个缺点(但最初可能并不明显)是它们形成了一个分布式系统。众所周知,分布式系统由于其本质而非常难以处理。这一点已经为人所知多年(但在许多情况下直到被证明不同才被忽视)。我最喜欢的关于这一事实的引言来自彼得·德克斯特,他在 1994 年提出了以下观点:
分布式计算的 8 大谬误:基本上,每个人在第一次构建分布式应用程序时,都会做出以下八个假设。所有这些假设在长期内都被证明是错误的,并且都导致了巨大的麻烦和痛苦的学习经历:
网络是可靠的
延迟为零
带宽是无限的
网络是安全的
拓扑结构不会改变
只有一个管理员
传输成本为零
网络是同质的
——彼得·德克斯特,1994 年
通常,基于这些错误假设构建的微服务会导致容易受到暂时性网络故障和其他微服务实例中发生的问题的影响。当系统景观中微服务的数量增加时,问题的可能性也会增加。一个很好的经验法则是根据系统景观中始终存在某些问题的假设来设计您的微服务架构。微服务架构需要设计来处理这些问题,包括检测问题和重启失败的组件。此外,在客户端,确保不要向失败的微服务实例发送请求。当问题得到纠正时,应恢复对之前失败的微服务的请求;也就是说,微服务客户端需要具有弹性。当然,所有这些都需要完全自动化。对于大量微服务,操作员手动处理这是不可行的!
本主题的范围很广,但我们将暂时限制自己,并继续学习微服务的设计模式。
微服务设计模式
本主题将涵盖使用设计模式来缓解前述部分中描述的微服务挑战。在本书的后续部分,我们将看到如何使用 Spring Boot、Spring Cloud、Kubernetes 和 Istio 来实现这些设计模式。
设计模式的概念实际上相当古老;它是由克里斯托弗·亚历山大在 1977 年发明的。本质上,设计模式是关于在特定上下文中描述一个可重用解决方案来解决问题的。使用设计模式中的经过验证和测试的解决方案可以节省大量时间,并且与自行发明解决方案相比,可以提高实现的品质。
我们将要介绍的设计模式如下:
-
服务发现
-
边缘服务器
-
响应式微服务
-
中央配置
-
中央日志分析
-
分布式跟踪
-
电路断路器
-
控制回路
-
中央监控和警报
此列表并非旨在全面;相反,它是一个最小列表,列出了处理我们之前描述的挑战所需的设计模式。
我们将采用轻量级的方法来描述设计模式,并重点关注以下内容:
-
问题
-
解决方案
-
解决方案的要求
在整本书中,我们将更深入地探讨如何应用这些设计模式。这些设计模式的应用背景是一个由协作微服务组成的系统景观,其中微服务通过同步请求(例如,使用 HTTP)或发送异步消息(例如,使用消息代理)相互通信。
服务发现
服务发现模式存在以下问题、解决方案和解决方案要求。
问题
客户端如何找到微服务和它们的实例?
微服务实例在启动时通常会分配动态分配的 IP 地址,例如,在容器中运行时。这使得客户端难以向一个例如通过 HTTP 暴露 REST API 的微服务发起请求。考虑以下图表:
https://github.com/OpenDocCN/freelearn-java-zh/raw/master/docs/msvc-spbt3-spcld/img/B19825_09.png
图 1.9:服务发现问题
解决方案
向系统景观添加一个新的组件——一个服务发现服务——以跟踪当前可用的微服务和其实例的 IP 地址。
解决方案要求
一些解决方案的要求如下:
-
自动注册/注销微服务和它们的实例,随着它们的到来和离去。
-
客户端必须能够向微服务的一个逻辑端点发起请求。请求将被路由到可用的微服务实例之一。
-
必须在可用的实例之间对微服务的请求进行负载均衡。
-
我们必须能够检测当前不健康的实例,以便请求不会路由到它们。
实施说明:正如我们将在第九章“使用 Netflix Eureka 添加服务发现”、第十五章“Kubernetes 简介”和第十六章“将我们的微服务部署到 Kubernetes”中看到的那样,此设计模式可以使用两种不同的策略实现:
-
客户端路由:客户端使用一个库与服务发现服务通信,以找出应向其发送请求的正确实例。
-
服务器端路由:服务发现服务的基础设施还公开了一个反向代理,所有请求都发送到该代理。反向代理代表客户端将请求转发到适当的微服务实例。
边缘服务器
边缘服务器模式具有以下问题、解决方案和解决方案要求。
问题
在微服务系统景观中,在许多情况下,希望将一些微服务暴露给系统景观之外,并将剩余的微服务隐藏对外访问。暴露的微服务必须保护免受恶意客户端的请求。
解决方案
向系统景观中添加一个新的组件,一个边缘服务器,所有进入的请求都将通过它:
https://github.com/OpenDocCN/freelearn-java-zh/raw/master/docs/msvc-spbt3-spcld/img/B19825_10.png
图 1.10:边缘服务器设计模式
实施说明:边缘服务器通常表现得像一个反向代理,可以与发现服务集成以提供动态负载均衡功能。
解决方案要求
一些解决方案要求如下:
-
隐藏那些不应暴露在其上下文之外的内部服务;也就是说,只将请求路由到配置为允许外部请求的微服务
-
暴露外部服务并保护它们免受恶意请求;也就是说,使用标准协议和最佳实践,如 OAuth、OIDC、JWT 令牌和 API 密钥,以确保客户端是可信的。
反应式微服务
反应式微服务模式存在以下问题、解决方案和解决方案要求。
问题
传统上,作为 Java 开发者,我们习惯于使用阻塞 I/O 实现同步通信,例如,通过 HTTP 的 RESTful JSON API。使用阻塞 I/O 意味着操作系统为请求的长度分配了一个线程。
如果并发请求数量增加,服务器可能会在操作系统中耗尽可用的线程,导致从响应时间变长到服务器崩溃的问题。使用微服务架构通常会使这个问题更加严重,通常使用一系列协作的微服务来处理请求。参与服务请求的微服务越多,可用的线程就会越快耗尽。
解决方案
使用非阻塞 I/O 来确保在等待另一个服务(例如数据库或另一个微服务)的处理过程中不会分配线程。
解决方案要求
一些解决方案要求如下:
-
在可行的情况下,使用异步编程模型,发送消息而不等待接收者处理它们。
-
如果更喜欢同步编程模型,请使用可以使用非阻塞 I/O 执行同步请求的响应式框架,在等待响应时不分配线程。这将使微服务更容易扩展以处理增加的工作负载。
-
微服务还必须设计成具有弹性和自我修复能力。弹性意味着即使在它依赖的服务中有一个失败的情况下也能产生响应;自我修复意味着一旦失败的服务再次运行,微服务必须能够重新使用它。
2013 年,在**《反应式宣言》**中确立了设计反应式系统的关键原则(www.reactivemanifesto.org/)。
根据宣言,反应式系统的基础是它们是消息驱动的;它们使用异步通信。这使得它们具有弹性,即可伸缩性,以及弹性,即对失败的容忍性。弹性和弹性共同使反应式系统能够始终及时响应。
中央配置
中央配置模式存在以下问题、解决方案和解决方案需求。
问题
传统上,应用程序与其配置一起部署,例如,一组环境变量和/或包含配置信息的文件。给定一个基于微服务架构的系统景观,即有大量部署的微服务实例,会出现一些查询:
-
我如何获得所有运行中的微服务实例中配置的完整视图?
-
我如何更新配置并确保所有受影响的微服务实例都正确更新?
解决方案
向系统架构中添加一个新的组件,即配置服务器,以存储所有微服务的配置,如下所示:
https://github.com/OpenDocCN/freelearn-java-zh/raw/master/docs/msvc-spbt3-spcld/img/B19825_11.png
图 1.11:中央配置设计模式
解决方案需求
使一组微服务的配置信息能够存储在一个地方,并为不同的环境(例如,dev、test、qa和prod)设置不同的配置。
集中日志分析
集中日志分析存在以下问题、解决方案和解决方案需求。
问题
传统上,应用程序将日志事件写入存储在应用程序运行的服务器本地文件系统中的日志文件。给定一个基于微服务架构的系统景观,即在大量较小的服务器上部署了大量的微服务实例,我们可以提出以下问题:
-
当每个微服务实例将其自己的本地日志文件写入时,我如何获得系统景观中正在发生的事情的概览?
-
我如何知道是否有任何微服务实例遇到麻烦并开始向它们的日志文件中写入错误消息?
-
如果最终用户开始报告问题,我如何找到相关的日志消息;也就是说,我如何确定哪个微服务实例是问题的根本原因?以下图表说明了问题:
https://github.com/OpenDocCN/freelearn-java-zh/raw/master/docs/msvc-spbt3-spcld/img/B19825_12.png
图 1.12:微服务将日志文件写入其本地文件系统
解决方案
添加一个新组件,它可以管理集中式日志记录,并能够执行以下操作:
-
检测新的微服务实例并从它们收集日志事件
-
在中央数据库中以结构化和可搜索的方式解释和存储日志事件
-
提供查询和分析日志事件的 API 和图形工具
解决方案要求
一些解决方案要求如下:
-
微服务将日志事件流式传输到标准系统输出,
stdout。与将日志事件写入特定微服务的日志文件相比,这使得日志收集器更容易找到日志事件。 -
微服务使用下一节中关于分布式跟踪设计模式描述的相关 ID 标记日志事件。
-
定义了一个规范化的日志格式,以便日志收集器可以在将日志事件存储到中央数据库之前,将来自微服务的日志事件转换为规范化日志格式。在规范化日志格式中存储日志事件是能够查询和分析收集到的日志事件所必需的。
分布式跟踪
分布式跟踪存在以下问题、解决方案和解决方案要求。
问题
必须能够跟踪在处理系统景观外部请求时在微服务之间流动的请求和消息。
一些故障场景的例子如下:
-
如果最终用户开始提交关于特定故障的支持案例,我们如何识别导致问题的微服务,即根本原因?
-
如果某个支持案例提到了与特定实体相关的问题,例如,特定的订单号,我们如何找到与处理此特定订单相关的日志消息——例如,所有参与处理该订单的微服务的日志消息?
-
如果最终用户开始提交关于响应时间过长的问题支持案例,我们如何识别调用链中哪个微服务导致了延迟?
以下图示展示了这一点:
https://github.com/OpenDocCN/freelearn-java-zh/raw/master/docs/msvc-spbt3-spcld/img/B19825_13.png
图 1.13:分布式跟踪问题
解决方案
为了跟踪协作微服务之间的处理过程,我们需要确保所有相关请求和消息都带有共同的关联 ID,并且关联 ID 是所有日志事件的一部分。基于关联 ID,我们可以使用集中式日志服务来查找所有相关的日志事件。如果其中一个日志事件还包含有关业务标识符的信息,例如,客户的 ID、产品或订单,我们可以使用关联 ID 找到该业务标识符的所有相关日志事件。
为了能够分析协作微服务调用链中的延迟,我们必须能够收集请求、响应和消息进入和退出每个微服务的时间戳。
解决方案要求
解决方案要求如下:
-
在一个已知位置为所有传入或新的请求和事件分配唯一的关联 ID,例如,带有标准化名称的报头
-
当微服务发出出站请求或发送消息时,它必须将关联 ID 添加到请求和消息中。
-
所有日志事件都必须包含预定义格式的关联 ID,以便集中日志服务可以从日志事件中提取关联 ID 并使其可搜索。
-
当请求、响应和消息进入和退出微服务实例时,必须创建跟踪记录。
断路器
断路器模式存在以下问题、解决方案和解决方案需求。
问题
使用同步交互的微服务系统景观可能会暴露于连锁故障之中。如果一个微服务停止响应,其客户端可能会遇到问题,并停止对客户端的请求做出响应。问题可能会递归地传播到整个系统景观,并使其主要部分失效。
这在同步请求使用阻塞 I/O 执行的情况下尤其常见,即阻塞底层操作系统的线程,在请求处理期间。结合大量并发请求和开始以意外缓慢的速度响应的服务,线程池可能会迅速耗尽,导致调用者挂起和/或崩溃。这种故障会迅速传播到调用者的调用者,依此类推。
解决方案
如果检测到调用服务的问题,添加一个断路器以阻止调用者发出新的出站请求。
解决方案需求
解决方案需求如下:
-
打开电路并快速失败(不等待超时),如果检测到服务存在问题。
-
用于故障纠正的探测(也称为半开电路);也就是说,定期允许单个请求通过,以查看服务是否已恢复正常运行。
-
关闭电路,如果探测到服务再次正常运行。这种能力非常重要,因为它使系统景观对这些类型的问题具有弹性;换句话说,它能够自我修复。
下图展示了在微服务系统景观中所有同步通信都通过断路器进行的场景。所有断路器都是关闭的;它们允许流量通过,除了检测到请求服务存在问题的一个断路器(针对微服务 E)。因此,这个断路器是打开的,并使用快速失败逻辑;也就是说,它不调用失败的服务,并等待超时发生。相反,微服务 E可以立即返回响应,在响应前可选地应用一些回退逻辑:
https://github.com/OpenDocCN/freelearn-java-zh/raw/master/docs/msvc-spbt3-spcld/img/B19825_14.png
图 1.14:断路器设计模式
控制循环
控制循环模式有以下问题、解决方案和解决方案需求。
问题
在一个拥有大量微服务实例的系统景观中,这些实例分散在多个服务器上,手动检测和纠正崩溃或挂起的微服务实例等问题非常困难。
解决方案
在系统景观中添加一个新的组件,一个控制循环。这个过程如下所示:
https://github.com/OpenDocCN/freelearn-java-zh/raw/master/docs/msvc-spbt3-spcld/img/B19825_15.png
图 1.15:控制循环设计模式
解决方案要求
控制循环将不断观察系统景观的实际状态,将其与操作员指定的期望状态进行比较。如果两个状态不同,它将采取行动使实际状态等于期望状态。
实施说明:在容器世界中,通常使用容器编排器(如 Kubernetes)来实现此模式。我们将在第十五章“Kubernetes 简介”中了解更多关于 Kubernetes 的内容。
集中式监控和警报
对于这个模式,我们遇到了以下问题、解决方案和解决方案要求。
问题
如果观察到的响应时间或/和硬件资源的使用变得无法接受地高,很难发现问题的根本原因。例如,我们需要能够分析每个微服务的硬件资源消耗。
解决方案
为了遏制这种情况,我们在系统景观中添加了一个新的组件,一个监控服务,它能够收集每个微服务实例级别的硬件资源使用指标。
解决方案要求
解决方案要求如下:
-
它必须能够从系统景观中使用的所有服务器收集指标,这包括自动扩展服务器。
-
它必须能够检测在可用服务器上启动的新微服务实例,并从它们那里开始收集指标。
-
它必须能够提供 API 和图形工具,用于查询和分析收集的指标。
-
必须能够定义当指定的指标超过指定的阈值时触发的警报。
下面的截图显示了 Grafana,它可视化 Prometheus 的指标,我们将在第二十章“监控微服务”中了解这个监控工具:
https://github.com/OpenDocCN/freelearn-java-zh/raw/master/docs/msvc-spbt3-spcld/img/B19825_16.png
图 1.16:使用 Grafana 进行监控
这是一个详尽的列表!我相信这些设计模式帮助你更好地理解了微服务面临的挑战。接下来,我们将继续学习关于软件使能器的知识。
软件使能器
如我们之前提到的,我们有一些非常好的开源工具可以帮助我们满足对微服务的期望,更重要的是,处理随之而来的新挑战:
-
Spring Boot,一个应用程序框架
-
Spring Cloud/Netflix OSS,一个应用程序框架和现成服务的混合体
-
Docker,一个在单个服务器上运行容器的工具
-
Kubernetes,一个容器编排器,它管理着一群运行容器的服务器
-
Istio,一个服务网格实现
以下表格映射了我们将需要处理这些挑战的设计模式,以及本书中将用于实现这些设计模式的相应开源工具:
| 设计模式 | Spring Boot | Spring Cloud | Kubernetes | Istio |
|---|---|---|---|---|
| 服务发现 | Netflix Eureka 和 Spring Cloud LoadBalancer | Kubernetes kube-proxy 和服务资源 | ||
| 边缘服务器 | Spring Cloud Gateway 和 Spring Security OAuth | Kubernetes 入口控制器 | Istio 入口网关 | |
| 响应式微服务 | Project Reactor 和 Spring WebFlux | |||
| 中心配置 | Spring Config Server | Kubernetes ConfigMaps 和 Secrets | ||
| 集中日志分析 | Elasticsearch、Fluentd 和 Kibana。注意:实际上不是 Kubernetes 的组成部分,但可以轻松地与 Kubernetes 一起部署和配置 | |||
| 分布式追踪 | Micrometer 追踪和 Zipkin | Jaeger | ||
| 断路器 | Resilience4j | 异常检测 | ||
| 控制循环 | Kubernetes 控制管理器 | |||
| 集中监控和警报 | Kiali、Grafana 和 Prometheus |
图 1.17:将设计模式映射到开源工具
请注意,Spring Cloud、Kubernetes 或 Istio 中的任何一个都可以用来实现某些设计模式,例如服务发现、边缘服务器和中心配置。我们将在本书的后面讨论使用这些替代方案的优缺点。
在介绍了本书中将使用的模式和工具之后,我们将通过一些相关领域来结束本章,这些领域也很重要,但本书没有涉及。
其他重要考虑因素
在实施微服务架构时要想取得成功,还需要考虑许多相关领域。本书不会涵盖这些领域;相反,我将在以下内容中简要提及它们:
-
DevOps 的重要性:微服务架构的一个好处是它能够缩短交付时间,在极端情况下,甚至允许持续交付新版本。为了能够快速交付,你需要建立一个遵循“你构建它,你就运行它”这一格言的组织。这意味着开发者不再被允许简单地传递软件的新版本给运维团队。相反,开发和运维组织需要更加紧密地合作,组建起对单个微服务(或一组相关微服务)端到端生命周期负责的团队。除了 Dev/ops 的组织部分,团队还需要自动化交付链,即构建、测试、打包和将微服务部署到各种部署环境中的步骤。这被称为建立交付管道。
-
组织方面和康威定律:微服务架构可能对组织产生影响的另一个有趣方面是康威定律,该定律表述如下:
“任何设计系统(广义上)的组织都会产生一个结构,其结构与组织的沟通结构相匹配。”
—— Melvyn Conway,1967
这意味着,基于技术专长(例如,用户体验、业务逻辑和数据库团队)的传统方法来组织大型 IT 团队,将导致一个庞大的三层应用——通常是一个庞大的单体应用,具有一个独立的 UI 部署单元、一个处理业务逻辑的单元和一个大型数据库的部署单元。
要成功交付基于微服务架构的应用程序,组织需要转变为与一个或一组相关微服务合作的团队。该团队必须具备执行这些微服务所需的技能,例如,业务逻辑的语言和框架以及用于持久化数据的数据库技术。
-
将单体应用分解为微服务:最困难的决策之一(如果执行不当则代价高昂)是如何将单体应用分解为一系列协作的微服务。如果这样做的方式不正确,你最终会遇到以下问题:
-
缓慢的交付:业务需求的变化将影响太多的微服务,从而导致额外的工作。
-
不良性能:为了执行特定的业务功能,需要在各种微服务之间传递大量请求,从而导致响应时间过长。
-
数据不一致:由于相关数据被分离到不同的微服务中,随着时间的推移,由不同微服务管理的数据可能会出现不一致性。
为微服务找到合适的边界的一个好方法是应用领域驱动设计及其边界上下文的概念。根据埃里克·埃文斯(Eric Evans)的说法,一个边界上下文是:
“一个边界(通常是子系统或特定团队的作业)的描述,其中定义并适用特定的模型。”
这意味着由边界上下文定义的微服务将拥有其自身数据的明确模型。
-
-
API 设计的重要性:如果一个微服务组公开了一个通用的、外部可用的 API,那么这个 API 需要易于理解,并遵循以下指南:
-
如果在多个 API 中使用相同的概念,那么在命名和数据类型方面应该有相同的描述。
-
允许 API 以独立但受控的方式演变非常重要。这通常需要为 API 应用适当的版本控制方案,例如,
semver.org/。这意味着在特定时期内支持 API 的多个主要版本,允许 API 客户端以自己的节奏迁移到新的主要版本。
-
-
从本地到云的迁移路径:许多公司今天在本地运行他们的工作负载,但正在寻找将工作负载的部分迁移到云的方法。由于大多数云提供商今天都提供Kubernetes as a Service,一个吸引人的迁移方法可以是将工作负载首先迁移到本地的 Kubernetes(无论是否作为微服务)上,然后重新部署到由首选云提供商提供的 Kubernetes as a Service 服务上。
-
微服务的良好设计原则,12 因素应用:12 因素应用(
12factor.net)是一套设计原则,用于构建可以在云中部署的软件。这些设计原则中的大多数都可以独立于部署位置和方式(即在云中或本地)构建微服务。本书将涵盖其中的一些原则,例如配置、进程和日志,但并非全部。
第一章节的内容就到这里!我希望这能给你一个关于微服务及其带来的挑战的良好基本概念,以及我们将在本书中涵盖的内容概述。
摘要
在本章的介绍中,我描述了自己进入微服务的方式,并简要探讨了它们的历史。我们定义了微服务是什么——一种具有特定要求的自主分布式组件。我们还探讨了基于微服务的架构的优缺点。
为了应对这些挑战,我们定义了一套设计模式,并简要地将开源产品(如 Spring Boot、Spring Cloud、Kubernetes 和 Istio)的功能映射到设计模式上。
你现在渴望开发你的第一个微服务,对吧?在下一章中,你将介绍 Spring Boot 和我们将使用的互补开源工具,以开发我们的第一个微服务。
加入我们的 Discord 社区
加入我们的 Discord 空间,与作者和其他读者进行讨论:
第二章:Spring Boot 简介
在本章中,我们将介绍如何使用 Spring Boot 构建一组协作的微服务,重点关注如何开发提供业务价值的功能。我们将对上一章中指出的微服务挑战进行一定程度上的考虑,但在后续章节中将对它们进行全面解决。
我们将开发包含业务逻辑的微服务,基于普通的Spring Beans,并使用Spring WebFlux公开 REST API。API 将根据 OpenAPI 规范使用springdoc-openapi进行文档编制。为了使微服务处理的数据持久化,我们将使用Spring Data在 SQL 和 NoSQL 数据库中存储数据。
自 2018 年 3 月 Spring Boot v2.0 版本发布以来,开发反应式微服务(包括非阻塞同步 REST API)变得更加容易。为了开发基于消息的异步服务,我们将使用Spring Cloud Stream。有关更多信息,请参阅第一章,微服务简介中的反应式微服务部分。
2022 年 11 月,Spring Boot 3.0 版本发布。它基于 Spring Framework 6.0 和 Jakarta EE 9,同时也兼容 Jakarta EE 10。作为最低 Java 版本,需要 Java 17,这是当前的长期支持(LTS)版本。
最后,我们将使用Docker将我们的微服务作为容器运行。这将使我们能够通过单个命令启动和停止包括数据库服务器和消息代理在内的微服务景观。
这有很多技术和框架,所以让我们简要地了解一下它们各自的内容!
在本章中,我们将介绍以下开源项目:
- Spring Boot
(本节还包括 v3.0 的新功能和如何迁移 v2 应用程序的概述。)
-
Spring WebFlux
-
springdoc-openapi -
Spring Data
-
Spring Cloud Stream
-
Docker
每个产品的更多详细信息将在后续章节中提供。
技术要求
本章不包含任何可以下载的源代码,也不需要安装任何工具。
Spring Boot
Spring Boot,以及它所基于的 Spring 框架,是一个用于在 Java 中开发微服务的优秀框架。
当 Spring Framework v1.0 版本在 2004 年发布时,其主要目标之一是解决过于复杂的J2EE标准(即Java 2 Platform, Enterprise Edition)及其臭名昭著且重量级的部署描述符。Spring 框架提供了一个基于依赖注入概念的更轻量级的开发模型。与 J2EE 中的部署描述符相比,Spring 框架还使用了更轻量级的 XML 配置文件。
要使 J2EE 标准的问题更加严重,重量级的部署描述符实际上有两种类型:
-
标准部署描述符,以标准化的方式描述配置
-
供应商特定的部署描述符,将配置映射到供应商应用程序服务器中的特定功能
在 2006 年,J2EE 被更名为Java EE,即Java 平台,企业版。在 2017 年,Oracle 将 Java EE 提交给 Eclipse 基金会。2018 年 2 月,Java EE 被更名为 Jakarta EE。新的名称,Jakarta EE,也影响了由标准定义的 Java 包的名称,要求开发者在升级到 Jakarta EE 时进行包重命名,如迁移 Spring Boot 2 应用程序部分所述。多年来,随着 Spring 框架越来越受欢迎,Spring 框架的功能也显著增长。慢慢地,使用不再轻量级的 XML 配置文件设置 Spring 应用程序所带来的负担成为一个问题。
在 2014 年,Spring Boot v1.0 发布,解决了这些问题!
约定优于配置和胖 JAR 文件
Spring Boot 通过对如何设置 Spring 框架的核心模块和第三方产品(如用于日志记录或连接到数据库的库)有明确的意见,旨在快速开发生产就绪的 Spring 应用程序。Spring Boot 通过默认应用一系列约定来实现这一点,最大限度地减少了对配置的需求。在需要时,可以通过编写一些配置来覆盖每个约定,具体情况具体分析。这种设计模式被称为约定优于配置,并最大限度地减少了初始配置的需求。
在需要时,我认为使用 Java 和注解编写配置最佳。虽然它们比 Spring Boot 之前要小得多,但基于 XML 的老式配置文件仍然可以使用。
除了使用约定优于配置之外,Spring Boot 还倾向于基于独立 JAR 文件(也称为胖 JAR 文件)的运行时模型。在 Spring Boot 之前,运行 Spring 应用程序最常见的方式是将它作为 WAR 文件部署在 Java EE 服务器上,例如 Apache Tomcat。Spring Boot 仍然支持 WAR 文件部署。
一个胖 JAR 文件不仅包含应用程序本身的类和资源文件,还包含应用程序所依赖的所有 JAR 文件。这意味着胖 JAR 文件是运行应用程序所需的唯一 JAR 文件;也就是说,我们只需要将一个 JAR 文件传输到我们想要运行应用程序的环境中,而不是将应用程序的 JAR 文件以及所有依赖的 JAR 文件一起传输。
启动胖 JAR 文件不需要安装单独的 Java EE 服务器,例如 Apache Tomcat。相反,可以使用简单的命令(如 java -jar app.jar)启动,使其成为在 Docker 容器中运行的理想选择!例如,如果 Spring Boot 应用程序使用 HTTP 来公开 REST API,它也将包含一个嵌入的 Web 服务器。
设置 Spring Boot 应用程序的代码示例
为了更好地理解这意味着什么,让我们看看一些源代码示例。
我们在这里只将查看一些小的代码片段来指出主要特性。对于完整的工作示例,你将不得不等到下一章才能看到!
魔法的@SpringBootApplication注解
基于约定的自动配置机制可以通过在应用程序类(即包含静态main方法的类)上使用@SpringBootApplication注解来启动。以下代码展示了这一点:
**@SpringBootApplication**
public class MyApplication {
public static void main(String[] args) {
SpringApplication.run(MyApplication.class, args);
}
}
此注解将提供以下功能:
-
它启用了组件扫描,即在应用程序类的包及其所有子包中查找 Spring 组件和配置类。
-
应用程序类本身变成了一个配置类。
-
它启用了自动配置,Spring Boot 会在类路径中查找它可以自动配置的 JAR 文件。例如,如果你有 Tomcat 在类路径中,Spring Boot 将自动配置 Tomcat 作为嵌入式 Web 服务器。
组件扫描
假设我们有一个以下 Spring 组件在应用程序类(或其子包)的包中:
@Component
public class MyComponentImpl implements MyComponent { ...
应用程序中的另一个组件可以使用@Autowired注解自动获取这个组件,也称为自动装配:
public class AnotherComponent {
private final MyComponent myComponent;
**@Autowired**
public AnotherComponent(MyComponent myComponent) {
this.myComponent = myComponent;
}
我更喜欢使用构造函数注入(而不是字段和 setter 注入)来保持组件的状态不可变。在多线程运行时环境中运行组件时,不可变状态非常重要。
如果我们想要使用在应用程序包外部声明的组件,例如,多个 Spring Boot 应用程序共享的实用组件,我们可以在应用程序类中的@SpringBootApplication注解上补充一个@ComponentScan注解:
package se.magnus.myapp;
@SpringBootApplication
**@ComponentScan**({"se.magnus.myapp",**"se.magnus.util"** })
public class MyApplication {
我们现在可以从应用程序代码中的se.magnus.util包自动装配组件,例如,一个名为MyUtility的实用组件,如下所示:
package se.magnus.util;
@Component
public class **MyUtility** { ...
这个实用组件可以像这样在一个应用程序组件中自动装配:
package se.magnus.myapp.services;
public class AnotherComponent {
private final MyUtility myUtility;
@Autowired
public AnotherComponent(**MyUtility** myUtility) {
this.myUtility = myUtility;
}
基于 Java 的配置
如果我们想要覆盖 Spring Boot 的默认配置,或者我们想要添加自己的配置,我们可以简单地使用@Configuration注解一个类,它将被我们之前描述的组件扫描机制所拾取。
例如,如果我们想在处理 HTTP 请求(由 Spring WebFlux 处理,将在下一节中描述)的过程中设置一个过滤器,在处理开始和结束时写入日志消息,我们可以配置一个日志过滤器,如下所示:
**@Configuration**
public class SubscriberApplication {
@Bean
public **Filter** **logFilter****()** {
CommonsRequestLoggingFilter filter = new
CommonsRequestLoggingFilter();
filter.setIncludeQueryString(true);
filter.setIncludePayload(true);
filter.setMaxPayloadLength(5120);
return filter;
}
我们也可以直接在应用程序类中放置配置,因为@SpringBootApplication注解隐含了@Configuration注解。
关于 Spring Boot 的内容就到这里,但在我们转向下一个组件之前,让我们看看 Spring Boot 3.0 的新特性以及如何迁移 Spring Boot 2 应用程序。
Spring Boot 3.0 的新特性
对于本书的范围,Spring Boot 3.0 中最重要的新功能如下:
-
可观测性
Spring Boot 3.0 增强了对可观测性的支持,在之前 Spring Boot 版本中已有的对指标和日志的支持基础上,增加了内置的分布式跟踪支持。新的分布式跟踪支持基于 Spring 框架 v6.0 中的新可观测性 API 和名为 Micrometer Tracing 的新模块。Micrometer Tracing 基于 Spring Cloud Sleuth,现在已被弃用。第十四章,理解分布式跟踪,介绍了如何使用新的可观测性和分布式跟踪支持。
-
原生编译
Spring Boot 3.0 还支持将 Spring Boot 应用程序编译成原生镜像,这些镜像是可以独立执行的文件。原生编译的 Spring Boot 应用程序启动速度更快,并且消耗更少的内存。第二十三章,原生编译的 Java 微服务,描述了如何基于 Spring Boot 原生编译微服务。
-
虚拟线程
最后,Spring Boot 3.0 支持轻量级线程,称为来自 OpenJDK Project Loom 的虚拟线程。虚拟线程预计将简化开发反应式非阻塞微服务的编程模型,例如,与 Project Reactor 和各种 Spring 组件使用的编程模型相比。虚拟线程目前在 Java 19 中仅作为预览版提供。它们目前也缺乏对可组合性功能的支持,例如,构建同时从其他微服务聚合信息的微服务所需的。因此,虚拟线程将不会在本书中介绍。第七章,开发反应式微服务,介绍了如何使用 Project Reactor 和 Spring WebFlux 实现虚拟线程。
将 Spring Boot 2 应用程序迁移
如果您已经基于 Spring Boot 2 开发了应用程序,您可能想了解迁移到 Spring Boot 3.0 需要做什么。以下是需要采取的操作列表:
-
Pivotal 建议首先将 Spring Boot 2 应用程序升级到最新的 v2.7.x 版本,因为他们的迁移指南假设您正在使用 v2.7。
-
确保您已安装 Java 17 或更高版本,无论是在您的开发环境还是运行时环境中。如果您的 Spring Boot 应用程序作为 Docker 容器部署,您需要确保贵公司批准使用基于 Java 17 或更高版本发布的 Docker 镜像。
-
移除对 Spring Boot 2.x 中已弃用方法的调用。所有弃用方法都在 Spring Boot 3.0 中被移除,因此您必须确保您的应用程序没有调用这些方法中的任何一个。要查看应用程序中调用这些方法的确切位置,您可以在 Java 编译器中启用
lint:deprecation标志(假设使用 Gradle):tasks.withType(JavaCompile) { options.compilerArgs += ['-Xlint:deprecation'] } -
将
javax包的所有导入重命名为jakarta。 -
对于不由 Spring 管理的库,您需要确保您使用的是符合 Jakarta 规范的版本,即使用
jakarta包。 -
对于破坏性变更和其他重要迁移信息,请阅读以下内容:
github.com/spring-projects/spring-boot/wiki/Spring-Boot-3.0-Migration-Guidedocs.spring.io/spring-security/reference/migration/index.html -
确保您有端到端的全黑盒测试来验证您应用程序的功能。在迁移前后运行这些测试,以确保应用程序的功能没有受到迁移的影响。
在将本书前版源代码迁移到 Spring Boot 3.0 时,最耗时的工作是弄清楚如何处理 Spring Security 配置中的破坏性变更;有关详细信息,请参阅第十一章,保护 API 访问。例如,前版中授权服务器的以下配置需要更新:
@Bean
SecurityFilterChain defaultSecurityFilterChain(HttpSecurity http) throws Exception {
http
.authorizeRequests(authorizeRequests -> authorizeRequests
.antMatchers("/actuator/**").permitAll()
此配置在 Spring Boot 3.0 中看起来如下:
@Bean
SecurityFilterChain defaultSecurityFilterChain(HttpSecurity http) throws Exception {
http
.authorizeHttpRequests(authorizeRequests -> authorizeRequests
.requestMatchers("/actuator/**").permitAll()
随着每个章节一起提供的端到端测试脚本 test-em-all.bash 在验证每个章节迁移后功能未受影响方面变得不可或缺。
既然我们已经了解了 Spring Boot,那么让我们来谈谈 Spring WebFlux。
Spring WebFlux
Spring Boot 3.0 基于 Spring Framework 6.0,它内置了对开发反应式应用程序的支持。Spring Framework 使用 Project Reactor 作为其反应式支持的基础实现,并附带了一个新的网络框架 Spring WebFlux,该框架支持开发反应式(即非阻塞)的 HTTP 客户端和服务。
Spring WebFlux 支持两种不同的编程模型:
-
一种基于注解的命令式风格,类似于已经存在的网络框架 Spring Web MVC,但支持反应式服务
-
基于路由器和处理器的新功能导向模型
在本书中,我们将使用基于注解的命令式风格来演示如何轻松地将 REST 服务从 Spring Web MVC 移动到 Spring WebFlux,然后开始重构服务,使它们成为完全反应式的。
Spring WebFlux 还提供了一个完全反应式的 HTTP 客户端 WebClient,作为现有 RestTemplate 客户端的补充。
Spring WebFlux 支持在基于 Jakarta Servlet 规范 v5.0 或更高版本的 servlet 容器上运行,例如 Apache Tomcat,但也支持基于反应式且非 servlet 的嵌入式网络服务器,如 Netty (netty.io/)。
Servlet 规范是 Java EE 平台中的一个规范,它标准化了如何开发使用 HTTP 等网络协议进行通信的 Java 应用程序。
设置 REST 服务的代码示例
在我们能够创建基于 Spring WebFlux 的 REST 服务之前,我们需要将 Spring WebFlux(以及 Spring WebFlux 所需的依赖项)添加到类路径中,以便 Spring Boot 在启动时检测和配置。Spring Boot 提供了大量方便的 启动依赖项,它们带来了特定的功能,以及每个功能通常所需的依赖项。因此,让我们使用 Spring WebFlux 的启动依赖项,然后看看一个简单的 REST 服务是什么样的!
启动依赖项
在这本书中,我们将使用 Gradle 作为我们的构建工具,因此 Spring WebFlux 的启动依赖项将被添加到 build.gradle 文件中。它看起来像这样:
implementation('org.springframework.boot:spring-boot-starter-webflux')
你可能想知道为什么我们不指定版本号。当我们查看 第三章,创建一组协作微服务 的完整示例时,我们将讨论这个问题!
当微服务启动时,Spring Boot 将检测类路径上的 Spring WebFlux 并进行配置,以及启动嵌入式 Web 服务器等其他事情。Spring WebFlux 默认使用 Netty,这可以从日志输出中看到:
2023-03-09 15:23:43.592 INFO 17429 --- [ main] o.s.b.web.embedded.netty.NettyWebServer : Netty started on port(s): 8080
如果我们想将 Netty 作为嵌入式 Web 服务器切换到 Tomcat,我们可以通过排除 Netty 的启动依赖项并添加 Tomcat 的启动依赖项来覆盖默认配置:
implementation('org.springframework.boot:spring-boot-starter-webflux')
{
**exclude** group: 'org.springframework.boot', module: **'spring-boot-**
**starter-reactor-netty'**
}
**implementation**('org.springframework.boot:**spring-boot-starter-tomcat'**)
微服务重启后,我们可以看到 Spring Boot 选择了 Tomcat:
2023-03-09 18:23:44.182 INFO 17648 --- [ main] o.s.b.w.embedded.tomcat.TomcatWebServer : Tomcat initialized with port(s): 8080 (http)
属性文件
如前所述的示例所示,Web 服务器使用端口 8080 启动。如果您想更改端口,您可以使用属性文件覆盖默认值。Spring Boot 应用程序属性文件可以是 .properties 文件或 YAML 文件。默认情况下,它们分别命名为 application.properties 和 application.yml。
在这本书中,我们将使用 YAML 文件,以便嵌入式 Web 服务器使用的 HTTP 端口可以更改为,例如,7001。通过这样做,我们可以避免与其他在同一服务器上运行的微服务发生端口冲突。为此,我们可以在 application.yml 文件中添加以下行:
server.port: 7001
当我们在 第四章,使用 Docker 部署我们的微服务 中开始以容器形式开发我们的微服务时,端口冲突将不再成为问题。每个容器都有自己的主机名和端口范围,因此所有微服务都可以使用,例如端口 8080,而不会相互冲突。
示例 RestController
现在,有了 Spring WebFlux 和我们选择的嵌入式 Web 服务器,我们可以像使用 Spring MVC 一样编写 REST 服务,即作为一个 RestController:
**@RestController**
public class MyRestService {
**@GetMapping**(value = "/my-resource", produces = "application/json")
List<Resource> listResources() {
…
}
listResources() 方法上的 @GetMapping 注解将 Java 方法映射到 host:8080/myResource URL 上的 HTTP GET API。List<Resource> 类型的返回值将被转换为 JSON。
现在我们已经讨论了 Spring WebFlux,让我们看看我们如何使用 Spring WebFlux 记录我们开发的 API。
springdoc-openapi
开发 API 的一个重要方面,例如 RESTful 服务,是如何对其进行文档化,以便它们易于使用。SmartBear Software 的 Swagger 规范是记录 RESTful 服务最广泛使用的方法之一。许多领先的 API 网关都原生支持使用 Swagger 规范公开 RESTful 服务的文档。
2015 年,SmartBear Software 在OpenAPI 倡议下将 Swagger 规范捐赠给了 Linux Foundation,并创建了OpenAPI 规范。Swagger 这个名字仍然被用于 SmartBear Software 提供的工具。
springdoc-openapi是一个开源项目,独立于 Spring 框架,可以在运行时创建基于 OpenAPI 的 API 文档。它是通过检查应用程序来实现的,例如,检查 WebFlux 和基于 Swagger 的注解。
我们将在接下来的章节中查看完整的源代码示例,但到目前为止,以下简化的截图(被移除的部分用“…”标记)将展示一个示例 API 文档:
https://github.com/OpenDocCN/freelearn-java-zh/raw/master/docs/msvc-spbt3-spcld/img/B19825_02_01.png
图 2.1:使用 Swagger UI 可视化的示例 API 文档
注意到大的执行按钮,它可以用来实际尝试 API,而不仅仅是阅读其文档!
springdoc-openapi帮助我们记录微服务暴露的 API。现在,让我们继续了解 Spring Data。
Spring Data
Spring Data 提供了一种通用的编程模型,用于在多种类型的数据库引擎中持久化数据,从传统的关系型数据库(SQL 数据库)到各种类型的 NoSQL 数据库引擎,例如文档数据库(例如,MongoDB)、键值数据库(例如,Redis)和图数据库(例如,Neo4j)。
Spring Data 项目被分为几个子项目,在这本书中,我们将使用映射到 MySQL 数据库的 Spring Data 子项目,即 MongoDB 和 JPA。
JPA代表Jakarta 持久化 API,是关于如何处理关系型数据的一个 Java 规范。请访问jakarta.ee/specifications/persistence/获取最新的规范。Jakarta EE 9 基于 Jakarta Persistence 3.0。
Spring Data 编程模型的两个核心概念是实体和仓库。实体和仓库概括了如何从各种类型的数据库中存储和访问数据。它们提供了一个共同的抽象,但仍然支持向实体和仓库添加特定于数据库的行为。随着我们继续本章,我们将简要解释这两个核心概念,并伴随一些示例代码。请记住,更多细节将在接下来的章节中提供!
尽管 Spring Data 为不同类型的数据库提供了一个共同的编程模型,但这并不意味着您将能够编写可移植的源代码。例如,将数据库技术从 SQL 数据库切换到 NoSQL 数据库通常需要在源代码中进行一些更改才能实现!
实体
实体描述了 Spring Data 将要存储的数据。实体类通常使用通用的 Spring Data 注解和针对每种数据库技术特定的注解进行注解。
例如,一个将要存储在关系型数据库中的实体可以注解以下 JPA 注解:
import jakarta.persistence.Entity;
import jakarta.persistence.Id;
import jakarta.persistence.IdClass;
import jakarta.persistence.Table;
@Entity
@IdClass(ReviewEntityPK.class)
@Table(name = "review")
public class ReviewEntity {
@Id private int productId;
@Id private int reviewId;
private String author;
private String subject;
private String content;
如果一个实体要存储在 MongoDB 数据库中,可以使用 Spring Data MongoDB 子项目中的注解,与通用的 Spring Data 注解一起使用。例如,考虑以下代码:
import org.springframework.data.annotation.Id;
import org.springframework.data.annotation.Version;
import org.springframework.data.mongodb.core.mapping.Document;
@Document
public class RecommendationEntity {
@Id
private String id;
@Version
private int version;
private int productId;
private int recommendationId;
private String author;
private int rate;
private String content;
@Id和@Version注解是通用注解,而@Document注解是 Spring Data MongoDB 子项目特有的。
这可以通过研究import语句来揭示;包含mongodb的import语句来自 Spring Data MongoDB 子项目。
仓库
仓库用于存储和访问不同类型数据库的数据。在其最基本的形式中,一个仓库可以被声明为一个 Java 接口,Spring Data 将根据既定的约定动态生成其实现。这些约定可以通过额外的配置来覆盖和/或补充,如果需要,还可以通过一些 Java 代码。
Spring Data 还提供了一些基本的 Java 接口,例如CrudRepository,以使仓库的定义更加简单。基本接口CrudRepository为我们提供了创建、读取、更新和删除操作的标准方法。
要指定处理 JPA 实体ReviewEntity的仓库,我们只需要声明以下内容:
import org.springframework.data.repository.CrudRepository;
public interface ReviewRepository extends
CrudRepository<**ReviewEntity**, **ReviewEntityPK**> {
Collection<ReviewEntity> **findByProductId**(int productId);
}
在本例中,我们使用一个类,ReviewEntityPK,来描述一个复合主键。它看起来如下所示:
public class **ReviewEntityPK** implements Serializable {
public int productId;
public int reviewId;
}
我们还添加了一个额外的方法findByProductId,允许我们根据productid(它是主键的一部分)查找Review实体。方法的命名遵循 Spring Data 定义的命名约定,允许 Spring Data 动态生成此方法的实现。
如果我们要使用仓库,我们可以简单地注入它,然后开始使用它,例如:
private final ReviewRepository repository;
@Autowired
public ReviewService(ReviewRepository repository) {
this.repository = repository;
}
public void someMethod() {
repository.save(entity);
repository.delete(entity);
repository.findByProductId(productId);
除了CrudRepository接口外,Spring Data 还提供了一个反应式的基本接口ReactiveCrudRepository,它使得反应式仓库成为可能。该接口中的方法不返回对象或对象集合;相反,它们返回Mono和Flux对象。正如我们将在第七章开发反应式微服务中看到的,Mono和Flux对象是能够返回 0 到 1 个或 0 到 m 个实体作为它们在流中可用时的反应式流。
基于响应式的接口只能由支持响应式数据库驱动程序的 Spring Data 子项目使用;也就是说,它们基于非阻塞 I/O。Spring Data MongoDB 子项目支持响应式存储库,而 Spring Data JPA 不支持。
指定一个响应式存储库来处理之前描述的 MongoDB 实体RecommendationEntity可能看起来像以下这样:
import org.springframework.data.repository.reactive.ReactiveCrudRepository;
import reactor.core.publisher.Flux;
public interface RecommendationRepository extends ReactiveCrudRepository<**RecommendationEntity**, String> {
Flux<RecommendationEntity> findByProductId(int productId);
}
这就结束了 Spring Data 部分的内容。现在让我们看看如何使用 Spring Cloud Stream 来开发基于消息的异步服务。
Spring Cloud Stream
在这部分中,我们不会关注 Spring Cloud;我们将在书的第二部分第二部分中这样做,从第八章,Spring Cloud 简介到第十四章,理解分布式跟踪。然而,我们将引入 Spring Cloud 的一部分:Spring Cloud Stream。Spring Cloud Stream 在基于发布和订阅集成模式的信息传递之上提供了一个流抽象。Spring Cloud Stream 目前内置了对 Apache Kafka 和 RabbitMQ 的支持。存在许多独立的项目,它们提供了与其他流行信息传递系统的集成。有关更多详细信息,请参阅github.com/spring-cloud?q=binder。
Spring Cloud Stream 的核心概念如下:
-
消息:一种用于描述发送到和从信息传递系统接收到的数据的数据结构。
-
发布者:向信息传递系统发送消息,也称为供应商。
-
订阅者:从信息传递系统接收消息,也称为消费者。
-
目的地:用于与信息传递系统通信。发布者使用输出目的地,订阅者使用输入目的地。目的地由特定的绑定器映射到底层信息传递系统中的队列和主题。
-
绑定器:绑定器提供了与特定信息传递系统的实际集成,类似于 JDBC 驱动程序对特定类型数据库的集成。
实际要使用的信息传递系统是在运行时确定的,取决于类路径上找到的内容。Spring Cloud Stream 提供了一套关于如何处理信息传递的约定。这些约定可以通过指定消息功能(如消费者组、分区、持久性、持久性和错误处理)的配置来覆盖;例如,重试和死信队列处理。
发送和接收消息的代码示例
为了更好地理解所有这些是如何结合在一起的,让我们看看一些源代码示例。
Spring Cloud Stream 提供了两种编程模型:一种是基于注解使用(例如,@EnableBinding、@Output和@StreamListener)的较老且现在已弃用的模型,另一种是基于编写函数的新模型。在这本书中,我们将使用函数式实现。
要实现发布者,我们只需要将java.util.function.Supplier功能接口实现为一个 Spring Bean。例如,以下是一个发布消息为字符串的发布者:
@Bean
public **Supplier**<String> myPublisher() {
return () -> new Date().toString();
}
订阅者作为实现java.util.function.Consumer功能接口的 Spring Bean 实现。例如,以下是一个消费字符串消息的订阅者:
@Bean
public **Consumer**<String> mySubscriber() {
return s -> System.out.println("ML RECEIVED: " + s);
}
也可以定义一个处理消息的 Spring Bean,这意味着它既消费又发布消息。这可以通过实现java.util.function.Function功能接口来完成。例如,一个 Spring Bean 消费传入的消息,并在一些处理后发布一条新消息(在这个例子中,两条消息都是字符串):
@Bean
public **Function**<String, String> myProcessor() {
return s -> "ML PROCESSED: " + s;
}
要使 Spring Cloud Stream 了解这些函数,我们需要使用spring.cloud.function.definition配置属性来声明它们。例如,对于之前定义的三个函数,这会看起来如下:
**spring.cloud.function:**
**definition**: myPublisher;myProcessor;mySubscriber
最后,我们需要告诉 Spring Cloud Stream 每个函数使用哪个目标。为了连接我们的三个函数,以便我们的处理器从发布者那里消费消息,而我们的订阅者从处理器那里消费消息,我们可以提供以下配置:
spring.cloud.stream.bindings:
myPublisher-out-0:
destination: myProcessor-in
myProcessor-in-0:
destination: myProcessor-in
myProcessor-out-0:
destination: myProcessor-out
mySubscriber-in-0:
destination: myProcessor-out
这将导致以下消息流:
myPublisher → myProcessor → mySubscriber
默认情况下,Spring Cloud Stream 每秒触发一次供应商,因此如果我们启动一个包含之前描述的函数和配置的 Spring Boot 应用程序,我们可以期望以下输出:
ML RECEIVED: ML PROCESSED: Wed Mar 09 16:28:30 CET 2021
ML RECEIVED: ML PROCESSED: Wed Mar 09 16:28:31 CET 2021
ML RECEIVED: ML PROCESSED: Wed Mar 09 16:28:32 CET 2021
ML RECEIVED: ML PROCESSED: Wed Mar 09 16:28:33 CET 2021
在供应商应该由外部事件触发而不是使用定时器的情况下,可以使用StreamBridge辅助类。例如,如果当调用 REST API sampleCreateAPI时应该向处理器发布消息,代码可能如下所示:
@Autowired
private StreamBridge streamBridge;
@PostMapping
void **sampleCreateAPI**(@RequestBody String body) {
**streamBridge**.send("myProcessor-in-0", body);
}
现在我们已经了解了各种 Spring API,让我们在下一节中学习一些关于 Docker 和容器的内容。
Docker
我假设 Docker 和容器概念不需要深入介绍。Docker 在 2013 年使容器概念作为轻量级虚拟机替代品变得非常流行。实际上,容器是 Linux 主机上的一个进程,它使用 Linux 命名空间来在不同容器之间提供隔离,这些容器在全局系统资源(如用户、进程、文件系统和网络)的使用方面是隔离的。Linux 控制组(也称为cgroups)用于限制容器可以消耗的 CPU 和内存量。
与使用虚拟机管理程序在每个虚拟机上运行操作系统完整副本的虚拟机相比,容器中的开销是传统虚拟机开销的一小部分。
这导致启动时间更快,并且在 CPU 和内存使用方面显著降低开销。
然而,提供的容器隔离性并不被认为像虚拟机提供的隔离性那样安全。随着 Windows Server 2016 的发布,Microsoft 支持在 Windows 服务器中使用 Docker。
在过去几年里,一种轻量级的虚拟机形式已经发展起来。它结合了传统虚拟机和容器的优点,为虚拟机提供了与容器相似的足迹和启动时间,并且提供了与传统虚拟机相同级别的安全隔离。一些例子包括 Amazon Firecracker 和 Microsoft Windows Subsystem for Linux v2(WSL2)。更多信息,请参阅 firecracker-microvm.github.io 和 docs.microsoft.com/en-us/windows/wsl/.
容器在开发和测试阶段都非常有用。能够用一个命令启动一个完整的协作微服务和资源管理器(例如,数据库服务器、消息代理等)的系统景观进行测试,真是太神奇了。
例如,我们可以编写脚本来自动化微服务景观的端到端测试。测试脚本可以启动微服务景观,使用公开的 API 运行测试,并拆除景观。这种类型的自动化测试脚本非常有用,既可以在将代码推送到源代码库之前在开发者的 PC 上本地运行,也可以作为交付管道中的一个步骤执行。构建服务器可以在开发者将代码推送到源代码库时,在其持续集成和部署过程中运行这些类型的测试。
对于生产使用,我们需要一个容器编排器,如 Kubernetes。我们将在本书的后面部分回到容器编排器和 Kubernetes。
在这本书中我们将要查看的大部分微服务,只需要以下这样的 Dockerfile 就可以运行作为 Docker 容器的微服务:
FROM openjdk:17
MAINTAINER Magnus Larsson <magnus.larsson.ml@gmail.com>
EXPOSE 8080
ADD ./build/libs/*.jar app.jar
ENTRYPOINT ["java","-jar","/app.jar"]
如果我们想要用一个命令启动和停止许多容器,Docker Compose 是完美的工具。Docker Compose 使用 YAML 文件来描述要管理的容器。
对于我们的微服务,可能看起来像以下这样:
product:
**build**: microservices/product-service
recommendation:
**build**: microservices/recommendation-service
review:
**build**: microservices/review-service
composite:
**build**: microservices/product-composite-service
**ports**:
- "**8080:8080**"
让我稍微解释一下前面的源代码:
-
使用
build指令来指定每个微服务要使用的 Dockerfile。Docker Compose 将使用它来构建 Docker 镜像,然后基于该镜像启动 Docker 容器。 -
复合服务的
ports指令用于在 Docker 运行的服务器上公开端口8080。在开发者的机器上,这意味着可以通过localhost:8080直接访问复合服务的端口!
YAML 文件中的所有容器都可以使用以下简单的命令进行管理:
-
docker-compose up -d:启动所有容器。-d表示容器在后台运行,不会锁定执行命令的终端。 -
docker-compose down:停止并删除所有容器。 -
docker-compose logs -f --tail=0:打印出所有容器的日志消息。-f表示命令不会完成,而是等待新的日志消息。--tail=0表示我们不想看到任何以前的日志消息,只想看到新的。
有关 Docker Compose 命令的完整列表,请参阅 docs.docker.com/compose/reference/。
这是对 Docker 的简要介绍。我们将从 第四章,使用 Docker 部署我们的微服务 开始更详细地介绍 Docker。
摘要
在本章中,我们介绍了 Spring Boot 和可以用来构建协作微服务的开源工具。
Spring Boot 用于简化基于 Spring 的、适用于生产的应用程序的开发,例如微服务。它在如何设置 Spring 框架的核心模块和第三方工具方面有很强的意见。使用 Spring WebFlux,我们可以开发暴露反应式(即非阻塞)REST 服务的微服务。为了记录这些 REST 服务,我们可以使用 springdoc-openapi 来为 API 创建基于 OpenAPI 的文档。如果我们需要持久化微服务使用的数据,我们可以使用 Spring Data,它为使用实体和仓库访问和操作持久数据提供了一个优雅的抽象。Spring Data 的编程模型类似,但不是完全可移植到不同类型的数据库之间,例如关系型、文档型、键值型和图数据库。
如果我们更喜欢在微服务之间异步发送消息,我们可以使用 Spring Cloud Stream,它提供了基于消息的流抽象。Spring Cloud Stream 自带对 Apache Kafka 和 RabbitMQ 的支持,但可以通过自定义绑定器扩展以支持其他消息代理。最后,Docker 使得容器作为轻量级虚拟机的替代方案的概念变得易于使用。基于 Linux 命名空间和控制组,容器提供了类似于传统虚拟机的隔离,但在 CPU 和内存使用方面具有显著更低的开销。
在下一章中,我们将迈出第一步,使用 Spring Boot 和 Spring WebFlux 创建具有最小功能性的微服务。
问题
-
@SpringBootApplication注解的目的是什么? -
旧的用于开发 REST 服务的 Spring 组件 Spring Web MVC 和新的 Spring WebFlux 之间主要区别是什么?
-
springdoc-openapi如何帮助开发者记录 REST API? -
Spring Data 中的仓库有什么功能?以及仓库的最简单实现是什么?
-
Spring Cloud Stream 中的绑定器有什么作用?
-
Docker Compose 的目的是什么?
610



被折叠的 条评论
为什么被折叠?



