Spring 微服务(一)

原文:zh.annas-archive.org/md5/52026E2A45414F981753F74B874EEB00

译者:飞龙

协议:CC BY-NC-SA 4.0

前言

微服务是一种架构风格和模式,复杂系统被分解为较小的服务,这些服务共同形成更大的业务服务。微服务是自治的、独立的,可以独立部署的服务。在当今世界,许多企业将微服务作为构建大型面向服务的企业应用程序的默认标准。

Spring 框架是开发者社区多年来广受欢迎的编程框架。Spring Boot 消除了需要使用笨重的应用容器,并提供了一种部署轻量级、无服务器应用程序的方法。Spring Cloud 结合了许多 Netflix OSS 组件,并提供了一个运行和管理大规模微服务的生态系统。它提供了诸如负载平衡、服务注册、监控、服务网关等功能。

然而,微服务也带来了自己的挑战,例如监控、管理、分发、扩展、发现等,特别是在大规模部署时。在不解决常见的微服务挑战的情况下采用微服务将导致灾难性的结果。本书最重要的部分是一个技术无关的微服务能力模型,它有助于解决所有常见的微服务挑战。

本书的目标是以务实的方法和指南为读者提供在大规模实施响应式微服务时的启示。本书将带领读者深入了解 Spring Boot、Spring Cloud、Docker、Mesos 和 Marathon。本书的读者将了解 Spring Boot 如何用于部署无服务器的自治服务,从而消除了使用笨重的应用服务器的需要。读者将学习不同的 Spring Cloud 功能,并了解 Docker 用于容器化以及 Mesos 和 Marathon 用于计算资源抽象和集群范围控制的用途。

我相信读者会喜欢本书的每一部分。而且,我真诚地相信本书通过成功构思微服务为您的业务增添了巨大的价值。在整本书中,我通过提供许多例子,包括旅行领域的案例研究,使用了微服务实施的实际方面。最终,您将学会如何使用 Spring 框架、Spring Boot 和 Spring Cloud 实施微服务架构。这些都是经过实战检验的强大工具,用于开发和部署可扩展的微服务。根据 Spring 的最新规范编写,借助本书的帮助,您将能够在短时间内构建现代的、互联网规模的 Java 应用程序。

本书涵盖的内容

《第一章》《解密微服务》为您介绍了微服务。本章涵盖了微服务的基本概念、它们的演变以及它们与面向服务的架构的关系,以及云原生和十二要素应用的概念。

《第二章》《使用 Spring Boot 构建微服务》介绍了如何使用 Spring 框架构建基于 REST 和消息的微服务,以及如何使用 Spring Boot 封装它们。此外,我们还将探索 Spring Boot 的一些核心功能。

《第三章》《应用微服务概念》详细解释了微服务实施的实际方面,详细说明了开发人员在企业级微服务中面临的挑战。这也将总结成功管理微服务生态系统所需的能力。

第四章,“微服务演进-案例研究”,将读者带入了一个真实的微服务演进案例研究,介绍了 BrownField 航空公司。使用案例研究,本章解释了如何应用前几章学到的微服务概念。

第五章,“使用 Spring Cloud 扩展微服务”,展示了如何使用 Spring Cloud 堆栈功能扩展先前的示例。它详细介绍了 Spring Cloud 的架构和不同组件以及它们如何集成在一起。

第六章,“微服务的自动扩展”,演示了使用简单的生命周期管理器来实现弹性和微服务的自我管理,通过编排服务网关来编排服务。它解释了在现实世界中如何向服务网关添加智能。

第七章,“微服务的日志记录和监控”,涵盖了在开发微服务时日志记录和监控方面的重要性。在这里,我们将详细介绍使用微服务时一些最佳实践,如使用开源工具实现集中式日志记录和监控功能,以及如何将它们与 Spring 项目集成。

第八章,“使用 Docker 容器化微服务”,解释了微服务上下文中的容器化概念。使用 Mesos 和 Marathon,本章演示了一个替代自定义生命周期管理器用于大规模部署的下一级实现。

第九章,“使用 Mesos 和 Marathon 管理 Docker 化的微服务”,解释了微服务的自动配置和部署。在这里,您还将学习如何在前面的示例中使用 Docker 容器进行大规模部署。

第十章,“微服务开发生命周期”,涵盖了微服务开发的过程和实践。本章还解释了 DevOps 和持续交付管道的重要性。

本书需要什么

第二章,“使用 Spring Boot 构建微服务”,介绍了 Spring Boot,需要以下软件组件来测试代码:

  • JDK 1.8

  • Spring Tool Suite 3.7.2 (STS)

  • Maven 3.3.1

  • Spring Framework 4.2.6.RELEASE

  • Spring Boot 1.3.5.RELEASE

  • spring-boot-cli-1.3.5.RELEASE-bin.zip

  • RabbitMQ 3.5.6

  • FakeSMTP

在第五章,“使用 Spring Cloud 扩展微服务”,您将了解 Spring Cloud 项目。除了前面提到的软件组件外,还需要以下软件组件:

  • Spring Cloud Brixton.RELEASE

在第七章,“微服务的日志记录和监控”中,我们将看看如何为微服务实现集中式日志记录。这需要以下软件堆栈:

  • Elasticsearch 1.5.2

  • kibana-4.0.2-darwin-x64

  • Logstash 2.1.2

在第八章,“使用 Docker 容器化微服务”,我们将演示如何使用 Docker 进行微服务部署。这需要以下软件组件:

  • Docker 版本 1.10.1

  • Docker Hub

第九章,“使用 Mesos 和 Marathon 管理 Docker 化的微服务”,使用 Mesos 和 Marathon 将 docker 化的微服务部署到可自动扩展的云中。为此需要以下软件组件:

  • Mesos 版本 0.27.1

  • Docker 版本 1.6.2

  • 本书主要面向寻求构建云就绪的互联网规模应用程序以满足现代业务需求的 Spring 开发人员。本书将通过检查许多真实用例和实际代码示例,帮助开发人员了解微服务的确切含义以及它们为何在当今世界中如此重要。开发人员将了解如何构建简单的 RESTful 服务,并将其有机地发展成真正的企业级微服务生态系统。

马拉松版本 0.15.3

警告或重要说明以如下方式显示在一个框中。

新术语重要单词以粗体显示。您在屏幕上看到的单词,例如菜单或对话框中的单词,会以如下方式出现在文本中:“点击发出请求按钮。”

代码块设置如下:

本书中,您将找到许多文本样式,用于区分不同类型的信息。以下是这些样式的一些示例以及它们的含义解释。

文本中的代码词、数据库表名、文件夹名、文件名、文件扩展名、路径名、虚拟 URL、用户输入和 Twitter 句柄显示如下:“可以在application.properties中设置以下属性来自定义与应用程序相关的信息。”

<parent>
  <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-parent</artifactId>
    <version>1.3.4.RELEASE</version>
</parent>

当我们希望引起您对代码块的特定部分的注意时,相关行或项目以粗体设置:

eureka-server2.properties
eureka.client.serviceUrl.defaultZone:http://localhost:8761/eureka/
eureka.client.registerWithEureka:false
eureka.client.fetchRegistry:false

任何命令行输入或输出都以以下方式编写:

$ java -jar fakeSMTP-2.0.jar

本书适合对象

注意

约定

提示和技巧以如下方式出现。

本书将对寻求使用 Spring 框架、Spring Boot 和 Spring Cloud 设计强大的互联网规模微服务,并使用 Docker、Mesos 和 Marathon 进行管理的架构师感兴趣。能力模型将帮助架构师制定甚至超越本书讨论的工具和技术的解决方案。

第一章:解密微服务

微服务是一种软件开发的架构风格和方法,以满足现代业务需求。微服务并非是创新,而是从以前的架构风格中演变而来。

我们将从更近距离地观察微服务架构从传统的大型架构演变而来。我们还将研究微服务的定义、概念和特点。最后,我们将分析微服务的典型用例,并建立微服务与其他架构方法(如面向服务的架构(SOA)和十二要素应用)之间的相似性和关系。十二要素应用定义了一套针对云平台开发应用程序的软件工程原则。

在本章中,您将学习以下内容:

  • 微服务的演进

  • 带有示例的微服务架构的定义

  • 微服务架构的概念和特点

  • 微服务架构的典型用例

  • 微服务与 SOA 和十二要素应用的关系

微服务的演进

微服务是继 SOA 之后越来越受欢迎的架构模式之一,与 DevOps 和云相辅相成。微服务的演进受到了现代业务中颠覆性数字创新趋势和过去几年技术演进的极大影响。我们将在本节中研究这两个因素。

业务需求作为微服务演进的催化剂

在这个数字转型的时代,企业越来越多地采用技术作为大幅增加其收入和客户群的关键推动力之一。企业主要利用社交媒体、移动、云、大数据和物联网作为实现颠覆性创新的工具。利用这些技术,企业找到了快速渗透市场的新途径,这严重挑战了传统的 IT 交付机制。

以下图表显示了传统开发和微服务在面对敏捷性、交付速度和规模等新企业挑战方面的状态。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

提示

微服务承诺比传统的大型应用程序更具敏捷性、交付速度和规模。

过去企业投资于数年的大型应用程序开发的时代已经过去了。企业不再有兴趣开发整合的应用程序来管理他们的端到端业务功能,就像几年前那样。

以下图表显示了传统的大型应用程序和微服务在周转时间和成本方面的比较。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

提示

微服务提供了一种开发快速敏捷应用程序的方法,从而降低总体成本。

例如,如今,航空公司或金融机构不再投资于重建他们的核心大型主机系统,以避免另一个庞大的怪物。零售商和其他行业也不再重建重量级供应链管理应用程序,比如他们传统的 ERP 系统。焦点已转向构建满足业务特定需求的快速解决方案,以最敏捷的方式进行。

让我们举个例子,一个在线零售商正在使用传统的大型应用程序。如果零售商希望通过根据客户的过往购物、偏好等个性化客户的产品来创新销售,并且还希望根据客户购买倾向来为客户提供产品,他们将快速开发个性化引擎或根据客户的即时需求提供优惠,并将其插入他们的传统应用程序中。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

如前面的图表所示,与其投资于重建核心遗留系统,这将通过将响应通过新功能传递来完成,如图中标有A的部分所示,或者通过修改核心遗留系统以调用这些功能作为处理的一部分,如图中标有B的部分所示。这些功能通常被编写为微服务。

这种方法为组织提供了大量的机会,以较低的成本在实验模式下快速尝试新功能。企业可以随后验证关键绩效指标,并根据需要修改或替换这些实现。

提示

现代架构期望最大化替换其部分并最小化替换其部分的成本。微服务方法是实现这一目标的手段。

技术作为微服务演变的催化剂

新兴技术也使我们重新思考构建软件系统的方式。例如,几十年前,我们甚至无法想象没有两阶段提交的分布式应用程序。后来,NoSQL 数据库使我们有了不同的思考方式。

同样,技术上的这种范式转变已经重塑了软件架构的所有层面。

HTML 5 和 CSS3 的出现以及移动应用程序的进步重新定位了用户界面。诸如 Angular、Ember、React、Backbone 等客户端 JavaScript 框架因其客户端渲染和响应式设计而广受欢迎。

随着云采用成为主流,平台即服务PaaS)提供商,如 Pivotal CF、AWS、Salesforce.com、IBM 的 Bluemix、RedHat 的 OpenShift 等,使我们重新思考构建中间件组件的方式。Docker 带来的容器革命从根本上影响了基础设施领域。如今,基础设施被视为一种商品服务。

集成景观也随着集成平台即服务iPaaS)的出现而发生了变化。Dell Boomi、Informatica、MuleSoft 等平台是 iPaaS 的例子。这些工具帮助组织将集成边界延伸到传统企业之外。

NoSQL 已经彻底改变了数据库领域。几年前,我们只有几种流行的数据库,都基于关系数据建模原则。今天我们有了一个很长的数据库列表:Hadoop、Cassandra、CouchDB、Neo 4j 等等。每个数据库都解决了特定的架构问题。

命令式架构演变

应用架构一直在与不断变化的业务需求和技术的发展一起不断演进。架构已经经历了古老的主机系统的演变,到完全抽象的云服务,比如 AWS Lambda。

提示

使用 AWS Lambda,开发人员现在可以将他们的“功能”放入一个完全托管的计算服务中。

aws.amazon.com/documentation/lambda/了解更多关于 Lambda 的信息。

不同的架构方法和风格,如主机、客户端服务器、N 层和面向服务的架构,在不同的时间段都很受欢迎。无论选择哪种架构风格,我们总是用来构建单片架构的一种或另一种形式。微服务架构的演变是现代业务需求(如敏捷性和交付速度)、新兴技术以及从以前一代架构中学到的结果。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

微服务帮助我们打破了单片应用程序的边界,并构建了一个逻辑上独立的更小的系统,如前面的图表所示。

提示

如果我们将单片应用程序视为一组逻辑子系统,包含有物理边界,那么微服务就是一组没有封闭物理边界的独立子系统。

什么是微服务?

微服务是许多组织今天使用的一种架构风格,作为实现高度敏捷性、交付速度和规模的游戏规则改变者。微服务为我们提供了一种开发更加物理分离的模块化应用程序的方式。

微服务并非创造出来的。许多组织,如 Netflix、亚马逊和 eBay,成功地使用分而治之的技术,将其单片应用程序功能性地分割成更小的原子单元,每个单元执行单一功能。这些组织解决了他们在单片应用程序中遇到的一些问题。

在这些组织的成功之后,许多其他组织开始将这种模式作为重构其单片应用程序的常见模式。后来,倡导者将这种模式称为微服务架构。

微服务源自 Alistair Cockburn 提出的六边形架构的概念。六边形架构也被称为端口和适配器模式。

提示

alistair.cockburn.us/Hexagonal+architecture了解更多关于六边形架构。

微服务是一种建立 IT 系统的架构风格或方法,作为一组自治、自包含和松耦合的业务能力:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

前面的图表描述了传统的 N 层应用程序架构,包括表示层、业务层和数据库层。模块ABC代表三种不同的业务能力。图表中的层代表架构关注点的分离。每个层包含与该层相关的所有三种业务能力。表示层包含所有三个模块的 Web 组件,业务层包含所有三个模块的业务组件,数据库托管所有三个模块的表。在大多数情况下,层是可以物理分开的,而层内的模块是硬连接的。

现在让我们来看看基于微服务的架构。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

正如前面的图表所示,微服务架构中的边界是相反的。每个垂直切片代表一个微服务。每个微服务都有自己的表示层、业务层和数据库层。微服务是与业务能力对齐的。通过这样做,对一个微服务的更改不会影响其他微服务。

微服务的通信或传输机制没有标准。一般来说,微服务使用广泛采用的轻量级协议进行通信,如 HTTP 和 REST,或者消息协议,如 JMS 或 AMQP。在特定情况下,可以选择更优化的通信协议,如 Thrift、ZeroMQ、Protocol Buffers 或 Avro。

由于微服务更加符合业务能力,并且具有独立可管理的生命周期,它们是企业在采用 DevOps 和云时的理想选择。DevOps 和云是微服务的两个方面。

提示

DevOps 是一种 IT 重新调整,缩小传统 IT 开发和运营之间的差距,以提高效率。

了解更多关于 DevOps:

dev2ops.org/2010/02/what-is-devops/

微服务-蜂窝类比

蜂窝是一个理想的类比,用来代表演进的微服务架构。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

在现实世界中,蜜蜂通过排列六边形蜂窝来建造蜂巢。它们从小开始,使用不同的材料来建造蜂窝。建造是基于当时可用的材料。重复的蜂窝形成了一个模式,并形成了一个坚固的结构。蜂巢中的每个蜂房都是独立的,但也与其他蜂房集成在一起。通过添加新的蜂房,蜂巢有机地增长成一个大而坚固的结构。每个蜂房内部的内容是抽象的,对外不可见。一个蜂房的损坏不会影响其他蜂房,蜜蜂可以重建这些蜂房而不影响整个蜂巢。

微服务的原则

在这一部分,我们将研究微服务架构的一些原则。这些原则在设计和开发微服务时是“必须具备”的。

每个服务只有一个责任

单一责任原则是 SOLID 设计模式的一部分所定义的原则之一。它指出一个单元应该只有一个责任。

提示

在这里了解更多关于 SOLID 设计模式的信息:

c2.com/cgi/wiki?PrinciplesOfObjectOrientedDesign

这意味着一个单元,无论是类、函数还是服务,都应该只有一个责任。在任何时候,两个单元都不应该共享一个责任,或者一个单元有多个责任。一个具有多个责任的单元表示紧密耦合。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

如前图所示,CustomerProductOrder是电子商务应用程序的不同功能。与其将它们全部构建到一个应用程序中,最好是有三个不同的服务,每个负责一个业务功能,这样对一个责任的更改不会影响其他责任。在前述情景中,CustomerProductOrder将被视为三个独立的微服务。

微服务是自治的

微服务是自包含的、独立部署的、自治的服务,完全负责业务能力及其执行。它们捆绑了所有依赖,包括库依赖,以及抽象物理资源的执行环境,如 Web 服务器、容器或虚拟机。

微服务和 SOA 之间的一个主要区别在于它们的自治级别。虽然大多数 SOA 实现提供服务级别的抽象,但微服务更进一步,抽象了实现和执行环境。

在传统的应用程序开发中,我们构建一个 WAR 或 EAR,然后将其部署到 JEE 应用服务器,如 JBoss、WebLogic、WebSphere 等。我们可能会将多个应用程序部署到同一个 JEE 容器中。在微服务方法中,每个微服务将被构建为一个 fat Jar,嵌入所有依赖项,并作为一个独立的 Java 进程运行。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

微服务也可以获得自己的容器来执行,如前图所示。容器是可移植的、独立可管理的、轻量级的运行时环境。容器技术,如 Docker,是微服务部署的理想选择。

微服务的特点

本章前面讨论的微服务定义是任意的。倡导者和实践者对微服务有着强烈但有时不同的看法。对于微服务并没有一个单一、具体和普遍接受的定义。然而,所有成功的微服务实现都表现出一些共同的特征。因此,重要的是要理解这些特征,而不是坚持理论上的定义。本节详细介绍了一些共同的特征。

服务是一等公民

在微服务世界中,服务是一等公民。微服务将服务端点公开为 API,并抽象了所有实现细节。内部实现逻辑、架构和技术(包括编程语言、数据库、服务质量机制等)完全隐藏在服务 API 的后面。

此外,在微服务架构中,不再有应用程序开发;相反,组织专注于服务开发。在大多数企业中,这需要应用程序构建方式的重大文化转变。

客户资料微服务中,诸如数据结构、技术、业务逻辑等的内部细节被隐藏起来。它们不会暴露或对任何外部实体可见。访问是通过服务端点或 API 进行限制的。例如,客户资料微服务可以公开注册客户获取客户作为其他人与之交互的两个 API。

微服务中的服务特性

由于微服务在某种程度上类似于 SOA,因此在微服务中也适用于 SOA 中定义的许多服务特性。

以下是一些适用于微服务的服务特性:

  • 服务契约:与 SOA 类似,微服务通过明确定义的服务契约进行描述。在微服务世界中,JSON 和 REST 被普遍接受用于服务通信。在 JSON/REST 的情况下,有许多用于定义服务契约的技术。JSON Schema、WADL、Swagger 和 RAML 是一些例子。

  • 松耦合:微服务是独立的和松耦合的。在大多数情况下,微服务接受事件作为输入,并以另一个事件作为响应。消息传递、HTTP 和 REST 通常用于微服务之间的交互。基于消息的端点提供更高级别的解耦。

  • 服务抽象:在微服务中,服务抽象不仅是服务实现的抽象,还提供了所有库和环境细节的完全抽象,正如前面讨论的那样。

  • 服务重用:微服务是粗粒度可重用的业务服务。这些服务可以被移动设备和桌面渠道、其他微服务,甚至其他系统访问。

  • 无状态性:设计良好的微服务是无状态的,不共享任何共享状态或由服务维护的对话状态。如果需要维护状态,它们会在数据库中维护,可能是在内存中。

  • 服务可发现性:微服务是可发现的。在典型的微服务环境中,微服务会自我宣传其存在,并使自己可供发现。当服务终止时,它们会自动从微服务生态系统中退出。

  • 服务互操作性:服务是可互操作的,因为它们使用标准协议和消息交换标准。消息传递、HTTP 等被用作传输机制。在微服务世界中,REST/JSON 是开发可互操作服务最流行的方法。在需要进一步优化通信的情况下,可以使用其他协议,如 Protocol Buffers、Thrift、Avro 或 Zero MQ。然而,使用这些协议可能会限制服务的整体互操作性。

  • 服务可组合性:微服务是可组合的。服务可组合性是通过服务编排或服务编舞来实现的。

提示

有关 SOA 原则的更多细节可以在以下找到:

serviceorientation.com/serviceorientation/index

微服务是轻量级的

设计良好的微服务与单一业务能力对齐,因此它们只执行一个功能。因此,在大多数实现中,我们看到的共同特征是具有较小足迹的微服务。

在选择支持技术(如 Web 容器)时,我们必须确保它们也是轻量级的,以便整体占用空间保持可管理。例如,Jetty 或 Tomcat 作为微服务的应用容器,与 WebLogic 或 WebSphere 等更复杂的传统应用服务器相比更好。

与 VMWare 或 Hyper-V 等虚拟化技术相比,Docker 等容器技术还可以帮助我们尽可能地减少基础设施的占用空间。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

如前图所示,微服务通常部署在 Docker 容器中,这些容器封装了业务逻辑和所需的库。这有助于我们在新机器上或完全不同的托管环境甚至不同的云提供商之间快速复制整个设置。由于没有物理基础设施依赖,容器化的微服务非常易于移植。

多语言架构的微服务

由于微服务是自治的,并且将一切都抽象在服务 API 后面,因此可能会有不同的微服务采用不同的架构。微服务实现中常见的一些特征包括:

  • 不同的服务使用相同技术的不同版本。一个微服务可能是基于 Java 1.7 编写的,另一个可能是基于 Java 1.8 的。

  • 不同的语言用于开发不同的微服务,例如一个微服务用 Java 开发,另一个用 Scala 开发。

  • 使用不同的架构,例如一个微服务使用 Redis 缓存来提供数据,而另一个微服务可能使用 MySQL 作为持久性数据存储。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

在前面的例子中,由于酒店搜索预计具有高交易量和严格的性能要求,因此使用 Erlang 进行实现。为了支持预测搜索,使用 Elasticsearch 作为数据存储。同时,酒店预订需要更多的 ACID 事务特性。因此,它使用 MySQL 和 Java 进行实现。内部实现被隐藏在定义为 REST/JSON over HTTP 的服务端点后面。

微服务环境中的自动化

大多数微服务实现都是从开发到生产的最大程度自动化的。

由于微服务将单片应用程序分解为多个较小的服务,大型企业可能会看到微服务的激增。除非自动化到位,否则大量微服务很难管理。微服务的较小占用空间也有助于我们自动化微服务的开发到部署生命周期。总的来说,微服务是端到端自动化的,例如自动化构建、自动化测试、自动化部署和弹性扩展。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

如前图所示,在开发、测试、发布和部署阶段通常会应用自动化:

  • 开发阶段使用版本控制工具(如 Git)与持续集成CI)工具(如 Jenkins、Travis CI 等)进行自动化。这也可能包括代码质量检查和单元测试的自动化。微服务也可以实现每次代码提交时进行完整构建的自动化。

  • 测试阶段将使用 Selenium、Cucumber 和其他 AB 测试策略等测试工具进行自动化。由于微服务与业务能力对齐,因此相对于单片应用程序,自动化的测试用例数量较少,因此每次构建都可以进行回归测试。

  • 基础设施的配置是通过诸如 Docker 之类的容器技术进行的,再加上诸如 Chef 或 Puppet 之类的发布管理工具,以及诸如 Ansible 之类的配置管理工具。自动化部署使用诸如 Spring Cloud、Kubernetes、Mesos 和 Marathon 之类的工具进行处理。

具有支持生态系统的微服务

大多数大规模的微服务实现都有一个支持生态系统。生态系统的能力包括 DevOps 流程、集中式日志管理、服务注册表、API 网关、广泛的监控、服务路由和流量控制机制。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

当前图表中所代表的支持能力齐备时,微服务运行良好。

微服务是分布式和动态的

成功的微服务实现将逻辑和数据封装在服务内部。这导致了两种非常规的情况:分布式数据和逻辑以及分散的治理。

与将所有逻辑和数据整合到一个应用边界的传统应用程序相比,微服务将数据和逻辑分散化。每个服务都与特定的业务能力对齐,拥有自己的数据和逻辑。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

前面图表中的虚线表示逻辑单片应用边界。当我们将其迁移到微服务时,每个微服务 A、B 和 C 都创建了自己的物理边界。

微服务通常不使用像 SOA 中那样的集中式治理机制。微服务实现的一个共同特点是它们不依赖于重量级的企业级产品,如企业服务总线(ESB)。相反,业务逻辑和智能被嵌入到服务本身中。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

前面图表中显示了典型的 SOA 实现。购物逻辑完全在 ESB 中实现,通过编排由客户、订单和产品提供的不同服务。另一方面,在微服务方法中,购物本身将作为一个独立的微服务运行,以一种相当解耦的方式与客户、产品和订单进行交互。

SOA 实现严重依赖于静态注册表和存储库配置来管理服务和其他工件。微服务为此带来了更加动态的特性。因此,静态治理方法被视为在维护最新信息方面的负担。这就是为什么大多数微服务实现使用自动化机制从运行时拓扑动态构建注册表信息。

反脆弱性、快速失败和自愈

反脆弱性是 Netflix 成功实验的一种技术。这是现代软件开发中构建故障安全系统的最强大方法之一。

提示

反脆弱性概念是由纳西姆·尼古拉斯·塔勒布在他的书《反脆弱:从混乱中获益的事物》中引入的。

在反脆弱性实践中,软件系统不断受到挑战。软件系统通过这些挑战不断发展,并在一段时间内变得越来越擅长应对这些挑战。亚马逊的 GameDay 练习和 Netflix 的 Simian Army 是这种反脆弱性实验的很好例子。

快速失败是另一个用于构建容错、弹性系统的概念。这种理念主张期望系统出现故障,而不是构建永远不会出现故障的系统。重要的是系统能够多快地失败,以及如果失败了,它能够多快地从失败中恢复。采用这种方法,重点从“平均故障间隔时间”(MTBF)转移到“平均恢复时间”(MTTR)。这种方法的一个关键优势是,如果出现问题,系统会自行终止,并且下游功能不会受到压力。

自愈在微服务部署中通常被使用,系统会自动从失败中学习并进行调整。这些系统还可以防止未来的失败。

微服务示例

在实施微服务时,没有“一刀切”的方法。在本节中,将分析不同的示例来阐明微服务的概念。

假期门户网站示例

在第一个示例中,我们将回顾一个假期门户网站,Fly By Points。Fly By Points 收集了当客户通过在线网站预订酒店、航班或汽车时积累的积分。当客户登录 Fly By Points 网站时,他/她可以看到积累的积分、通过兑换积分可以获得的个性化优惠以及即将到来的旅行(如果有的话)。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

假设前一页是登录后的主页。Jeo有两次即将到来的旅行,四个个性化的优惠和 21,123 积分。当用户点击每个框时,将查询并显示详细信息。

假期门户网站采用了基于 Java Spring 的传统单体应用架构,如下所示:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

如前图所示,假期门户网站的架构是基于 Web 的模块化架构,各层之间有明确的分离。按照通常的做法,假期门户网站也部署为单个 WAR 文件,放在诸如 Tomcat 之类的 Web 服务器上。数据存储在一个全面的关系型数据库中。当业务增长、用户基数扩大并且复杂性增加时,交易量也会成比例增加。在这一点上,企业应该考虑将单体应用重新架构为微服务,以获得更快的交付速度、灵活性和可管理性。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

检查这个应用程序的简单微服务版本时,我们可以立即注意到架构中的一些事情:

  • 每个子系统现在都成为了一个独立的系统,即一个微服务。有三个微服务代表三个业务功能:TripsOffersPoints。每个微服务都有自己的内部数据存储和中间层。每个服务的内部结构保持不变。

  • 每个服务都封装了自己的数据库以及自己的 HTTP 监听器。与之前的模型相反,没有 Web 服务器或 WAR。相反,每个服务都有自己的嵌入式 HTTP 监听器,如 Jetty、Tomcat 等。

  • 每个微服务都会暴露一个 REST 服务来操作属于该服务的资源/实体。

假设呈现层是使用客户端 JavaScript MVC 框架(如 Angular JS)开发的。这些客户端框架能够直接调用 REST 调用。

当网页加载时,三个框,Trips、Offers 和 Points 将显示出来,包括积分、优惠数量和旅行次数等详细信息。每个框都会独立地通过 REST 对应的后端微服务进行异步调用。服务层之间没有依赖关系。当用户点击任何一个框时,屏幕将进行过渡并加载所点击项目的详细信息。这将通过对应微服务的另一个调用来完成。

基于微服务的订单管理系统

让我们再来看一个微服务示例:在线零售网站。在这种情况下,我们将更多地关注后端服务,比如处理订单事件的订单服务,当客户通过网站下订单时会生成订单事件:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

这个微服务系统完全基于反应式编程实践设计。

提示

在以下网址阅读有关响应式编程的更多信息:

www.reactivemanifesto.org

当事件发布时,许多微服务准备好在接收事件时启动。它们每一个都是独立的,不依赖于其他微服务。这种模型的优势在于我们可以不断添加或替换微服务以满足特定需求。

在上图中,显示了八个微服务。在订单事件到达时,发生以下活动:

  1. 订单服务在接收到订单事件时启动。订单服务创建订单并将详细信息保存到自己的数据库中。

  2. 如果订单成功保存,订单服务将创建并发布订单成功事件。

  3. 当订单成功事件到达时,一系列操作发生。

  4. 交付服务接受事件并放置交付记录以将订单交付给客户。这反过来生成交付事件并发布事件。

  5. 运输服务接收交付事件并处理。例如,运输服务创建运输计划。

  6. 客户通知服务向客户发送通知,通知客户订单已经下达。

  7. 库存缓存服务使用可用产品数量更新库存缓存。

  8. 库存重新订购服务检查库存限制是否足够,并在需要时生成补货事件。

  9. 客户积分服务根据此购买重新计算客户的忠诚积分。

  10. 客户账户服务更新客户账户中的订单历史记录。

在这种方法中,每个服务只负责一个功能。服务接受并生成事件。每个服务都是独立的,不知道自己的邻居。因此,邻居可以像蜂窝类比中提到的那样有机地增长。新服务可以根据需要添加。添加新服务不会影响任何现有服务。

旅行代理门户的示例

这第三个示例是一个简单的旅行代理门户应用程序。在这个例子中,我们将看到同步的 REST 调用以及异步事件。

在这种情况下,门户只是一个包含多个菜单项或链接的容器应用程序。当请求特定页面时,例如当单击菜单或链接时,它们将从特定的微服务加载。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

当客户请求预订时,内部发生以下事件:

  1. 旅行代理打开航班 UI,搜索航班,并为客户确定合适的航班。在幕后,航班 UI 是从航班微服务加载的。航班 UI 只与航班微服务内部的自己的后端 API 交互。在这种情况下,它通过 REST 调用航班微服务来加载要显示的航班。

  2. 然后,旅行代理通过访问客户 UI 查询客户详细信息。与航班 UI 类似,客户 UI 是从客户微服务加载的。客户 UI 中的操作将在客户微服务上调用 REST 调用。在这种情况下,通过调用客户微服务上的适当 API 加载客户详细信息。

  3. 然后,旅行代理检查客户的签证详细信息,以确定客户是否有资格前往所选国家。这也遵循前两点中提到的相同模式。

  4. 接下来,旅行代理使用预订微服务的预订 UI 进行预订,这再次遵循相同的模式。

  5. 支付页面是从支付微服务加载的。一般来说,支付服务有额外的约束条件,比如 PCIDSS 合规性(保护和加密数据在传输和静态数据)。微服务方法的优势在于,与单体应用相比,其他微服务不需要考虑 PCIDSS 的监管范围,而单体应用中整个应用都受 PCIDSS 规则的约束。支付也遵循前面描述的模式。

  6. 一旦预订提交,预订微服务调用航班服务来验证和更新航班预订。这种编排是作为预订微服务的一部分定义的。进行预订的智能也保存在预订微服务中。作为预订过程的一部分,它还验证、检索和更新客户微服务。

  7. 最后,预订微服务发送预订事件,通知服务接收并向客户发送通知。

这里的有趣因素是,我们可以在不影响其他微服务的情况下更改微服务的用户界面、逻辑和数据。

这是一种清晰而整洁的方法。可以通过组合不同微服务的不同屏幕来构建许多门户应用程序,特别是针对不同的用户群体。整体行为和导航将由门户应用程序控制。

除非页面是根据这种方法设计的,否则这种方法会面临许多挑战。请注意,网站布局和静态内容将由内容管理系统CMS)作为布局模板加载。或者,这些内容可以存储在 Web 服务器上。网站布局可能包含从微服务在运行时加载的 UI 片段。

微服务的好处

微服务相对于传统的多层、单片架构有许多优势。本节解释了微服务架构方法的一些关键优势。

支持多语言架构

有了微服务,架构师和开发人员可以为每个微服务选择合适的架构和技术。这样可以灵活地以更具成本效益的方式设计更合适的解决方案。

由于微服务是自治和独立的,每个服务可以使用自己的架构或技术,或者不同版本的技术运行。

以下是一个简单、实际的微服务多语言架构的示例。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

有一个要求对所有系统交易进行审计,并记录诸如请求和响应数据、发起交易的用户、调用的服务等交易细节。

如前图所示,核心服务如订单和产品微服务使用关系型数据存储,而审计微服务将数据持久化在 Hadoop 文件系统(HDFS)中。在存储大数据量的情况下,关系型数据存储既不理想也不划算,比如审计数据。在单体架构中,应用通常使用一个共享的单一数据库来存储订单、产品和审计数据。

在这个例子中,审计服务是一个使用不同架构的技术微服务。同样,不同的功能服务也可以使用不同的架构。

在另一个例子中,可能有一个运行在 Java 7 上的预订微服务,而搜索微服务可能在 Java 8 上运行。同样,订单微服务可以用 Erlang 编写,而交付微服务可以使用 Go 语言。这些在单体架构中都是不可能的。

促进实验和创新

现代企业正在追求快速成功。微服务是企业实施颠覆性创新的关键推动因素之一,因为它们提供了实验和快速失败的能力。

由于服务相当简单且规模较小,企业可以承担尝试新的流程、算法、业务逻辑等。对于大型单片应用程序,实验并不容易;也不直接或成本效益高。企业必须花费巨资来构建或更改应用程序以尝试新的东西。使用微服务,可以编写一个小型微服务来实现目标功能,并以一种反应式的方式将其插入系统中。然后可以对新功能进行几个月的实验,如果新的微服务不如预期那样工作,我们可以更改或替换为另一个。与单片方法相比,变更成本将大大降低。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

在另一个航空公司预订网站的示例中,航空公司希望在其预订页面上显示个性化的酒店推荐。这些推荐必须显示在预订确认页面上。

如前图所示,编写一个可以插入单片应用程序预订流程的微服务比在单片应用程序本身中包含此要求更方便。航空公司可以选择从一个简单的推荐服务开始,并不断用更新的版本替换,直到满足所需的准确性。

弹性和可选择性可扩展

由于微服务是较小的工作单元,它们使我们能够实现选择性的可伸缩性。

不同功能在应用程序中可能有不同的可伸缩性要求。作为单个 WAR 或 EAR 打包的单片应用程序只能作为一个整体进行扩展。当 I/O 密集型功能以高速数据流进行传输时,很容易降低整个应用程序的服务水平。

在微服务的情况下,每个服务都可以独立地进行横向或纵向扩展。由于可在每个服务上选择性地应用可伸缩性,因此与微服务方法相比,扩展的成本相对较低。

在实践中,有许多不同的方法可用于扩展应用程序,这在很大程度上取决于应用程序的架构和行为。Scale Cube主要定义了扩展应用程序的三种方法:

  • 通过水平克隆应用程序来扩展x

  • 通过分割不同的功能来扩展y

  • 通过分区或分片数据来扩展z

提示

在以下网站了解有关 Scale Cube 的更多信息:

theartofscalability.com/

当将y轴扩展应用于单片应用程序时,它将单片应用程序分解为与业务功能对齐的较小单元。许多组织成功地应用了这种技术,摆脱了单片应用程序。原则上,功能的结果单元符合微服务的特征。

例如,在典型的航空公司网站上,统计数据表明,航班搜索与航班预订的比例可能高达 500:1。这意味着每 500 次搜索交易就会有一次预订交易。在这种情况下,搜索需要比预订功能多 500 倍的可伸缩性。这是选择性扩展的理想用例。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

解决方案是将搜索请求和预订请求区分对待。在单片架构中,这只有在规模立方体的z扩展中才可能。然而,这种方法很昂贵,因为在z规模中,整个代码库都会被复制。

在前面的图表中,搜索和预订被设计为不同的微服务,以便搜索可以与预订不同比例地扩展。在图表中,搜索有三个实例,预订有两个实例。选择性的可伸缩性不仅限于实例的数量,如图表所示,还包括微服务的架构方式。在搜索的情况下,可以使用诸如 Hazelcast 之类的内存数据网格IMDG)作为数据存储。这将进一步提高搜索的性能和可伸缩性。当实例化一个新的搜索微服务实例时,将向 IMDG 集群添加一个额外的 IMDG 节点。预订不需要相同级别的可伸缩性。在预订的情况下,预订的两个实例都连接到同一个数据库实例。

允许替换

微服务是自包含的、独立的部署模块,使得可以用另一个类似的微服务替换一个微服务。

许多大型企业遵循购买与自建政策来实施软件系统。一个常见的情况是大部分功能在内部开发,而从外部专家购买某些特定的能力。这在传统的单片应用程序中存在挑战,因为这些应用程序组件高度内聚。试图将第三方解决方案插入单片应用程序会导致复杂的集成。而在微服务中,这不是事后想法。从架构上看,一个微服务可以很容易地被另一个内部开发的微服务或者来自第三方的微服务所替代。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

航空公司业务中的定价引擎是复杂的。不同航线的票价是使用复杂的数学公式计算的,称为定价逻辑。航空公司可以选择从市场上购买定价引擎,而不是自行开发产品。在单片架构中,定价是票价和预订的一个功能。在大多数情况下,定价、票价和预订都是硬编码的,几乎不可能分离。

在设计良好的微服务系统中,预订、票价和定价将是独立的微服务。替换定价微服务对其他任何服务的影响将最小,因为它们都是松散耦合和独立的。今天,它可能是第三方服务;明天,它可能很容易被另一个第三方或自行开发的服务所替代。

使有机系统建设成为可能

微服务帮助我们构建有机性质的系统。这在逐步将单片系统迁移到微服务时非常重要。

有机系统是指随着时间的推移,通过不断添加更多功能而横向增长的系统。在实践中,一个应用程序在其生命周期内增长得难以想象地庞大,而且在大多数情况下,应用程序的可管理性在同一时期内急剧减少。

微服务是关于独立可管理的服务。这使我们能够在需要时不断添加更多的服务,对现有服务的影响最小。构建这样的系统并不需要巨额资本投资。因此,企业可以将其作为运营支出的一部分不断建设。

多年前,一家航空公司建立了一个忠诚度系统,针对个人乘客。一切都很顺利,直到航空公司开始向其企业客户提供忠诚度福利。企业客户是以公司为单位分组的个人。由于当前系统的核心数据模型是扁平的,针对个人,企业环境需要对核心数据模型进行根本性的改变,因此需要大量的重塑,以满足这一要求。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

如前图所示,在基于微服务的架构中,客户信息将由客户微服务管理,忠诚度将由忠诚度积分微服务管理。

在这种情况下,很容易添加一个新的企业客户微服务来管理企业客户。当一个公司注册时,个别成员将被推送到客户微服务中,以便像往常一样管理他们。企业客户微服务通过从客户微服务中聚合数据来提供企业视图。它还将提供支持企业特定业务规则的服务。采用这种方法,添加新服务对现有服务的影响将最小化。

帮助减少技术债务

由于微服务体积较小且依赖性较小,它们允许以最小成本迁移使用末期技术的服务。

技术变化是软件开发中的障碍之一。在许多传统的单片应用程序中,由于技术的快速变化,今天的下一代应用程序甚至在发布到生产之前就可能成为遗留系统。架构师和开发人员倾向于通过添加抽象层来保护技术变化。然而,实际上,这种方法并不能解决问题,反而导致了过度设计的系统。由于技术升级通常是风险和昂贵的,并且对业务没有直接回报,因此业务可能不愿意投资于减少应用程序的技术债务。

使用微服务,可以单独为每个服务更改或升级技术,而不是升级整个应用程序。

例如,将使用 EJB 1.1 和 Hibernate 编写的五百万行代码升级到 Spring、JPA 和 REST 服务几乎等同于重写整个应用程序。在微服务世界中,这可以逐步完成。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

如前图所示,旧版本的服务在旧版本的技术上运行,而新服务开发可以利用最新的技术。与增强单片应用程序相比,使用末期技术迁移微服务的成本要低得多。

允许不同版本的共存

由于微服务将服务运行时环境与服务本身打包在一起,这使得同一环境中可以存在多个服务版本。

将出现需要同时运行同一服务的多个版本的情况。零停机推广,其中必须从一个版本平稳地切换到另一个版本,就是这样一个情况的例子,因为会有一个时间窗口,两个服务必须同时运行。对于单片应用程序来说,这是一个复杂的过程,因为在集群的一个节点中升级新服务是很麻烦的,例如,这可能导致类加载问题。金丝雀发布,其中新版本只发布给少数用户以验证新服务,是另一个需要多个服务版本共存的例子。

使用微服务,这两种情况都很容易管理。由于每个微服务都使用独立的环境,包括像 Tomcat 或 Jetty 嵌入式的服务监听器,因此可以发布多个版本并在没有太多问题的情况下进行平稳过渡。当消费者查找服务时,他们会寻找特定版本的服务。例如,在金丝雀发布中,新用户界面发布给用户 A。当用户 A 发送请求到微服务时,它会查找金丝雀发布版本,而所有其他用户将继续查找上一个生产版本。

在数据库层面需要注意确保数据库设计始终向后兼容,以避免破坏更改。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

如前图所示,客户服务的版本 1 和 2 可以共存,因为它们不会相互干扰,考虑到它们各自的部署环境。路由规则可以在网关处设置,以将流量转发到特定实例,如图所示。或者,客户端可以在请求本身中请求特定版本。在图中,网关根据请求的来源地选择版本。

支持自组织系统的构建

微服务帮助我们构建自组织系统。自组织系统支持将自动化部署,具有弹性,并展现自愈和自学习能力。

在良好架构的微服务系统中,服务不知道其他服务。它接受来自选定队列的消息并处理它。在处理结束时,它可能发送另一条消息,触发其他服务。这使我们可以将任何服务放入生态系统中,而无需分析对整个系统的影响。根据输入和输出,服务将自组织到生态系统中。不需要额外的代码更改或服务编排。没有中央大脑来控制和协调流程。

想象一下,现有的通知服务监听INPUT队列并将通知发送到SMTP服务器,如下图所示:

支持自组织系统的构建

假设以后需要引入一个个性化引擎,负责将消息的语言更改为客户的母语,以在发送给客户之前个性化消息,个性化引擎负责将消息的语言更改为客户的母语。

支持自组织系统的构建

使用微服务,将创建一个新的个性化微服务来执行此任务。输入队列将在外部配置服务器中配置为 INPUT,并且个性化服务将从 INPUT 队列中获取消息(之前由通知服务使用),并在完成处理后将消息发送到 OUTPUT 队列。通知服务的输入队列将然后发送到 OUTPUT。从下一刻起,系统将自动采用这种新的消息流。

支持事件驱动架构

微服务使我们能够开发透明的软件系统。传统系统通过本机协议相互通信,因此表现为黑匣子应用程序。业务事件和系统事件,除非明确发布,否则很难理解和分析。现代应用程序需要数据进行业务分析,以了解动态系统行为,并分析市场趋势,它们还需要响应实时事件。事件是数据提取的有用机制。

良好架构的微服务始终使用事件作为输入和输出。这些事件可以被任何服务利用。一旦提取,事件可以用于各种用例。

例如,业务希望实时查看按产品类型分类的订单速度。在单片系统中,我们需要考虑如何提取这些事件。这可能会对系统造成改变。

支持事件驱动架构

在微服务世界中,订单事件在订单创建时已经发布。这意味着只需要添加一个新的服务来订阅相同的主题,提取事件,执行请求的聚合,并推送另一个事件供仪表板消费。

启用 DevOps

微服务是 DevOps 的关键推动因素之一。DevOps 被广泛采用作为许多企业的实践,主要是为了提高交付速度和敏捷性。成功采用 DevOps 需要文化变革、流程变革以及架构变革。DevOps 主张具有敏捷开发、高速发布周期、自动化测试、自动化基础设施配置和自动化部署。

用传统的单片应用程序自动化所有这些过程是非常难以实现的。微服务并不是终极答案,但在许多 DevOps 实施中,微服务处于中心舞台。许多 DevOps 工具和技术也围绕着微服务的使用而不断发展。

考虑一个需要数小时才能完成完整构建并且需要 20 到 30 分钟才能启动应用程序的单片应用程序;可以看出这种应用程序不太适合 DevOps 自动化。很难在每次提交时自动化持续集成。由于大型的单片应用程序不太适合自动化,连续测试和部署也很难实现。

另一方面,小型微服务更易于自动化,并且因此更容易支持这些要求。

微服务还可以为开发提供更小、更专注的敏捷团队。团队将根据微服务的边界进行组织。

与其他架构风格的关系

现在我们已经看到了微服务的特点和好处,在本节中,我们将探讨微服务与其他密切相关的架构风格(如 SOA 和十二要素应用)的关系。

与 SOA 的关系

SOA 和微服务遵循类似的概念。在本章的前面,我们讨论了微服务是从 SOA 发展而来的,并且许多服务特点在这两种方法中都是共通的。

然而,它们是相同的还是不同的?

由于微服务是从 SOA 发展而来的,许多微服务的特点与 SOA 相似。让我们首先来看一下 SOA 的定义。

The Open Group联盟对 SOA 的定义如下:

“面向服务的架构(SOA)是一种支持服务定位的架构风格。服务定位是一种以服务和基于服务的开发思维方式以及服务的结果。

一个服务:

是一个可重复的业务活动的逻辑表示,具有指定的结果(例如,检查客户信用、提供天气数据、整合钻井报告)

它是自包含的。

它可能由其他服务组成。

对于服务的消费者来说,这是一个“黑匣子”。

我们也在微服务中观察到了类似的方面。那么,微服务有什么不同之处呢?答案是:这取决于情况。

对于上一个问题的答案可能是肯定的,也可能是否定的,这取决于组织及其对 SOA 的采用。SOA 是一个更广泛的术语,不同的组织以不同的方式来解决不同的组织问题。微服务和 SOA 之间的区别在于组织如何对待 SOA。

为了明确,将研究一些案例。

面向服务的集成

面向服务的集成是指许多组织使用的基于服务的集成方法。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

许多组织主要使用 SOA 来解决其集成复杂性,也被称为集成意大利面。一般来说,这被称为面向服务的集成SOI)。在这种情况下,应用程序通过一个通用的集成层使用标准协议和消息格式进行通信,例如基于 SOAP/XML 的 Web 服务通过 HTTP 或 JMS。这些类型的组织专注于企业集成模式EIP)来建模其集成需求。这种方法严重依赖于重量级的 ESB,如 TIBCO Business Works、WebSphere ESB、Oracle ESB 等。大多数 ESB 供应商还打包了一套相关产品,如规则引擎、业务流程管理引擎等,作为 SOA 套件。这些组织的集成深深扎根于他们的产品中。他们要么在 ESB 层中编写繁重的编排逻辑,要么在服务总线中编写业务逻辑本身。在这两种情况下,所有企业服务都通过 ESB 部署和访问。这些服务通过企业治理模型进行管理。对于这样的组织,微服务与 SOA 完全不同。

遗留系统现代化

SOA 也用于在遗留应用程序之上构建服务层。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

另一类组织将在转型项目或遗留现代化项目中使用 SOA。在这种情况下,服务是在 ESB 层构建和部署的,通过 ESB 适配器连接到后端系统。对于这些组织来说,微服务与 SOA 是不同的。

面向服务的应用程序

一些组织在应用程序级别采用 SOA。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

在这种方法中,轻量级的集成框架,如 Apache Camel 或 Spring Integration,被嵌入到应用程序中,用于处理与服务相关的横切能力,如协议转换、并行执行、编排和服务集成。由于一些轻量级集成框架具有本地 Java 对象支持,这样的应用程序甚至会使用本地普通旧 Java 对象POJO)服务进行集成和服务之间的数据交换。因此,所有服务都必须打包为一个单体 Web 存档。这样的组织可能会将微服务视为其 SOA 的下一个逻辑步骤。

使用 SOA 进行单体迁移

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

最后一种可能性是在单体系统达到瓶颈后,将单体应用程序转换为更小的单元。他们会将应用程序分解为更小的、可以物理部署的子系统,类似于前面解释的y轴扩展方法,并将它们部署为 Web 服务器上的 Web 存档或部署为一些自制容器上的 JAR 文件。这些作为服务的子系统将使用 Web 服务或其他轻量级协议在服务之间交换数据。他们还将使用 SOA 和服务设计原则来实现这一点。对于这样的组织,他们可能倾向于认为微服务只是新瓶装旧酒。

与十二要素应用的关系

云计算是一种快速发展的技术之一。云计算承诺了许多好处,如成本优势、速度、灵活性和弹性。有许多云提供商提供不同的服务。他们降低了成本模型,使其对企业更具吸引力。不同的云提供商,如 AWS、微软、Rackspace、IBM、谷歌等,使用不同的工具、技术和服务。另一方面,企业意识到这个不断发展的战场,因此,他们正在寻找从锁定到单一供应商的风险降低的选择。

许多组织将它们的应用程序迁移到云中。在这种情况下,应用程序可能无法实现云平台所承诺的所有好处。有些应用程序需要进行彻底改造,而有些可能只需要在移动到云之前进行小的调整。这在很大程度上取决于应用程序的架构和开发方式。

例如,如果应用程序的生产数据库服务器 URL 被硬编码为应用程序 WAR 的一部分,那么在将应用程序移动到云之前,需要对其进行修改。在云中,基础设施对应用程序是透明的,特别是物理 IP 地址不能被假定。

我们如何确保应用程序,甚至微服务,可以在多个云提供商之间无缝运行,并利用云服务的优势,比如弹性?

在开发云原生应用程序时,遵循一定的原则是很重要的。

提示

云原生是指开发能够在云环境中高效工作的应用程序的术语,理解和利用云行为,比如弹性、基于利用率的计费、故障感知等。

由 Heroku 提出的 Twelve-Factor App 是一种描述现代云就绪应用程序所期望具备的特征的方法论。Twelve-Factor App 同样适用于微服务。因此,理解 Twelve-Factor App 是很重要的。

单一的代码库

代码库原则建议每个应用程序都有一个单一的代码库。可以部署多个相同代码库的实例,比如开发、测试和生产。代码通常在诸如 Git、Subversion 等源代码控制系统中进行管理。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

将相同的理念应用于微服务,每个微服务都应该有自己的代码库,并且这个代码库不与任何其他微服务共享。这也意味着一个微服务有且仅有一个代码库。

捆绑依赖

根据这一原则,所有应用程序应该将它们的依赖项与应用程序捆绑在一起。借助 Maven 和 Gradle 等构建工具,我们可以在pom.xml.gradle文件中明确管理依赖项,并使用诸如 Nexus 或 Archiva 之类的中央构建存储库将它们链接起来。这确保了版本的正确管理。最终的可执行文件将被打包为 WAR 文件或可执行的 JAR 文件,嵌入所有的依赖项。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

在微服务的背景下,这是一个必须遵循的基本原则。每个微服务应该将所有所需的依赖项和执行库(如 HTTP 监听器等)捆绑在最终的可执行包中。

外部化配置

这一原则建议从代码中外部化所有配置参数。应用程序的配置参数在不同的环境中会有所不同,比如对外部系统的电子邮件 ID 或 URL 的支持,用户名、密码、队列名称等。这些在开发、测试和生产环境中都会有所不同。所有服务配置都应该被外部化。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

同样的原则对于微服务也是显而易见的。微服务的配置参数应该从外部源加载。这也将有助于自动化发布和部署过程,因为这些环境之间唯一的区别是配置参数。

后端服务是可寻址的

所有后端服务都应该通过可寻址的 URL 访问。所有服务在其执行周期中需要与一些外部资源进行通信。例如,它们可能会监听或发送消息到消息系统,发送电子邮件,将数据持久化到数据库等等。所有这些服务都应该通过 URL 可达,而不需要复杂的通信要求。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

在微服务世界中,微服务要么通过消息系统发送或接收消息,要么可以接受或发送消息到其他服务 API。在常规情况下,这些要么是使用 REST 和 JSON 的 HTTP 端点,要么是基于 TCP 或 HTTP 的消息端点。

构建、发布和运行之间的隔离

这一原则主张在构建、发布和运行阶段之间进行强大的隔离。构建阶段指的是通过包含所有所需资产来编译和生成二进制文件。发布阶段指的是将二进制文件与特定于环境的配置参数相结合。运行阶段指的是在特定的执行环境中运行应用程序。流水线是单向的,因此不可能将运行阶段的更改传播回构建阶段。基本上,这也意味着不建议为生产进行特定的构建;而是必须通过流水线进行。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

在微服务中,构建将创建可执行的 JAR 文件,包括诸如 HTTP 监听器之类的服务运行时。在发布阶段,这些可执行文件将与发布配置(如生产 URL 等)相结合,创建一个发布版本,很可能是类似 Docker 的容器。在运行阶段,这些容器将通过容器调度程序部署到生产环境。

无状态,共享无事务处理

这一原则建议进程应该是无状态的并且不共享任何东西。如果应用程序是无状态的,那么它就是容错的,并且可以很容易地扩展。

所有微服务都应设计为无状态函数。如果有存储状态的要求,应该使用后备数据库或内存缓存来完成。

通过端口绑定公开服务

预期十二要素应用程序是自包含的。传统上,应用程序部署到服务器上:Web 服务器或应用服务器,如 Apache Tomcat 或 JBoss。十二要素应用程序不依赖外部 Web 服务器。HTTP 监听器,如 Tomcat 或 Jetty,必须嵌入到服务本身中。

端口绑定是微服务能够自主和自包含的基本要求之一。微服务将服务监听器嵌入到服务本身作为其一部分。

并发性以扩展规模

这一原则规定进程应该被设计为通过复制进程来扩展。这是在进程内使用线程之外的另一种方法。

在微服务世界中,服务被设计为扩展而不是扩大。x轴扩展技术主要用于通过启动另一个相同的服务实例来扩展服务。根据流量流动,服务可以弹性地扩展或收缩。此外,微服务可能利用并行处理和并发框架来进一步加快或扩展事务处理。

具有最小开销的可处置性

这一原则主张以最小的启动和关闭时间以及优雅的关闭支持构建应用程序。在自动化部署环境中,我们应该能够尽快启动或关闭实例。如果应用程序的启动或关闭需要相当长的时间,将对自动化产生不利影响。启动时间与应用程序的大小成正比。在针对自动扩展的云环境中,我们应该能够快速启动新实例。这也适用于推广新版本的服务。

在微服务上下文中,为了实现完全自动化,将应用程序的大小保持尽可能小,启动和关闭时间尽可能短是非常重要的。微服务还应考虑对象和数据的延迟加载。

开发和生产的对等性

这个原则强调了尽可能保持开发和生产环境的相同重要性。例如,让我们考虑一个具有多个服务或进程的应用程序,比如作业调度服务、缓存服务和一个或多个应用程序服务。在开发环境中,我们倾向于在一台机器上运行它们,而在生产环境中,我们将为每个进程提供独立的机器来运行。这主要是为了管理基础设施的成本。缺点是,如果生产环境出现问题,就没有相同的环境来重新生成和修复问题。

这个原则不仅适用于微服务,也适用于任何应用程序开发。

外部化日志

十二要素应用程序永远不会尝试存储或传输日志文件。在云中,最好避免本地 I/O。如果在特定基础设施中 I/O 速度不够快,可能会造成瓶颈。解决这个问题的方法是使用集中式日志框架。Splunk、Greylog、Logstash、Logplex 和 Loggly 是一些日志传输和分析工具的例子。推荐的方法是通过连接 logback appenders 将日志传输到一个中央存储库,并写入其中一个传输点。

在微服务生态系统中,这一点非常重要,因为我们正在将一个系统分解成许多较小的服务,这可能导致日志的去中心化。如果它们将日志存储在本地存储中,将极其难以在服务之间进行日志相关性的对比。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

在开发中,微服务可以将日志流重定向到stdout,而在生产中,这些流将被日志传输器捕获,并发送到中央日志服务进行存储和分析。

打包管理进程

除了应用程序服务,大多数应用程序还提供管理任务。这个原则建议对应用程序服务和管理任务使用相同的发布包以及相同的环境。管理代码也应该与应用程序代码一起打包。

这个原则不仅适用于微服务,也适用于任何应用程序开发。

微服务用例

微服务并不是万能药,也不会解决当今世界的所有架构挑战。关于何时使用微服务并没有硬性规定或严格的指导方针。

微服务可能并不适用于每种用例。微服务的成功很大程度上取决于用例的选择。首要活动是对用例进行酸碱试验,以检验其是否符合微服务的好处。酸碱试验必须涵盖我们在本章前面讨论过的所有微服务的好处。对于给定的用例,如果没有可量化的好处,或者成本超过了好处,那么该用例可能不适合微服务。

让我们讨论一些常用的适合微服务架构的场景:

  • 由于需要改进可伸缩性、可管理性、灵活性或交付速度,迁移单片应用程序。另一个类似的情况是重写一个即将到期且被广泛使用的遗留应用程序。在这两种情况下,微服务都提供了一个机会。使用微服务架构,可以通过逐步将功能转换为微服务来重新平台化遗留应用程序。这种方法有好处。不需要巨额的前期投资,不会对业务造成重大干扰,也没有严重的业务风险。由于服务依赖关系已知,可以很好地管理微服务的依赖关系。

  • 诸如集成优化服务、预测服务、价格计算服务、预测服务、报价服务、推荐服务等的实用计算场景都是微服务的良好候选者。这些是独立的无状态计算单元,接受特定数据,应用算法,并返回结果。独立的技术服务,如通信服务、加密服务、认证服务等也是微服务的良好候选者。

  • 在许多情况下,我们可以构建无头业务应用程序或具有自主性质的服务,例如支付服务、登录服务、航班搜索服务、客户档案服务、通知服务等等。这些通常在多个渠道中被重复使用,因此很适合构建为微服务。

  • 可能存在微型或宏型应用程序,用于单一目的并执行单一职责。一个简单的时间跟踪应用程序就是这一类的例子。它所做的就是捕获时间、持续时间和执行的任务。常用的企业应用程序也是微服务的候选者。

  • 良好架构、响应式客户端 MVC web 应用程序的后端服务(后端即服务BaaS)场景)根据用户导航需求加载数据。在大多数情况下,数据可能来自于多个逻辑上不同的数据源,就像之前提到的 飞越点 示例一样。

  • 高度敏捷的应用程序、需要快速交付或上市时间、创新试点、选择进行 DevOps 的应用程序、创新型系统的应用程序等等也可以被视为微服务架构的潜在候选者。

  • 我们可以预期从微服务中获益的应用程序,例如多语言要求、需要 命令查询职责分离CQRS)等等,也是微服务架构的潜在候选者。

如果使用案例属于这些类别之一,它就是微服务架构的潜在候选者。

有一些情况下,我们应该考虑避免使用微服务:

  • 如果组织的政策被迫使用集中管理的重量级组件,例如 ESB 来托管业务逻辑,或者如果组织有任何其他阻碍微服务基本原则的政策,那么微服务就不是正确的解决方案,除非组织流程得到放松。

  • 如果组织的文化、流程等等是基于传统的瀑布交付模型、漫长的发布周期、矩阵团队、手动部署和繁琐的发布流程、没有基础设施供应等等,那么微服务可能不适合。这是康威定律的基础。这一定律指出组织结构与其创建的软件之间存在着强烈的联系。

提示

阅读更多关于康威定律的信息:

www.melconway.com/Home/Conways_Law.html

微服务的早期采用者

许多组织已经成功地踏上了微服务世界的旅程。在本节中,我们将研究一些微服务领域的先驱者,分析他们为什么这样做以及他们是如何做到的。最后我们将进行一些分析以得出一些结论:

  • Netflix(www.netflix.com):Netflix 是一家国际点播媒体流媒体公司,在微服务领域是先驱。Netflix 将大量开发传统单片代码的开发人员转变为生产微服务的较小开发团队。这些微服务共同工作,向数百万 Netflix 客户流媒体数字媒体。在 Netflix,工程师们从单片开始,经历了痛苦,然后将应用程序分解为松散耦合且与业务能力对齐的较小单元。

  • Uber(www.uber.com):Uber 是一家国际运输网络公司,于 2008 年开始使用单片架构和单一代码库。所有服务都嵌入到单片应用程序中。当 Uber 将业务从一个城市扩展到多个城市时,挑战开始了。Uber 随后通过将系统分解为较小的独立单元,转向基于 SOA 的架构。每个模块都交给不同的团队,并授权他们选择自己的语言、框架和数据库。Uber 在其生态系统中部署了许多使用 RPC 和 REST 的微服务。

  • Airbnb(www.airbnb.com):Airbnb 是提供可信住宿市场的世界领先公司,开始使用一个执行业务所需功能的单片应用程序。随着流量增加,Airbnb 面临可扩展性问题。单一代码库变得过于复杂,导致关注点分离不良,并出现性能问题。Airbnb 将其单片应用程序分解为在单独机器上运行的具有单独部署周期的独立代码库的较小部分。Airbnb 围绕这些服务开发了自己的微服务或 SOA 生态系统。

  • Orbitz(www.orbitz.com):Orbitz 是一个在线旅行门户,在 2000 年代开始使用单片架构,有 Web 层、业务层和数据库层。随着 Orbitz 业务的扩展,他们面临了单片分层架构的可管理性和可扩展性问题。Orbitz 随后经历了持续的架构变化。后来,Orbitz 将其单片应用程序分解为许多较小的应用程序。

  • eBay(www.ebay.com):eBay 是最大的在线零售商之一,于上世纪 90 年代末开始使用单片 Perl 应用程序和 FreeBSD 作为数据库。随着业务的增长,eBay 经历了扩展问题。它一直在投资改进其架构。在 2000 年代中期,eBay 转向基于 Java 和 Web 服务的较小分解系统。他们采用了数据库分区和功能分离以满足所需的可扩展性。

  • 亚马逊(www.amazon.com):亚马逊是最大的在线零售商之一,于 2001 年运行了一个基于 C++的大型单片应用程序。这个良好架构的单片应用程序基于分层架构,有许多模块化组件。然而,所有这些组件都紧密耦合。因此,亚马逊无法通过将团队分成较小的组来加快其开发周期。亚马逊随后将代码分离为独立的功能服务,用 Web 服务封装,并最终发展为微服务。

  • Gilt(www.gilt.com):Gilt 是一个在线购物网站,于 2007 年开始使用分层单片 Rails 应用程序和后端的 Postgres 数据库。与许多其他应用程序类似,随着流量增加,Web 应用程序无法提供所需的弹性。Gilt 通过引入 Java 和多语言持久性进行了架构改造。后来,Gilt 转向使用微服务概念的许多较小的应用程序。

  • Twitterwww.twitter.com):Twitter,最大的社交网站之一,于 2000 年代中期开始使用三层单片的 rails 应用程序。后来,当 Twitter 的用户基数增长时,他们经历了一次架构重构周期。通过这次重构,Twitter 从典型的 Web 应用程序转向了基于 API 的事件驱动核心。Twitter 使用 Scala 和 Java 开发具有多语言持久性的微服务。

  • 耐克www.nike.com):耐克,全球服装和鞋类领导者,将他们的单片应用程序转变为微服务。与许多其他组织类似,耐克也是运行在古老的遗留应用程序上,几乎不稳定。在他们的旅程中,耐克转向了重量级商业产品,目标是稳定遗留应用程序,但最终变成了昂贵的单片应用程序,难以扩展,发布周期长,并且需要太多手动工作来部署和管理应用程序。后来,耐克转向了基于微服务的架构,大大缩短了开发周期。

共同主题是单片迁移

当我们分析前述企业时,有一个共同的主题。所有这些企业都是从单片应用程序开始,并通过应用他们以前版本的学习和痛点,转变为微服务架构。

即使在今天,许多初创公司也是从单体开始,因为这样更容易开始、概念化,然后在需求出现时慢慢转向微服务。单片到微服务的迁移场景有一个额外的优势:它们有所有的信息提前准备好,可以随时进行重构。

然而,对于所有这些企业来说,单片转变的催化剂是不同的。一些共同的动机是缺乏可伸缩性、长时间的开发周期、流程自动化、可管理性以及业务模式的变化。

虽然单片迁移是毫无疑问的,但也有机会从头开始构建微服务。与其构建从头开始的系统,不如寻找为业务快速赢得的机会,例如为航空公司的端到端货物管理系统添加卡车服务,或者为零售商的忠诚度系统添加客户评分服务。这些可以作为独立的微服务实现,并与它们各自的单片应用程序交换消息。

另一个观点是,许多组织仅将微服务用于业务关键的客户参与应用程序,而将其余的遗留单片应用程序采取自己的轨迹。

另一个重要观察是,先前检查的大多数组织在微服务旅程中处于不同的成熟水平。当 eBay 在 2000 年代初从单片应用程序过渡时,他们将应用程序在功能上分割成更小、独立、可部署的单元。这些逻辑上划分的单元被封装在 Web 服务中。虽然单一责任和自治性是它们的基本原则,但这些架构受限于当时可用的技术和工具。像 Netflix 和 Airbnb 这样的组织构建了自己的能力来解决他们面临的具体挑战。总之,所有这些都不是真正的微服务,而是遵循相同特征的小型、与业务对齐的服务。

没有所谓的“确定或最终的微服务”状态。这是一个不断发展和成熟的过程。架构师和开发人员的口头禅是可替代性原则;建立一种最大限度地提高替换其部分的能力并最小化替换成本的架构。最重要的是,企业不应该只是追随炒作来开发微服务。

总结

在本章中,您通过一些例子了解了微服务的基础知识。

我们探讨了微服务从传统的单片应用程序的演变。我们研究了一些现代应用架构所需的原则和思维转变。我们还看了一下微服务的特点和好处以及使用案例。在本章中,我们建立了微服务与面向服务的架构和十二要素应用的关系。最后,我们分析了来自不同行业的一些企业的例子。

在下一章中,我们将开发一些示例微服务,以更清晰地了解本章的内容。

第二章:使用 Spring Boot 构建微服务

开发微服务不再那么乏味,这要归功于强大的 Spring Boot 框架。Spring Boot 是一个用于开发 Java 生产就绪微服务的框架。

本章将从上一章中解释的微服务理论转移到实际操作,通过审查代码示例来介绍 Spring Boot 框架,并解释 Spring Boot 如何帮助构建符合上一章讨论的原则和特征的 RESTful 微服务。最后,将回顾 Spring Boot 提供的一些功能,使微服务达到生产就绪状态。

在本章结束时,您将学到:

  • 设置最新的 Spring 开发环境

  • 使用 Spring 框架开发 RESTful 服务

  • 使用 Spring Boot 构建完全合格的微服务

  • 使用 Spring Boot 构建生产就绪的微服务的有用功能

建立开发环境

为了明确微服务的概念,将构建一对微服务。为此,假定已安装以下组件:

也可以使用其他 IDE,如 IntelliJ IDEA、NetBeans 或 Eclipse。同样,也可以使用其他构建工具,如 Gradle。假设 Maven 仓库、类路径和其他路径变量已正确设置以运行 STS 和 Maven 项目。

本章基于以下版本的 Spring 库:

  • Spring 框架4.2.6.RELEASE

  • Spring Boot1.3.5.RELEASE

提示

下载代码包的详细步骤在本书的前言中有提到。看一看。

本书的代码包也托管在 GitHub 上,网址为github.com/PacktPublishing/Spring-Microservices。我们还有其他丰富的书籍和视频代码包可供使用,网址为github.com/PacktPublishing/。去看看吧!

开发 RESTful 服务-传统方法

在深入研究 Spring Boot 之前,本示例将回顾传统的 RESTful 服务开发。

STS 将用于开发此 REST/JSON 服务。

注意

此示例的完整源代码可在本书的代码文件中的legacyrest项目中找到。

以下是开发第一个 RESTful 服务的步骤:

  1. 启动 STS 并为该项目设置一个工作区。

  2. 导航到File | New | Project

  3. 选择Spring Legacy Project,如下截图所示,然后点击Next外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

  4. 选择Spring MVC Project,如下图所示,然后点击Next外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

  5. 选择一个顶级包名称。本示例使用org.rvslab.chapter2.legacyrest作为顶级包。

  6. 然后,点击Finish

  7. 这将在 STS 工作区中创建一个名为legacyrest的项目。

在继续之前,需要编辑pom.xml

  1. 将 Spring 版本更改为4.2.6.RELEASE,如下所示:
<org.springframework-version>4.2.6.RELEASE</org.springframework-version>
  1. pom.xml文件中添加Jackson依赖项,用于 JSON 到 POJO 和 POJO 到 JSON 的转换。请注意,使用2.*.*版本以确保与 Spring 4 的兼容性。
<dependency>
    <groupId>com.fasterxml.jackson.core</groupId>
    <artifactId>jackson-databind</artifactId>
    <version>2.6.4</version>
</dependency>
  1. 需要添加一些 Java 代码。在Java Resources下的legacyrest中,展开包并打开默认的HomeController.java文件:外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

  2. 默认实现更加面向 MVC 项目。重写HomeController.java以响应 REST 调用返回 JSON 值将会奏效。生成的HomeController.java文件将类似于以下内容:

@RestController
public class HomeController {
  @RequestMapping("/")
  public Greet sayHello(){
    return new Greet("Hello World!");
  }
}
class Greet { 
  private String message;
  public Greet(String message) {
    this.message = message;
  }
  //add getter and setter
}

检查代码,现在有两个类:

  • Greet:这是一个简单的 Java 类,具有用于表示数据对象的 getter 和 setter。Greet类中只有一个属性,即message

  • HomeController.java:这只是一个 Spring 控制器 REST 端点,用于处理 HTTP 请求。

请注意,在HomeController中使用的注释是@RestController,它会自动注入@Controller@ResponseBody,并具有与以下代码相同的效果:

@Controller
@ResponseBody
public class HomeController { }
  1. 项目现在可以通过右键单击legacyrest,导航到Run As | Run On Server,然后选择默认服务器(Pivotal tc Server Developer Edition v3.1)来运行。

这应该会自动启动服务器并在 TC 服务器上部署 Web 应用程序。

如果服务器正常启动,控制台将显示以下消息:

INFO : org.springframework.web.servlet.DispatcherServlet - FrameworkServlet 'appServlet': initialization completed in 906 ms
May 08, 2016 8:22:48 PM org.apache.catalina.startup.Catalina start
INFO: Server startup in 2289 ms

  1. 如果一切正常,STS 将打开一个浏览器窗口到http://localhost:8080/legacyrest/,并在浏览器中显示 JSON 对象。右键单击并导航到legacyrest | Properties | Web Project Settings,并查看Context Root以识别 Web 应用程序的上下文根:外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

另一个构建选项是使用 Maven。右键单击项目,导航到Run As | Maven install。这将在目标文件夹下生成chapter2-1.0.0-BUILD-SNAPSHOT.war。这个 war 文件可以部署在任何 Servlet 容器中,如 Tomcat、JBoss 等。

从传统的 Web 应用程序转向微服务

仔细检查前面的 RESTful 服务将会揭示这是否真的构成了微服务。乍一看,前面的 RESTful 服务是一个完全合格的可互操作的 REST/JSON 服务。然而,它在本质上并不是完全自治的。这主要是因为该服务依赖于底层的应用服务器或 Web 容器。在前面的例子中,一个 war 文件被明确创建并部署在 Tomcat 服务器上。

这是一种传统的开发 RESTful 服务的方法,作为 Web 应用程序。然而,从微服务的角度来看,人们需要一种机制来开发可执行的服务,即带有嵌入式 HTTP 监听器的自包含 JAR 文件。

Spring Boot 是一个工具,可以方便地开发这种类型的服务。Dropwizard 和 WildFly Swarm 是替代的无服务器 RESTful 堆栈。

使用 Spring Boot 构建 RESTful 微服务

Spring Boot 是 Spring 团队的一个实用框架,可以快速轻松地启动基于 Spring 的应用程序和微服务。该框架在决策制定方面采用了一种有见地的方法,从而减少了编写大量样板代码和配置所需的工作量。使用 80-20 原则,开发人员应该能够使用许多默认值快速启动各种 Spring 应用程序。Spring Boot 进一步为开发人员提供了定制应用程序的机会,通过覆盖自动配置的值。

Spring Boot 不仅提高了开发速度,还提供了一套生产就绪的运维功能,如健康检查和指标收集。由于 Spring Boot 掩盖了许多配置参数并抽象了许多底层实现,它在一定程度上减少了错误的机会。Spring Boot 根据类路径中可用的库识别应用程序的性质,并运行打包在这些库中的自动配置类。

许多开发人员错误地将 Spring Boot 视为代码生成器,但实际上并非如此。Spring Boot 只自动配置构建文件,例如 Maven 的 POM 文件。它还根据某些默认值设置属性,例如数据源属性。看一下以下代码:

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
    <groupId>org.hsqldb</groupId>
    <artifactId>hsqldb</artifactId>
    <scope>runtime</scope>
</dependency>

例如,在前面的案例中,Spring Boot 知道项目设置为使用 Spring Data JPA 和 HSQL 数据库。它会自动配置驱动程序类和其他连接参数。

Spring Boot 的一个伟大成果之一是几乎消除了传统的 XML 配置的需求。Spring Boot 还通过将所有所需的运行时依赖项打包到一个大的可执行 JAR 文件中来实现微服务的开发。

开始使用 Spring Boot

Spring Boot 基于应用程序开发的不同方式有很多:

  • 使用 Spring Boot CLI 作为命令行工具

  • 使用 STS 等 IDE 提供 Spring Boot,这些都是默认支持的

  • 使用 Spring Initializr 项目在start.spring.io

本章将探讨这三种选项,开发各种示例服务。

使用 CLI 开发 Spring Boot 微服务

开发和演示 Spring Boot 功能的最简单方法是使用 Spring Boot CLI,一个命令行工具。执行以下步骤:

  1. 通过从repo.spring.io/release/org/springframework/boot/spring-boot-cli/1.3.5.RELEASE/spring-boot-cli-1.3.5.RELEASE-bin.zip下载spring-boot-cli-1.3.5.RELEASE-bin.zip文件来安装 Spring Boot 命令行工具。

  2. 将文件解压缩到您选择的目录中。打开终端窗口,并将终端提示更改为bin文件夹。

确保将bin文件夹添加到系统路径中,以便可以从任何位置运行 Spring Boot。

  1. 使用以下命令验证安装。如果成功,Spring CLI 版本将打印在控制台上:
$spring –-version
Spring CLI v1.3.5.RELEASE

  1. 作为下一步,将在 Groovy 中开发一个快速的 REST 服务,Spring Boot 默认支持。为此,使用任何编辑器复制并粘贴以下代码,并将其保存为myfirstapp.groovy在任何文件夹中:
@RestController
class HelloworldController {
    @RequestMapping("/")
    String sayHello() {
        "Hello World!"
    }
}
  1. 要运行这个 Groovy 应用程序,转到保存myfirstapp.groovy的文件夹,并执行以下命令。服务器启动日志的最后几行将类似于以下内容:
$spring run myfirstapp.groovy 

2016-05-09 18:13:55.351  INFO 35861 --- [nio-8080-exec-1] o.s.web.servlet.DispatcherServlet        : FrameworkServlet 'dispatcherServlet': initialization started
2016-05-09 18:13:55.375  INFO 35861 --- [nio-8080-exec-1] o.s.web.servlet.DispatcherServlet        : FrameworkServlet 'dispatcherServlet': initialization completed in 24 ms

  1. 打开浏览器窗口,转到http://localhost:8080;浏览器将显示以下消息:

你好,世界!

没有创建 war 文件,也没有运行 Tomcat 服务器。Spring Boot 自动选择了 Tomcat 作为 Web 服务器,并将其嵌入到应用程序中。这是一个非常基本的、最小的微服务。在前面的代码中使用的@RestController注解将在下一个示例中进行详细讨论。

使用 STS 开发 Spring Boot Java 微服务

在本节中,将演示使用 STS 开发另一个基于 Java 的 REST/JSON Spring Boot 服务。

注意

本示例的完整源代码可作为本书的代码文件中的chapter2.bootrest项目获得。

  1. 打开 STS,在Project Explorer窗口中右键单击,导航到New | Project,然后选择Spring Starter Project,如下图所示,并单击Next外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

Spring Starter Project 是一个基本模板向导,提供了许多其他启动库供选择。

  1. 将项目名称命名为chapter2.bootrest或者您选择的其他名称。选择打包方式为 JAR 非常重要。在传统的 web 应用中,会创建 war 文件然后部署到 servlet 容器,而 Spring Boot 会将所有依赖项打包到一个独立的、自包含的 JAR 文件中,并带有嵌入式 HTTP 监听器。

  2. Java 版本下选择 1.8。建议 Spring 4 应用程序使用 Java 1.8。更改其他 Maven 属性,如GroupArtifactPackage,如下图所示:外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

  3. 完成后,点击下一步

  4. 向导将显示库选项。在这种情况下,由于正在开发 REST 服务,选择Web下的Web。这是一个有趣的步骤,告诉 Spring Boot 正在开发一个 Spring MVC web 应用程序,以便 Spring Boot 可以包含必要的库,包括 Tomcat 作为 HTTP 监听器和其他所需的配置:外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

  5. 点击完成

这将在 STS 的项目资源管理器中生成一个名为chapter2.bootrest的项目:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

  1. 花一点时间检查生成的应用程序。感兴趣的文件包括:
  • pom.xml

  • Application.java

  • Application.properties

  • ApplicationTests.java

检查 POM 文件

父元素是pom.xml文件中的一个有趣的方面。看一下以下内容:

<parent>
  <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-parent</artifactId>
    <version>1.3.4.RELEASE</version>
</parent>

spring-boot-starter-parent模式是 Maven 依赖管理使用的材料清单BOM)模式。BOM 是一种特殊的 POM 文件,用于管理项目所需的不同库版本。使用spring-boot-starter-parent POM 文件的优势在于开发人员无需担心找到不同库的兼容版本,比如 Spring、Jersey、JUnit、Logback、Hibernate、Jackson 等等。例如,在我们的第一个传统示例中,需要添加一个特定版本的 Jackson 库来与 Spring 4 一起使用。在这个示例中,这些都由spring-boot-starter-parent模式处理。

starter POM 文件具有一系列 Boot 依赖项、合理的资源过滤和 Maven 构建所需的合理插件配置。

提示

参考github.com/spring-projects/spring-boot/blob/1.3.x/spring-boot-dependencies/pom.xml查看 starter parent(版本 1.3.x)中提供的不同依赖项。如果需要,所有这些依赖项都可以被覆盖。

starter POM 文件本身不会向项目添加 JAR 依赖项,而是只会添加库版本。随后,当依赖项添加到 POM 文件中时,它们会引用该 POM 文件中的库版本。以下是一些属性的快照:

<spring-boot.version>1.3.5.BUILD-SNAPSHOT</spring-boot.version>
<hibernate.version>4.3.11.Final</hibernate.version>
<jackson.version>2.6.6</jackson.version>
<jersey.version>2.22.2</jersey.version>
<logback.version>1.1.7</logback.version>
<spring.version>4.2.6.RELEASE</spring.version>
<spring-data-releasetrain.version>Gosling-SR4</spring-data-releasetrain.version>
<tomcat.version>8.0.33</tomcat.version>

审查依赖部分,可以看到这是一个干净整洁的 POM 文件,只有两个依赖,如下所示:

<dependencies>
   <dependency>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-web</artifactId>
   </dependency>

   <dependency>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-test</artifactId>
  <scope>test</scope>
   </dependency>
</dependencies>

选择 web 后,spring-boot-starter-web会添加 Spring MVC 项目所需的所有依赖项。它还包括对 Tomcat 的依赖,作为嵌入式 HTTP 监听器。这提供了一种有效的方式来获取所有所需的依赖项作为一个单独的捆绑包。可以用其他库替换单个依赖项,例如用 Jetty 替换 Tomcat。

与 web 类似,Spring Boot 提供了许多spring-boot-starter-*库,比如amqpaopbatchdata-jpathymeleaf等等。

pom.xml文件中最后需要审查的是 Java 8 属性。默认情况下,父 POM 文件会添加 Java 6。建议将 Java 版本覆盖为 8 用于 Spring:

<java.version>1.8</java.version>

检查 Application.java

Spring Boot 默认在src/main/java下生成了一个org.rvslab.chapter2.Application.java类来引导,如下所示:

@SpringBootApplication
public class Application {
    public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
    }
}

Application中只有一个main方法,按照 Java 约定,在启动时将被调用。main方法通过在SpringApplication上调用run方法来引导 Spring Boot 应用程序。将Application.class作为参数传递,告诉 Spring Boot 这是主要组件。

更重要的是,这是由@SpringBootApplication注解完成的。@SpringBootApplication注解是一个顶级注解,封装了另外三个注解,如下面的代码片段所示:

@Configuration
@EnableAutoConfiguration
@ComponentScan
public class Application {

@Configuration注解提示包含的类声明一个或多个@Bean定义。@Configuration注解是元注解@Component的提示;因此,它是组件扫描的候选对象。

@EnableAutoConfiguration注解告诉 Spring Boot 根据类路径中可用的依赖项自动配置 Spring 应用程序。

检查 application.properties

默认的application.properties文件放置在src/main/resources下。这是一个重要的文件,用于配置 Spring Boot 应用程序的任何必需属性。目前,这个文件是空的,将在本章的后面一些测试用例中重新访问。

检查 ApplicationTests.java

要检查的最后一个文件是src/test/java下的ApplicationTests.java。这是一个占位符,用于针对 Spring Boot 应用程序编写测试用例。

要实现第一个 RESTful 服务,添加一个 REST 端点,如下所示:

  1. 可以编辑src/main/java下的Application.java,并添加一个 RESTful 服务实现。RESTful 服务与之前的项目中所做的完全相同。在Application.java文件的末尾添加以下代码:
@RestController
class GreetingController{
  @RequestMapping("/")
  Greet greet(){
    return new Greet("Hello World!");
  }
}
class Greet {
  private String message;
public Greet() {}

  public Greet(String message) {
    this.message = message;
  }
//add getter and setter
}
  1. 要运行,导航到Run As | Spring Boot App。Tomcat 将在8080端口上启动:外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

我们可以从日志中注意到:

  • Spring Boot 获得了自己的进程 ID(在本例中为41130

  • Spring Boot 会自动在本地主机的 Tomcat 服务器上启动,端口为8080

  1. 接下来,打开浏览器,指向http://localhost:8080。这将显示 JSON 响应,如下面的截图所示:外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

传统服务和这个服务之间的一个关键区别是,Spring Boot 服务是自包含的。为了更清楚地说明这一点,可以在 STS 之外运行 Spring Boot 应用程序。打开一个终端窗口,转到项目文件夹,并运行 Maven,如下所示:

$ maven install

这将在项目的目标文件夹下生成一个 fat JAR 文件。从命令行运行应用程序会显示:

$java -jar target/bootrest-0.0.1-SNAPSHOT.jar

正如大家所看到的,bootrest-0.0.1-SNAPSHOT.jar是自包含的,可以作为独立的应用程序运行。在这一点上,JAR 文件只有 13MB。尽管应用程序不过是一个简单的“Hello World”,但刚刚开发的 Spring Boot 服务实际上遵循了微服务的原则。

测试 Spring Boot 微服务

有多种方法可以测试 REST/JSON Spring Boot 微服务。最简单的方法是使用 Web 浏览器或指向 URL 的 curl 命令,如下所示:

curl http://localhost:8080

有许多工具可用于测试 RESTful 服务,例如 Postman、Advanced REST client、SOAP UI、Paw 等。

在这个例子中,为了测试服务,将使用 Spring Boot 生成的默认测试类。

ApplicatonTests.java中添加一个新的测试用例会导致:

@RunWith(SpringJUnit4ClassRunner.class)
@SpringApplicationConfiguration(classes = Application.class)
@WebIntegrationTest
public class ApplicationTests {
  @Test
  public void testVanillaService() {
    RestTemplate restTemplate = new RestTemplate();
    Greet greet = restTemplate.getForObject("http://localhost:8080", Greet.class);
    Assert.assertEquals("Hello World!", greet.getMessage());
  }
}

请注意,在类级别添加了@WebIntegrationTest,并删除了@WebAppConfiguration@WebIntegrationTest注解是一个方便的注解,可以确保测试针对一个完全运行的服务器。或者,@WebAppConfiguration@IntegrationTest的组合将产生相同的结果。

还要注意,RestTemplate用于调用 RESTful 服务。RestTemplate是一个实用程序类,它抽象了 HTTP 客户端的底层细节。

要测试这一点,可以打开一个终端窗口,转到项目文件夹,并运行mvn install

使用 Spring Initializr 开发 Spring Boot 微服务–HATEOAS 示例

在下一个示例中,将使用 Spring Initializr 创建一个 Spring Boot 项目。Spring Initializr 是 STS 项目向导的一个可插拔替代品,并提供了一个 Web UI 来配置和生成 Spring Boot 项目。Spring Initializr 的一个优点是它可以通过网站生成一个项目,然后可以导入到任何 IDE 中。

在本示例中,将研究基于 REST 的服务的HATEOAS应用程序状态的超文本作为引擎)概念和HAL超文本应用语言)浏览器。

HATEOAS 是一种 REST 服务模式,其中导航链接作为有效负载元数据的一部分提供。客户端应用程序确定状态并遵循作为状态的一部分提供的过渡 URL。这种方法在响应式移动和 Web 应用程序中特别有用,其中客户端根据用户导航模式下载附加数据。

HAL 浏览器是一个方便的 API 浏览器,用于浏览hal+json数据。HAL 是一种基于 JSON 的格式,它建立了表示资源之间超链接的约定。HAL 有助于使 API 更具可探索性和可发现性。

注意

此示例的完整源代码可在本书的代码文件中的chapter2.boothateoas项目中找到。

以下是使用 Spring Initilizr 开发 HATEOAS 示例的具体步骤:

  1. 要使用 Spring Initilizr,转到start.spring.io外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

  2. 填写详细信息,例如是否为 Maven 项目,Spring Boot 版本,组和 artifact ID,如前所示,并点击切换到完整版本链接下的生成项目按钮。选择WebHATEOASRest Repositories HAL Browser。确保 Java 版本为 8,并且包类型选择为JAR外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

  3. 选择后,点击生成项目按钮。这将生成一个 Maven 项目,并将项目下载为 ZIP 文件到浏览器的下载目录中。

  4. 解压文件并将其保存到您选择的目录中。

  5. 打开 STS,转到文件菜单,点击导入外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

  6. 转到Maven | 现有 Maven 项目,然后点击下一步

  7. 点击根目录旁边的浏览,选择解压的文件夹。点击完成。这将把生成的 Maven 项目加载到 STS 的项目资源管理器中。

  8. 编辑Application.java文件,添加一个新的 REST 端点,如下所示:

@RequestMapping("/greeting")
@ResponseBody
public HttpEntity<Greet> greeting(@RequestParam(value = "name", required = false, defaultValue = "HATEOAS") String name) {
       Greet greet = new Greet("Hello " + name);
       greet.add(linkTo(methodOn(GreetingController.class).greeting(name)).withSelfRel());

       return new ResponseEntity<Greet>(greet, HttpStatus.OK);
}
  1. 请注意,这与上一个示例中的GreetingController类相同。但是,这次添加了一个名为greeting的方法。在这个新方法中,定义了一个额外的可选请求参数,并将其默认为HATEOAS。以下代码将链接添加到生成的 JSON 代码中。在这种情况下,它将链接添加到相同的 API 中:
greet.add(linkTo(methodOn(GreetingController.class).greeting(name)).withSelfRel());

为了做到这一点,我们需要将Greet类从ResourceSupport扩展,如下所示。其余的代码保持不变:

class Greet extends ResourceSupport{
  1. add方法是ResourceSupport中的一个方法。linkTomethodOn方法是ControllerLinkBuilder的静态方法,用于在控制器类上创建链接的实用程序类。methodOn方法将执行一个虚拟方法调用,而linkTo将创建一个指向控制器类的链接。在这种情况下,我们将使用withSelfRel将其指向自身。

  2. 这将基本上生成一个默认的链接/greeting?name=HATEOAS。客户端可以读取链接并发起另一个调用。

  3. 将其作为 Spring Boot 应用程序运行。一旦服务器启动完成,将浏览器指向http://localhost:8080

  4. 这将打开 HAL 浏览器窗口。在Explorer字段中,输入/greeting?name=World!并单击Go按钮。如果一切正常,HAL 浏览器将显示响应详细信息,如下面的屏幕截图所示:外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

如屏幕截图所示,响应主体部分显示了一个带有href指向同一服务的链接。这是因为我们将引用指向自身。还要查看链接部分。self旁边的小绿色框是可导航链接。

在这个简单的例子中并没有太多意义,但在有许多相关实体的大型应用程序中可能会很方便。使用提供的链接,客户端可以轻松地在这些实体之间来回导航。

接下来是什么?

到目前为止,已经审查了许多基本的 Spring Boot 示例。本章的其余部分将从微服务开发的角度考虑一些重要的 Spring Boot 功能。在接下来的几节中,我们将看看如何处理动态可配置属性,更改默认的嵌入式 Web 服务器,为微服务添加安全性,并在处理微服务时实现跨源行为。

注意

此示例的完整源代码可作为本书的代码文件中的chapter2.boot-advanced项目获得。

Spring Boot 配置

在本节中,重点将放在 Spring Boot 的配置方面。已经开发的chapter2.bootrest项目将在本节中进行修改,以展示配置功能。复制并粘贴chapter2.bootrest并将项目重命名为chapter2.boot-advanced

了解 Spring Boot 自动配置

Spring Boot 使用约定优于配置,通过扫描类路径中可用的依赖库。对于 POM 文件中的每个spring-boot-starter-*依赖项,Spring Boot 执行默认的AutoConfiguration类。AutoConfiguration类使用*AutoConfiguration词法模式,其中*表示库。例如,JPA 存储库的自动配置是通过JpaRepositoriesAutoConfiguration完成的。

使用--debug运行应用程序以查看自动配置报告。以下命令显示了chapter2.boot-advanced项目的自动配置报告:

$java -jar target/bootadvanced-0.0.1-SNAPSHOT.jar --debug

以下是一些自动配置类的示例:

  • ServerPropertiesAutoConfiguration

  • RepositoryRestMvcAutoConfiguration

  • JpaRepositoriesAutoConfiguration

  • JmsAutoConfiguration

如果应用程序有特殊要求,并且您想完全控制配置,可以排除某些库的自动配置。以下是一个排除DataSourceAutoConfiguration的示例:

@EnableAutoConfiguration(exclude={DataSourceAutoConfiguration.class})

覆盖默认配置值

还可以使用application.properties文件覆盖默认配置值。STS 提供了一个易于自动完成的上下文帮助application.properties,如下面的屏幕截图所示:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

在前面的屏幕截图中,server.port被编辑为设置为9090。再次运行此应用程序将在端口9090上启动服务器。

更改配置文件的位置

为了与十二要素应用程序保持一致,配置参数需要从代码中外部化。Spring Boot 将所有配置外部化到application.properties中。然而,它仍然是应用程序构建的一部分。此外,可以通过设置以下属性从包外部读取属性:

spring.config.name= # config file name  
spring.config.location= # location of config file

在这里,spring.config.location可以是本地文件位置。

以下命令使用外部提供的配置文件启动 Spring Boot 应用程序:

$java -jar target/bootadvanced-0.0.1-SNAPSHOT.jar --spring.config.name=bootrest.properties

读取自定义属性

在启动时,SpringApplication加载所有属性并将它们添加到 Spring Environment类中。在application.properties文件中添加自定义属性。在这种情况下,自定义属性的名称为bootrest.customproperty。将 Spring Environment类自动装配到GreetingController类中。编辑GreetingController类以从Environment中读取自定义属性,并添加日志语句以将自定义属性打印到控制台。

执行以下步骤来完成此操作:

  1. application.properties文件中添加以下属性:
bootrest.customproperty=hello
  1. 然后,编辑GreetingController类如下:
@Autowired
Environment env;

Greet greet(){
    logger.info("bootrest.customproperty "+ env.getProperty("bootrest.customproperty"));
    return new Greet("Hello World!");
}
  1. 重新运行应用程序。日志语句将在控制台中打印自定义变量,如下所示:
org.rvslab.chapter2.GreetingController   : bootrest.customproperty hello

使用.yaml 文件进行配置

作为application.properties的替代,可以使用.yaml文件。与平面属性文件相比,YAML 提供了类似 JSON 的结构化配置。

要查看此操作,请简单地将application.properties替换为application.yaml并添加以下属性:

server
  port: 9080

重新运行应用程序以查看端口在控制台中打印。

使用多个配置文件。

此外,可以有不同的配置文件,如开发、测试、暂存、生产等。这些是逻辑名称。使用这些,可以为不同的环境配置相同属性的不同值。当在不同环境中运行 Spring Boot 应用程序时,这非常方便。在这种情况下,从一个环境切换到另一个环境时不需要重新构建。

更新.yaml文件如下。Spring Boot 根据点分隔符对配置文件进行分组:

spring:
    profiles: development
server:
      port: 9090
---

spring:
    profiles: production
server:
      port: 8080

按照以下方式运行 Spring Boot 应用程序以查看配置文件的使用:

mvn -Dspring.profiles.active=production install
mvn -Dspring.profiles.active=development install

可以使用@ActiveProfiles注解以编程方式指定活动配置文件,这在运行测试用例时特别有用,如下所示:

@ActiveProfiles("test")

读取属性的其他选项

可以以多种方式加载属性,例如以下方式:

  • 命令行参数(-Dhost.port =9090)

  • 操作系统环境变量

  • JNDI (java:comp/env)

更改默认的嵌入式 Web 服务器

嵌入式 HTTP 监听器可以轻松自定义如下。默认情况下,Spring Boot 支持 Tomcat、Jetty 和 Undertow。在以下示例中,Tomcat 被替换为 Undertow:

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
    <exclusions>
        <exclusion>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-tomcat</artifactId>
        </exclusion>
    </exclusions>
</dependency>
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-undertow</artifactId>
</dependency>

实现 Spring Boot 安全

保护微服务非常重要。在本节中,将审查一些保护 Spring Boot 微服务的基本措施,使用chapter2.bootrest来演示安全功能。

使用基本安全保护微服务

向 Spring Boot 添加基本身份验证非常简单。将以下依赖项添加到pom.xml中。这将包括必要的 Spring 安全库文件:

<dependency>
  <groupId>org.springframework.boot</groupId> 
  <artifactId>spring-boot-starter-security</artifactId>
</dependency>

打开Application.java并在Application类中添加@EnableGlobalMethodSecurity。此注解将启用方法级安全性:

@EnableGlobalMethodSecurity
@SpringBootApplication
public class Application {
    public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
    }
}

默认的基本身份验证假定用户为user。默认密码将在启动时打印在控制台上。或者,可以在application.properties中添加用户名和密码,如下所示:

security.user.name=guest
security.user.password=guest123

ApplicationTests中添加一个新的测试用例,测试安全服务的结果,如下所示:

  @Test
  public void testSecureService() {  
    String plainCreds = "guest:guest123";
    HttpHeaders headers = new HttpHeaders();
    headers.add("Authorization", "Basic " + new String(Base64.encode(plainCreds.getBytes())));
    HttpEntity<String> request = new HttpEntity<String>(headers);
    RestTemplate restTemplate = new RestTemplate();

    ResponseEntity<Greet> response = restTemplate.exchange("http://localhost:8080", HttpMethod.GET, request, Greet.class);
    Assert.assertEquals("Hello World!", response.getBody().getMessage());
  }

如代码所示,创建一个新的Authorization请求头,使用 Base64 编码用户名密码字符串。

使用 Maven 重新运行应用程序。请注意,新的测试用例通过了,但旧的测试用例出现了异常。早期的测试用例现在在没有凭据的情况下运行,结果服务器拒绝了请求,并显示以下消息:

org.springframework.web.client.HttpClientErrorException: 401 Unauthorized

使用 OAuth2 保护微服务

在本节中,我们将看一下 OAuth2 的基本 Spring Boot 配置。当客户端应用程序需要访问受保护的资源时,客户端向授权服务器发送请求。授权服务器验证请求并提供访问令牌。这个访问令牌对每个客户端到服务器的请求进行验证。来回发送的请求和响应取决于授权类型。

提示

oauth.net阅读有关 OAuth 和授权类型的更多信息。

在这个示例中将使用资源所有者密码凭据授权方法:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

在这种情况下,如前图所示,资源所有者提供客户端用户名和密码。然后客户端通过提供凭据信息向授权服务器发送令牌请求。授权服务器授权客户端并返回访问令牌。在每个后续请求中,服务器验证客户端令牌。

要在我们的示例中实现 OAuth2,请执行以下步骤:

  1. 首先,按照以下步骤更新pom.xml以添加 OAuth2 依赖:
<dependency>
  <groupId>org.springframework.security.oauth</groupId>
  <artifactId>spring-security-oauth2</artifactId>
  <version>2.0.9.RELEASE</version>
</dependency>
  1. 接下来,在Application.java文件中添加两个新的注释@EnableAuthorizationServer@EnableResourceServer@EnableAuthorizationServer注释创建一个授权服务器,其中包含一个内存存储库,用于存储客户端令牌并为客户端提供用户名、密码、客户端 ID 和密钥。@EnableResourceServer注释用于访问令牌。这将启用一个通过传入的 OAuth2 令牌进行身份验证的 Spring 安全过滤器。

在我们的示例中,授权服务器和资源服务器是相同的。然而,在实践中,这两者将分开运行。看一下以下代码:

@EnableResourceServer
@EnableAuthorizationServer
@SpringBootApplication
public class Application {
  1. 将以下属性添加到application.properties文件中:
security.user.name=guest
security.user.password=guest123
security.oauth2.client.clientId: trustedclient
security.oauth2.client.clientSecret: trustedclient123
security.oauth2.client.authorized-grant-types: authorization_code,refresh_token,password
security.oauth2.client.scope: openid
  1. 然后,添加另一个测试用例来测试 OAuth2,如下所示:
  @Test
  public void testOAuthService() {
        ResourceOwnerPasswordResourceDetails resource = new ResourceOwnerPasswordResourceDetails();
        resource.setUsername("guest");
        resource.setPassword("guest123");
          resource.setAccessTokenUri("http://localhost:8080/oauth/token");
        resource.setClientId("trustedclient");
        resource.setClientSecret("trustedclient123");
        resource.setGrantType("password");

        DefaultOAuth2ClientContext clientContext = new DefaultOAuth2ClientContext();
        OAuth2RestTemplate restTemplate = new OAuth2RestTemplate(resource, clientContext);

        Greet greet = restTemplate.getForObject("http://localhost:8080", Greet.class);

        Assert.assertEquals("Hello World!", greet.getMessage());
  }

如前面的代码所示,通过传递封装在资源详细信息对象中的资源详细信息来创建一个特殊的 REST 模板OAuth2RestTemplate。这个 REST 模板在 OAuth2 过程中处理访问令牌。访问令牌 URI 是令牌访问的端点。

  1. 使用mvn install重新运行应用程序。前两个测试用例将失败,而新的测试用例将成功。这是因为服务器只接受启用了 OAuth2 的请求。

这些是 Spring Boot 开箱即用提供的快速配置,但不足以达到生产级别。我们可能需要定制ResourceServerConfigurerAuthorizationServerConfigurer使其达到生产就绪。尽管如此,方法仍然是一样的。

为微服务启用跨源访问

当来自一个来源的客户端端网页应用程序从另一个来源请求数据时,通常会受到限制。启用跨源访问通常被称为CORS跨源资源共享)。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

这个示例显示了如何启用跨源请求。对于微服务来说,由于每个服务都有自己的来源,很容易出现客户端端网页应用程序从多个来源消费数据的问题。例如,浏览器客户端访问来自 Customer 微服务的 Customer 和来自 Order 微服务的 Order History 的情况在微服务世界中非常常见。

Spring Boot 提供了一种简单的声明性方法来启用跨源请求。以下示例显示了如何启用微服务以启用跨源请求:

@RestController
class GreetingController{
  @CrossOrigin
  @RequestMapping("/")
  Greet greet(){
    return new Greet("Hello World!");
  }
}

默认情况下,所有的来源和标头都被接受。我们可以通过给予特定来源访问的方式进一步定制跨源注释。@CrossOrigin注释使方法或类能够接受跨源请求:

@CrossOrigin("http://mytrustedorigin.com")

可以使用WebMvcConfigurer bean 并定制addCorsMappings(CorsRegistry registry)方法来启用全局 CORS。

实现 Spring Boot 消息传递

在理想情况下,所有微服务之间的交互都应该使用发布-订阅语义进行异步处理。Spring Boot 提供了一种无忧的机制来配置消息传递解决方案:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

在这个例子中,我们将创建一个带有发送者和接收者的 Spring Boot 应用程序,它们都通过一个外部队列连接。执行以下步骤:

注意

这个例子的完整源代码可以在本书的代码文件中的chapter2.bootmessaging项目中找到。

  1. 使用 STS 创建一个新项目来演示这个功能。在这个例子中,不要选择Web,而是在I/O下选择AMQP外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

  2. 这个例子也需要 Rabbit MQ。从www.rabbitmq.com/download.html下载并安装最新版本的 Rabbit MQ。

本书中使用的是 Rabbit MQ 3.5.6。

  1. 按照网站上记录的安装步骤进行操作。准备就绪后,通过以下命令启动 RabbitMQ 服务器:
$./rabbitmq-server

  1. application.properties文件进行配置更改,以反映 RabbitMQ 的配置。以下配置使用 RabbitMQ 的默认端口、用户名和密码:
spring.rabbitmq.host=localhost
spring.rabbitmq.port=5672
spring.rabbitmq.username=guest
spring.rabbitmq.password=guest
  1. src/main/java目录下的Application.java文件中添加一个消息发送组件和一个org.springframework.amqp.core.Queue类型的名为TestQ的队列。RabbitMessagingTemplate是发送消息的一种便捷方式,它将抽象出所有的消息语义。Spring Boot 提供了所有的样板配置来发送消息:
@Component 
class Sender {
  @Autowired
  RabbitMessagingTemplate template;
  @Bean
  Queue queue() {
    return new Queue("TestQ", false);
  }
  public void send(String message){
    template.convertAndSend("TestQ", message);
  }
}
  1. 要接收消息,只需要使用@RabbitListener注解。Spring Boot 会自动配置所有必需的样板配置:
@Component
class Receiver {
    @RabbitListener(queues = "TestQ")
    public void processMessage(String content) {
       System.out.println(content);
    }
}
  1. 这个练习的最后一部分是将发送者连接到我们的主应用程序,并实现CommandLineRunnerrun方法来启动消息发送。当应用程序初始化时,它会调用CommandLineRunnerrun方法,如下所示:
@SpringBootApplication
public class Application implements CommandLineRunner{

  @Autowired
  Sender sender;

    public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
    }

    @Override
    public void run(String... args) throws Exception {
      sender.send("Hello Messaging..!!!");
    }
}
  1. 将应用程序作为 Spring Boot 应用程序运行并验证输出。以下消息将打印在控制台上:
Hello Messaging..!!!

开发全面的微服务示例

到目前为止,我们考虑的例子不过是一个简单的“Hello world”。结合我们所学到的知识,本节演示了一个端到端的 Customer Profile 微服务实现。Customer Profile 微服务将展示不同微服务之间的交互。它还演示了具有业务逻辑和基本数据存储的微服务。

在这个例子中,将开发两个微服务,Customer Profile 和 Customer Notification 服务:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

如图所示,Customer Profile 微服务公开了用于创建、读取、更新和删除(CRUD)客户以及用于注册客户的注册服务的方法。注册过程应用了某些业务逻辑,保存了客户资料,并向 Customer Notification 微服务发送了一条消息。Customer Notification 微服务接受了注册服务发送的消息,并使用 SMTP 服务器向客户发送了一封电子邮件。异步消息传递用于将 Customer Profile 与 Customer Notification 服务集成起来。

Customer 微服务类的领域模型图如下所示:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

CustomerController在图中是 REST 端点,调用一个组件类CustomerComponent。组件类/bean 处理所有业务逻辑。CustomerRepository是一个 Spring data JPA repository,用于处理Customer实体的持久化。

注意

此示例的完整源代码可作为本书代码文件中的chapter2.bootcustomerchapter2.bootcustomernotification项目获得。

  1. 创建一个新的 Spring Boot 项目,并将其命名为chapter2.bootcustomer,与之前的方式相同。在启动模块选择屏幕中选择如下屏幕截图中的选项:外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

这将创建一个带有 JPA、REST 存储库和 H2 作为数据库的 Web 项目。H2 是一个微型的内存嵌入式数据库,可以轻松演示数据库功能。在现实世界中,建议使用适当的企业级数据库。此示例使用 JPA 定义持久性实体和 REST 存储库来公开基于 REST 的存储库服务。

项目结构将类似于以下屏幕截图:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

  1. 通过添加名为Customer的实体类来开始构建应用程序。为简单起见,Customer实体类只添加了三个字段:自动生成的id字段,nameemail。看一下以下代码:
@Entity
class Customer {
  @Id
  @GeneratedValue(strategy = GenerationType.AUTO)
  private Long id;
  private String name;
  private String email;
  1. 添加一个存储库类来处理客户的持久化处理。CustomerRepository扩展了标准的 JPA 存储库。这意味着所有 CRUD 方法和默认查找方法都由 Spring Data JPA 存储库自动实现,如下所示:
@RepositoryRestResource
interface CustomerRespository extends JpaRepository <Customer,Long>{
  Optional<Customer> findByName(@Param("name") String name);
}

在这个示例中,我们向存储库类添加了一个新的方法findByName,它基本上根据客户名称搜索客户,并在有匹配名称时返回Customer对象。

  1. @RepositoryRestResource注解通过 RESTful 服务启用存储库访问。这也将默认启用 HATEOAS 和 HAL。由于 CRUD 方法不需要额外的业务逻辑,我们将其保留为没有控制器或组件类的状态。使用 HATEOAS 将帮助我们轻松地浏览客户存储库方法。

请注意,没有在任何地方添加配置来指向任何数据库。由于 H2 库在类路径中,所有配置都是由 Spring Boot 根据 H2 自动配置默认完成的。

  1. 通过添加CommandLineRunner来初始化存储库并插入一些客户记录,更新Application.java文件,如下所示:
@SpringBootApplication
public class Application {
    public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
    }

    @Bean
  CommandLineRunner init(CustomerRespository repo) {
  return (evt) ->  {
    repo.save(new Customer("Adam","adam@boot.com"));
    repo.save(new Customer("John","john@boot.com"));
  repo.save(new Customer("Smith","smith@boot.com"));
    repo.save(new Customer("Edgar","edgar@boot.com"));
    repo.save(new Customer("Martin","martin@boot.com"));
    repo.save(new Customer("Tom","tom@boot.com"));
    repo.save(new Customer("Sean","sean@boot.com"));
  };
  }
}
  1. CommandLineRunner被定义为一个 bean,表示当它包含在SpringApplication中时应该运行。这将在启动时向数据库插入六个样本客户记录。

  2. 此时,将应用程序作为 Spring Boot 应用程序运行。打开 HAL 浏览器,并将浏览器指向http://localhost:8080

  3. 资源管理器部分,指向http://localhost:8080/customers,然后点击Go。这将在 HAL 浏览器的响应主体部分列出所有客户。

  4. 资源管理器部分,输入http://localhost:8080/customers?size=2&page=1&sort=name,然后点击Go。这将自动在存储库上执行分页和排序,并返回结果。

由于页面大小设置为2,并且请求了第一页,它将以排序顺序返回两条记录。

  1. 查看链接部分。如下屏幕截图所示,它将方便地导航firstnextprevlast。这些是通过存储库浏览器自动生成的 HATEOAS 链接完成的:外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

  2. 还可以通过选择适当的链接,如http://localhost:8080/customers/2,来探索客户的详细信息。

  3. 作为下一步,添加一个控制器类CustomerController来处理服务端点。在这个类中只有一个端点/register,用于注册客户。如果成功,它将返回Customer对象作为响应,如下所示:

@RestController
class CustomerController{

  @Autowired
  CustomerRegistrar customerRegistrar;

  @RequestMapping( path="/register", method = RequestMethod.POST)
  Customer register(@RequestBody Customer customer){
    return customerRegistrar.register(customer);
  }
}
  1. 添加了一个CustomerRegistrar组件来处理业务逻辑。在这种情况下,组件中只添加了最少的业务逻辑。在这个组件类中,注册客户时,我们只会检查数据库中是否已经存在客户名称。如果不存在,我们将插入一个新记录,否则,我们将发送一个错误消息,如下所示:
@Component 
class CustomerRegistrar {

  CustomerRespository customerRespository;

  @Autowired
  CustomerRegistrar(CustomerRespository customerRespository){
    this.customerRespository = customerRespository;
  }

  Customer register(Customer customer){
    Optional<Customer> existingCustomer = customerRespository.findByName(customer.getName());
    if (existingCustomer.isPresent()){
      throw new RuntimeException("is already exists");
    } else {
      customerRespository.save(customer); 
    }
    return customer;
  }
}
  1. 重新启动 Boot 应用程序,并通过 URL http://localhost:8080 使用 HAL 浏览器进行测试。

  2. Explorer字段指向http://localhost:8080/customers。在Links部分查看结果:外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

  3. 点击self旁边的NON-GET选项。这将打开一个表单来创建一个新的客户:外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

  4. 填写表格,并按照图中所示更改操作。点击发出请求按钮。这将调用注册服务并注册客户。尝试给出重复的名称以测试负面情况。

  5. 让我们通过将客户通知服务集成到通知客户的示例的最后部分来完成示例。当注册成功时,通过异步调用客户通知微服务向客户发送电子邮件。

  6. 首先更新CustomerRegistrar以调用第二个服务。这是通过消息传递完成的。在这种情况下,我们注入了一个Sender组件,通过将客户的电子邮件地址传递给发送者,向客户发送通知,如下所示:

@Component 
@Lazy
class CustomerRegistrar {

  CustomerRespository customerRespository;
  Sender sender;

  @Autowired
  CustomerRegistrar(CustomerRespository customerRespository, Sender sender){
    this.customerRespository = customerRespository;
    this.sender = sender;
  }

  Customer register(Customer customer){
    Optional<Customer> existingCustomer = customerRespository.findByName(customer.getName());
    if (existingCustomer.isPresent()){
      throw new RuntimeException("is already exists");
    } else {
      customerRespository.save(customer); 
      sender.send(customer.getEmail());
    } 
    return customer;
  }
}
  1. 发送者组件将基于 RabbitMQ 和 AMQP。在本例中,RabbitMessagingTemplate被用作上一个消息示例中所探讨的方式;请看以下内容:
@Component 
@Lazy
class Sender {

  @Autowired
  RabbitMessagingTemplate template;

  @Bean
  Queue queue() {
    return new Queue("CustomerQ", false);
  }

  public void send(String message){
    template.convertAndSend("CustomerQ", message);
  }
}

@Lazy注解是一个有用的注解,它有助于增加启动时间。这些 bean 只有在需要时才会被初始化。

  1. 我们还将更新application.property文件,以包括与 Rabbit MQ 相关的属性,如下所示:
spring.rabbitmq.host=localhost
spring.rabbitmq.port=5672
spring.rabbitmq.username=guest
spring.rabbitmq.password=guest
  1. 我们准备发送消息。为了消费消息并发送电子邮件,我们将创建一个通知服务。为此,让我们创建另一个 Spring Boot 服务,chapter2.bootcustomernotification。在创建 Spring Boot 服务时,请确保选择了AMQPMail启动器库。AMQPMail都在I/O下。

  2. chapter2.bootcustomernotification项目的包结构如下所示:外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

  3. 添加一个Receiver类。Receiver类等待客户端的消息。这将接收客户资料服务发送的消息。在收到消息时,它会发送一封电子邮件,如下所示:

@Component
class Receiver {  
  @Autowired
  Mailer mailer;

  @Bean
  Queue queue() {
    return new Queue("CustomerQ", false);
  }

  @RabbitListener(queues = "CustomerQ")
    public void processMessage(String email) {
       System.out.println(email);
       mailer.sendMail(email);
    }
}
  1. 添加另一个组件来向客户发送电子邮件。我们将使用JavaMailSender通过以下代码发送电子邮件:
@Component 
class Mailer {
  @Autowired
  private  JavaMailSender  javaMailService;
    public void sendMail(String email){
      SimpleMailMessage mailMessage=new SimpleMailMessage();
      mailMessage.setTo(email);
      mailMessage.setSubject("Registration");
      mailMessage.setText("Successfully Registered");
      javaMailService.send(mailMessage);
    }
}

在幕后,Spring Boot 会自动配置JavaMailSender所需的所有参数。

  1. 要测试 SMTP,需要一个 SMTP 的测试设置来确保邮件发送出去。在本例中,将使用 FakeSMTP。您可以从nilhcem.github.io/FakeSMTP下载 FakeSMTP。

  2. 下载fakeSMTP-2.0.jar后,通过执行以下命令运行 SMTP 服务器:

$ java -jar fakeSMTP-2.0.jar

这将打开一个 GUI 来监视电子邮件消息。点击监听端口文本框旁边的启动服务器按钮。

  1. 使用以下配置参数更新application.properties以连接到 RabbitMQ 以及邮件服务器:
spring.rabbitmq.host=localhost
spring.rabbitmq.port=5672
spring.rabbitmq.username=guest
spring.rabbitmq.password=guest

spring.mail.host=localhost
spring.mail.port=2525
  1. 我们准备测试我们的微服务端到端。启动两个 Spring Boot 应用程序。打开浏览器,并通过 HAL 浏览器重复客户端创建步骤。在这种情况下,提交请求后,我们将能够在 SMTP GUI 中看到电子邮件。

在内部,客户资料服务异步调用客户通知服务,后者又将电子邮件消息发送到 SMTP 服务器:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

Spring Boot actuator

前面的部分探讨了开发微服务所需的大部分 Spring Boot 功能。在本节中,将探讨 Spring Boot 的一些适用于生产的操作方面。

Spring Boot actuator 提供了一个出色的开箱即用的机制,用于监控和管理生产中的 Spring Boot 应用程序:

注意

此示例的完整源代码可在本书的代码文件中的chapter2.bootactuator项目中找到。

  1. 创建另一个Spring Starter Project,命名为chapter2.bootactuator。这次,在Ops下选择WebActuators。与chapter2.bootrest项目类似,添加一个带有greet方法的GreeterController端点。

  2. 启动应用程序作为 Spring Boot 应用程序。

  3. 将浏览器指向localhost:8080/actuator。这将打开 HAL 浏览器。然后,查看Links部分。

Links部分下有许多链接可用。这些链接是由 Spring Boot actuator 自动公开的:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

一些重要的链接列举如下:

  • dump:执行线程转储并显示结果

  • mappings:列出所有 HTTP 请求映射

  • info:显示有关应用程序的信息

  • health:显示应用程序的健康状况

  • autoconfig:显示自动配置报告

  • metrics:显示从应用程序收集的不同指标

使用 JConsole 进行监控

或者,我们可以使用 JMX 控制台查看 Spring Boot 信息。从 JConsole 连接到远程 Spring Boot 实例。Boot 信息将显示如下:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

使用 SSH 进行监控

Spring Boot 提供了使用 SSH 远程访问 Boot 应用程序的功能。以下命令从终端窗口连接到 Spring Boot 应用程序:

$ ssh -p 2000 user@localhost

可以通过在application.properties文件中添加shell.auth.simple.user.password属性来自定义密码。更新后的application.properties文件将类似于以下内容:

shell.auth.simple.user.password=admin

通过前面的命令连接时,可以访问类似的 actuator 信息。以下是通过 CLI 访问的指标信息示例:

  • help:列出所有可用选项

  • dashboard:这是一个显示大量系统级信息的有趣功能

配置应用程序信息

可以在application.properties中设置以下属性来自定义与应用程序相关的信息。添加后,重新启动服务器并访问 actuator 的/info端点以查看更新后的信息,如下所示:

info.app.name=Boot actuator
info.app.description= My Greetings Service
info.app.version=1.0.0

添加自定义健康模块

向 Spring Boot 应用程序添加新的自定义模块并不复杂。为了演示这一特性,假设如果一个服务在一分钟内获得超过两个事务,那么服务器状态将被设置为服务外。

为了自定义这一点,我们必须实现HealthIndicator接口并重写health方法。以下是一个快速而简单的实现来完成这项工作:

class TPSCounter {
  LongAdder count;
  int threshold = 2;
  Calendar expiry = null; 

  TPSCounter(){
    this.count = new LongAdder();
    this.expiry = Calendar.getInstance();
    this.expiry.add(Calendar.MINUTE, 1);
  }

  boolean isExpired(){
    return Calendar.getInstance().after(expiry);
  }

  boolean isWeak(){
    return (count.intValue() > threshold);
  }

  void increment(){
     count.increment();
  }
}

上述类是一个简单的 POJO 类,用于在窗口中维护事务计数。isWeak方法检查特定窗口中的事务是否达到了其阈值。isExpired方法检查当前窗口是否已过期。increment方法简单地增加计数器值。

下一步,实现我们的自定义健康指示器类TPSHealth。通过扩展HealthIndicator来完成:

@Component
class TPSHealth implements HealthIndicator {
  TPSCounter counter;

@Override
    public Health health() {
        boolean health = counter.isWeak(); // perform some specific health check
        if (health) {
            return Health.outOfService().withDetail("Too many requests", "OutofService").build();
        }
        return Health.up().build();
    }

    void updateTx(){
    if(counter == null || counter.isExpired()){
      counter = new TPSCounter();

    }
    counter.increment();
    }
}

health方法检查计数器是否弱。弱计数器意味着服务处理的事务比其可以处理的要多。如果它是弱的,它将把实例标记为服务外。

最后,我们将把TPSHealth自动装配到GreetingController类中,然后在greet方法中调用health.updateTx(),如下所示:

  Greet greet(){
    logger.info("Serving Request....!!!");
    health.updateTx(); 
    return new Greet("Hello World!");
  }

转到 HAL 浏览器中的/health端点,并查看服务器的状态。

现在,打开另一个浏览器,指向http://localhost:8080,并多次调用服务。返回/health端点并刷新以查看状态。它应该更改为服务外。

在此示例中,除了收集健康状态之外,没有采取其他行动,即使状态为服务外,新的服务调用仍将继续。但是,在现实世界中,程序应读取/health端点并阻止进一步的请求发送到此实例。

构建自定义指标

类似于健康状况,还可以自定义指标。以下示例显示了如何添加计数器服务和计量器服务,仅用于演示目的:

  @Autowired   
  CounterService counterService;

  @Autowired
  GaugeService gaugeService;

在问候方法中添加以下方法:

  this.counterService.increment("greet.txnCount");
  this.gaugeService.submit("greet.customgauge", 1.0);

重新启动服务器,转到/metrics以查看已添加的新计量器和计数器是否已反映在其中。

记录微服务

传统的 API 文档方法是编写服务规范文档或使用静态服务注册表。对于大量的微服务,很难保持 API 文档的同步。

微服务可以用许多方式记录。本节将探讨如何使用流行的 Swagger 框架记录微服务。以下示例将使用 Springfox 库生成 REST API 文档。Springfox 是一组 Java 和 Spring 友好的库。

创建一个新的Spring Starter Project,并在库选择窗口中选择Web。将项目命名为chapter2.swagger

注意

此示例的完整源代码可在本书的代码文件中的chapter2.swagger项目中找到。

由于 Springfox 库不是 Spring 套件的一部分,请编辑pom.xml并添加 Springfox Swagger 库依赖项。将以下依赖项添加到项目中:

<dependency>
    <groupId>io.springfox</groupId>
    <artifactId>springfox-swagger2</artifactId>
    <version>2.3.1</version>
</dependency>  
<dependency>
    <groupId>io.springfox</groupId>
    <artifactId>springfox-swagger-ui</artifactId>
    <version>2.3.1</version>
</dependency>

创建一个类似于之前创建的服务的 REST 服务,但还要添加@EnableSwagger2注释,如下所示:

@SpringBootApplication
@EnableSwagger2
public class Application {

这就是基本的 Swagger 文档所需的全部内容。启动应用程序,并将浏览器指向http://localhost:8080/swagger-ui.html。这将打开 Swagger API 文档页面:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

如图所示,Swagger 列出了问候控制器上可能的操作。单击GET操作。这将展开GET行,提供尝试操作的选项。

摘要

在本章中,您了解了 Spring Boot 及其构建生产就绪应用程序的关键功能。

我们探讨了上一代 Web 应用程序,以及 Spring Boot 如何使开发人员更容易开发完全合格的微服务。我们还讨论了服务之间的异步基于消息的交互。此外,我们探讨了如何通过实际示例实现微服务所需的一些关键功能,例如安全性、HATEOAS、跨源、配置等。我们还看了 Spring Boot 执行器如何帮助运营团队,以及如何根据需要自定义它。最后,还探讨了记录微服务 API。

在下一章中,我们将更深入地研究在实施微服务时可能出现的一些实际问题。我们还将讨论一个能力模型,该模型在处理大型微服务实施时对组织有所帮助。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值