vlambda博客
学习文章列表

读书笔记《hands-on-reactive-programming-in-spring-5》最后,释放它!

第 10 章。最后,释​​放它!

在本书中,我们涵盖了有关 Spring 5 中的反应性的所有内容。这包括使用 Reactor 3 进行反应式编程的概念和模式、Spring Boot 2 的新特性、Spring WebFlux、反应性 Spring 数据、Spring Cloud Streams 和测试技术反应式编程。现在我们已经熟悉了这些概念,是时候为生产准备我们的反应式应用程序了。应用程序应公开日志、指标、跟踪、功能切换和其他有助于确保成功操作的信息。应用程序还应该在不违反安全问题的情况下发现运行时依赖项,例如数据库或消息代理。考虑到所有这些,我们可以构建一个可执行的工件,为内部部署或云部署做好准备。

在本章中,我们将介绍以下主题:

  • The challenges of a software operation
  • The need for operational metrics
  • The aim and features of a Spring Boot Actuator
  • How to extend Actuator capabilities
  • Techniques and libraries for monitoring the reactive application
  • Tracing services interaction inside the reactive system
  • Tips and tricks for an application's deployment in the cloud

DevOps 友好型应用的重要性


我们可以从三个distinct 角度看待几乎所有软件。每个角度代表使用该系统的不同目标受众的需求,即:

  • Business users are interested in the business functions that the system provides
  • Developers want the system to be development-friendly
  • Operational teams want the system to DevOps-friendly

 

现在让我们探讨软件系统的操作方面。从 DevOps 团队成员的角度来看,当在生产中支持系统不难时,软件系统是 DevOps 友好的。这意味着系统公开了适当的健康检查和指标,并提供了衡量其性能和平滑更新不同组件的能力。此外,由于如今微服务架构是一种默认的软件开发技术,因此即使部署到云端也必须具备适当的监控能力。如果没有适当的监控基础设施,我们的软件将无法在生产中存活超过几天。

用于应用程序部署的云环境的出现通过提供适当的基础设施,即使是最苛刻的要求也能简化和民主化软件交付和操作流程软件设计。 IaaS、PaaS 和容器管理系统如 Kubernetes (https://kubernetes.io)  或 Apache Mesos (http://mesos.apache.org) 有消除了与操作系统和 network 配置、文件备份、指标收集、自动服务扩展等等相关的许多令人头疼的问题。但是,这些外部服务和技术仍然无法自行确定我们的 business 应用程序是否提供了适当的服务质量。此外,云提供商无法根据我们系统正在执行的任务来建议底层资源是否得到有效使用。这种责任仍然落在软件开发人员和 DevOps 的肩上。

为了有效地操作软件,主要是当它由几十个甚至有时是几千个服务组成时,我们需要一些方法来做到以下几点:

  • Identify services
  • Check the service's health status
  • Monitor operational metrics
  • Look through the logs and dynamically change log levels
  • Trace requests or data flows

让我们一一解决每个问题。服务识别是微服务架构中必须的。这是因为在大多数情况下,某些编排系统(例如 Kubernetes)会在不同节点上生成大量服务实例,随着客户需求的增长和减少,对它们进行洗牌、创建和销毁节点。尽管容器或可运行的 JAR 文件通常具有有意义的名称,但必须能够在运行时识别源代码的服务名称、类型、版本、构建时间和事件提交修订版。这使得在生产中发现不正确或有问题的服务版本成为可能,跟踪引入回归的更改(如果有)并自动跟踪同一服务的不同版本的性能特征。

 

当谈到能够在运行时区分服务时,我们想知道所有服务是否都是健康的。如果不是,我们想知道这是否重要,这取决于服务的角色。编排系统本身经常使用服务健康检查端点来识别和重新启动失败的服务。这里不是必须只有两种状态:健康和不健康。通常,健康状况会提供一整套基本检查——其中一些是关键的,有些则不是。例如,我们可能会根据处理队列大小、错误率、可用磁盘空间和可用内存来计算服务健康级别。在计算整体健康状况时,仅考虑基本指标是值得的。在其他情况下,我们可能会冒险构建一个 几乎不能被认为是健康的服务。一般来说,提供健康状态请求的能力意味着该服务 至少 能够处理请求。这个特性经常被使用由容器的管理系统检查服务可用性并决定服务重启。

即使服务正常运行 并且处于健康状态,我们也经常希望更深入地了解运营细节。一个成功的系统不仅包含健康的组件,而且还以最终用户可预测和可接受的方式运行。系统的关键指标可能包括平均响应时间、错误率,以及根据请求的复杂性处理请求所需的时间。了解我们的系统在负载下的行为方式 不仅可以让我们充分扩展它,还可以让我们计划基础设施的费用。它还可以发现热代码、低效算法和可扩展性的限制因素。运营指标提供了系统当前状态的快照并带来了很多价值,但是当持续收集指标时,信息量会大得多。运营指标提供趋势,并可能提供对一些相关特征的洞察,例如与正常运行时间有关的服务内存使用情况。明智实施的指标报告器不会消耗大量服务器资源,但随着时间的推移保持指标历史需要一些附加< /span> 基础设施,通常是时间序列database 如 Graphite (https://graphiteapp.org)、InfluxDB (https://www.influxdata.com),或 Prometheus (https://prometheus.io)。为了在有意义的仪表板上可视化时间序列并设置警报以响应紧急情况,我们通常需要额外的监控软件 比如 Grafana (https://grafana.com) 或者Zabbix ( https://www.zabbix.com)。云平台经常以额外服务的形式向客户提供此类软件。

 

在监控服务的操作特征和调查事件时,DevOps 团队经常会阅读日志。如今,所有应用程序日志 理想情况下 应该存储或至少分析在一个集中的地方。为此,在 Java 生态系统中,ELK 堆栈 (https://www.elastic .co/elk-stack) (由 Elasticsearch、Logstash 和 Kibana 组成)经常被使用。虽然这样的软件堆栈非常出色,并且可以有可能将数十个服务视为一个系统,但它的效率非常低下通过网络传输并存储所有日志级别的日志。通常,保存 INFO 消息并打开 DEBUG 或 TRACE 级别仅用于调查一些可重复的异常或错误就足够了。要进行动态日志级别管理,我们需要一些无障碍接口。

当日志不足以代表 整个画面时,我们在费力调试之前的最后尝试是跟踪进程的能力在我们的软件中。跟踪可以表示最近服务器请求的详细日志,也可以描述后续请求的完整拓扑,包括排队时间、带有时间的 DB 请求、带有相关 ID 的外部调用等。跟踪对于实时可视化请求处理非常有帮助,在提高软件性能时是必不可少的。分布式跟踪在分布式系统中做同样的事情,使得跟踪所有请求、消息、网络延迟、错误等成为可能。在本章的后面,我们将描述如何使用Spring Cloud Sleuth和Zipkin启用分布式跟踪 (https://zipkin.io)。

笔记

Zipkin 项目负责人 Adrian Cole 就不同视角在应用监控中的重要性做了精彩演讲:https://www.dotconferences.com/2017/04/adrian-cole-observability-3-ways-logging-metrics -跟踪

最重要的是,为了软件系统的成功运行,所有提到的技术都是必需的或至少是需要的。幸运的是,在 Spring 生态系统中,我们有一个 Spring Boot Actuator。

对于普通的基于 Servlet 的应用程序,前面提到的所有操作技术都很好理解和描述。然而,由于 Java 平台上的响应式编程仍然是一个新事物,因此为响应式应用程序实现类似的目标可能需要一些代码修改,甚至完全不同的实现方法。

然而,从操作的角度来看,使用响应式编程实现的服务不应与普通实现良好的同步服务不同。它应该遵循十二因子应用指南 (https://12factor.net),它应该对 DevOps 友好,并且应该易于操作和发展。

 

监控 Reactive Spring 应用程序


一般来说,可以实现以自定义方式为Spring应用程序 的所有监控基础设施,但这显然会这是一个相当浪费的冒险,尤其是在为微服务系统中的每个服务重复这样做的时候。希望 Spring Framework 提供了一个非常好的工具集来帮助构建一个对 DevOps 友好的应用程序。这个工具集称为 Spring Boot Actuator。只需要一个额外的 Spring Boot 依赖项,这就带来了一些重要的功能。同时,它也为所需的监控基础设施提供了一个骨架。

Spring Boot 执行器

Spring Boot Actuator 是 Spring Boot 的一个子项目,它为 Spring Boot 应用程序带来了许多生产就绪的特性。这包括服务信息、健康检查、指标收集、跟踪流量、数据库状态等。 Spring Boot 执行器背后的中心思想是提供应用程序的基本 metrics 并使其可以轻松扩展。

Spring Boot 执行器公开 HTTP 端点和具有大量操作信息的 JMX bean,这允许与许多监控软件。 它的众多插件进一步扩展了这些功能。与 Spring 的大多数模块一样,添加一个依赖项就足以获得一致的应用程序监控工具集。

在项目中添加执行器

添加 Spring Boot 执行器与向项目添加下一个依赖项一样 simple。在 Gradle 构建的文件中考虑以下依赖项:

compile('org.springframework.boot:spring-boot-starter-actuator')

在这里,我们依赖于一个事实,即库的实际版本及其所有依赖项 在 Spring Boot 物料清单BOM)。

Spring Boot 执行器最初是在 Spring Boot 版本 1.x 中引入的,但在 Spring Boot 2.x 中已经改进了很多。目前,执行器在技术上是不可知的,并且支持 Spring Web MVC 和 Spring WebFlux。它还与应用程序的其余部分共享安全模型,因此它可以轻松地用于反应式应用程序并利用反应式编程的所有优势。

 

执行器的默认配置公开了 URL /actuator 下的所有端点,但这可以通过 management.endpoints.web.base 进行更改-path 属性。因此,在 application 启动后,我们可以立即开始探索服务的内部结构。

笔记

由于 Spring Boot 执行器严重依赖应用程序的 Web 基础架构,因此需要将 Spring Web MVC 或 Spring WebFlux 作为应用程序依赖项。

现在让我们回顾一下前面描述的主要问题 并看看如何在反应式应用程序中实现它们。

服务信息端点

默认情况下,Spring Boot 执行器提供 用于系统监控、扩展和随时间演变的最有价值的信息。根据应用程序配置,它公开有关应用程序可执行工件(服务组、工件 ID、工件名称、工件版本、构建时间)和 Git 坐标(分支名称、提交 ID、提交时间)的信息。当然,如果需要,我们可能会在此列表中添加更多信息。

笔记

为了公开在构建应用程序期间收集的信息,我们可以在 Gradle 构建文件的 springBoot 部分添加以下配置: buildInfo() 。当然,Maven 构建也可以使用相同的功能。 以下文章提供了更多详细信息:https://docs.spring.io/spring-boot/docs/current/reference/htmlsingle/#howto-build

执行器通过/actuator/info 端点显示应用程序信息。一般来说,这个端点默认是启用的,但是它的可访问性可以通过management.endpoint.info.enabled property. 

我们可以通过以下方式配置 application.property 文件来添加一些暴露在该REST端点上的自定义信息:

info:
   name: "Reactive Spring App"
   mode: "testing"
   service-version: "2.0"
     features:
        feature-a: "enabled"
        feature-b: "disabled"

或者,我们可以通过注册实现 InfoContributor 接口的 bean 以编程方式提供类似信息。以下代码描述了此功能:

@Component
public class AppModeInfoProvider implements InfoContributor {        // (1)
   private final Random rnd = new Random();

   @Override
   public void contribute(Info.Builder builder) {                    // (2)
      boolean appMode = rnd.nextBoolean();
      builder
         .withDetail("application-mode",                             // (3)
                     appMode ? "experimental" : "stable");
   }
}

在这里,我们必须实现一个 InfoContributor接口(1)只有 contribute (...)(2) 方法, 提供了一个builder,可以用来添加 需要的<一个 id="id326206647" class="indexterm"> 信息使用 withDetail(...)(3)  builder方法:

读书笔记《hands-on-reactive-programming-in-spring-5》最后,释放它!

图 10.1。 Canary 部署模式的一个示例。负载 balancer 根据版本将流量路由到服务实例

在讨论高级使用场景时,可以在进行金丝雀部署时利用执行器服务信息端点 ( https://martinfowler.com/bliki/CanaryRelease.html)。在这里,负载均衡器可以使用关于 服务版本的信息来路由传入的 流量。 

笔记

要了解与 Spring Boot Actuator /info endpoint 相关的所有详细信息,请点击以下链接:https://docs.spring.io/spring-boot/docs/current/reference/htmlsingle /#production-ready-application-info

 

由于 Spring Boot 执行器不提供任何用于信息公开的反应式或异步 API,因此反应式服务与阻塞式服务没有任何不同。

健康信息端点

系统监控的下一个基本点是检查服务健康状态的能力。在最直接的方法中,服务健康可能被解释为服务响应请求的能力。下图描述了这样的场景:

读书笔记《hands-on-reactive-programming-in-spring-5》最后,释放它!

图 10.2。一个简单的健康检查的例子,而不是检查服务是否可用

这里的核心问题是该服务可能可以通过网络访问。但是,某些重要组件,例如硬盘驱动器(如果使用)、底层数据库或相关服务可能无法访问。因此,该服务无法完全发挥作用。从操作的角度来看,健康检查不仅仅意味着可用性状态。首先,健康的服务是一种安装了子组件并且也可用的服务。此外, 健康信息应包括允许运营团队尽快对潜在的故障威胁做出反应的所有细节。响应数据库不可用或可用磁盘空间不足的情况,将使 DevOps 能够采取相应的行动。因此,健康信息可能包括更多详细信息,如下图所示:

读书笔记《hands-on-reactive-programming-in-spring-5》最后,释放它!

图 10.3。包含底层资源详细信息的运行状况检查示例

 

令我们高兴的是,Spring Boot 执行器提供了一种成熟的健康监控方法。服务健康的基本细节可以通过/actuator/health端点访问。此端点默认启用并公开。 此外,Spring Boot 执行器具有广泛的内置健康指标列表,用于最常见的组件,例如 Cassandra、MongoDB、JMS 和其他集成到 Spring 生态系统中的流行服务。

笔记

要获取内置 HealthIndicator的完整列表,请访问以下链接:https://docs.spring.io/spring-boot/docs/current/reference/html /production-ready-endpoints.html#autoconfiguredhealthindicators

除了内置指标,我们还可以提供一个 HealthIndicator的自定义实现。然而,Spring Boot Actuator 2.0 最令人兴奋的部分是与 Spring WebFlux 和 Reactor 3 的正确集成。这种混合提供了一个用于健康指标的新 反应接口称为 ReactiveHealthIndicators。如第 6 章WebFlux 异步非阻塞通信。以下代码使用新 API 实现自定义运行状况指示器:

@Component
class TemperatureSensor {                                          // (1)
  public Mono<Integer> batteryLevel() {                            // (1.1)
    // A network request here
  }
  ...
}

@Component
class BatteryHealthIndicator implements ReactiveHealthIndicator {  // (2)
   private final TemperatureSensor temperatureSensor;              // (2.1)

   @Override
   public Mono<Health> health() {                                  // (3)
      return temperatureSensor
         .batteryLevel()
         .map(level -> {                              
            if (level > 40) {
               return new Health.Builder()
                  .up()                                            // (4)
                  .withDetail("level", level)
                  .build();
            } else {
               return new Health.Builder()
                  .status(new Status("Low Battery"))               // (5)
                  .withDetail("level", level)
                  .build();
            }
         }).onErrorResume(err -> Mono.                             // (6)
            just(new Health.Builder()
               .outOfService()                                     // (6.1)
               .withDetail("error", err.getMessage())
               .build())
         );
   }
}

前面的示例代码显示了如何与可以安装在房屋中但可通过网络使用的外部传感器进行有线通信:

  1.  TemperatureSensor 服务有一个 Mono<Integer> batteryLevel() (1.1) 方法, 向传感器发出请求,如果可用,则返回从 0 到 100% 的当前电池电量。此方法返回一个带有响应的 Mono 并使用 WebClient 进行高效通信。
  2. 要在计算服务健康状态时使用有关传感器电池电量的数据,我们必须定义一个自定义的 BatteryHealthIndicator 类。这实现了 ReactiveHealthIndicator接口并引用了 TemperatureSensor服务(2.1 )
  3. 为了实现接口,健康指标实现 the Mono<Health>具有反应返回类型的 health() 方法。因此,当网络响应从温度传感器到达时,健康状态可以被被动计算。
  4. 根据电池电量,我们可能会返回一个预定义的UP status 以及一些额外的细节。
  5. 完全自定义 Health 状态的示例。在此示例中,我们收到一个 Low Battery 状态。
  6. 此外,使用反应器功能,我们可以对通信错误做出反应并返回 OUTOFSERVICE状态以及有关实际错误的一些详细信息(6.1)

笔记

Spring Boot 执行器有几种模式可以确定何时扩展健康信息以及何时仅显示顶级状态(UPDOWNOUTOFSERVICEUNKNOWN)。对于我们的测试目的,将应用程序属性 management.endpoint.health.show-details 设置为值 always 。以下文章更详细地描述了可用选项:https://docs.spring.io/spring-boot/docs/current/reference/htmlsingle/#production-ready-health。此外,为了避免传感器过载并限制请求的速率,我们可以使用 Reactor 功能将电池电量缓存一段时间,或者使用 管理配置服务 health 状态的缓存。 endpoint.health.cache.time-to-live=10s 属性。

这样的监控方式可以让用户安排相应的动作,比如提出更换电池的罚单或发送通知 给管家 关于低电量状态。

笔记

Spring Boot Actuator 2 提供了一些内置的 ReactiveHealthIndicator,可以在以下链接中找到:https://docs.spring.io/spring-boot/docs/current /reference/html/production-ready-endpoints.html#autoconfiguredreactivehealthindicators

指标端点

成功监控应用程序的下一个重要部分是通过操作指标收集。当然,Spring Boot 执行器也涵盖了这方面,并提供对基本 JVM 特征的监控,例如进程正常运行时间、内存使用情况、CPU 使用情况和 GC 暂停。此外,WebFlux 提供了一些有关处理传入 HTTP 请求的统计信息。但是,为了从业务角度提供对服务中发生的情况的有意义的洞察,我们必须自行扩展运营指标。

从 Spring Boot 2.0 开始,执行器更改了用于度量收集的底层库,现在使用了千分尺库: https ://micrometer.io

如果暴露, /actuator/metrics REST 端点会提供跟踪指标列表,并可以导航到所需的仪表或计时器,还提供额外的上下文 信息标签的形式。一个指标的摘要,比如 jvm.gc.pause,可能如下所示:

{
  "name": "jvm.gc.pause",
  "measurements": [
    {
      "statistic": "COUNT",
      "value": 5
    },
    {
      "statistic": "TOTALTIME",
      "value": 0.347
    }
  ],
  "availableTags": [
    {
      "tag": "cause",
      "values": [
        "Heap Dump Initiated GC",
        "Metadata GC Threshold",
        "Allocation Failure"
      ]
    }
  ]
}

与 Spring Boot 1.x 对应物相比,新指标端点的一个缺点是无法通过单个 REST 请求检索所有跟踪的指标摘要。然而,这可能只是一个小问题。

像往常一样,我们可以使用千分尺指标注册表自由注册新指标。在本章后面,我们将介绍此过程的机制,并将了解哪些操作指标对反应式应用程序有用。

笔记

 /actuator/metrics endpoint 默认情况下不公开。相反,默认配置仅公开 infohealth 端点。因此,应该通过在 application.property 文件中提供以下属性来公开指标端点—management.endpoints.web.exposure .include:信息、健康、指标。这同样适用于本章后面提到的所有端点。在需要公开所有端点的情况下,我们可以遵循下一种技术并提供通配符来代替 - management.endpoints.web.exposure.include: *。有关更多详细信息,请参阅以下链接:https://docs.spring.io/spring-boot/docs/current/reference/htmlsingle/#production-ready-endpoints-enabling-endpoints

记录器管理端点

同样,Spring Boot 执行器提供了两个 valueable 端点用于日志管理。第一个是 /actuator/loggers, 它允许在运行时访问和更改日志级别,而无需重新启动应用程序。此端点非常有用,因为在不重新启动服务的情况下切换信息粒度是成功的应用程序操作的重要部分。鉴于与反应式应用程序相关的复杂调试体验,切换日志级别和动态分析结果的能力至关重要。当然,用 curl 命令从控制台改变日志级别不是很方便,但是这个特性和Spring Boot admin配合得很好,它提供了一个漂亮的UI用于此类目的。

此功能可以启用或禁用 动态 生成 日志 由log()操作符" linkend="ch04">第 4 章Project Reactor - 响应式应用程序的基础。以下代码描述了将事件推送到 SSE 流的反应式服务

@GetMapping(path = "/temperature-stream", 
            produces = MediaType.TEXTEVENTSTREAMVALUE)
public Flux<TemperatureDto> temperatureEvents() {
    return temperatureSensor.temperatureStream()                  
                            .log("sse.temperature", Level.FINE)      // (1)
                            .map(this::toDto);
}

在这里, log() 操作符注册了一个带有 Level 的记录器 sse.temperature。很好(1)。因此,我们可以使用 /actuator/loggers 端点动态启用或禁用此记录器的输出。

 

另一方面,无需从远程服务器复制文件即可访问应用程序日志的能力可能是有利的。为了简化这一点,Spring Boot 执行器提供了一个 /actuator/logfile 端点,它通过 Web 公开带有日志的文件。 Spring Boot 管理员还有一个简洁的网页,streams 应用程序以非常方便的方式记录到 UI。

笔记

请注意,在 /actuator/logfile 端点不可用的情况下,可能是应用程序中未配置日志文件。因此,我们必须显式配置它,如以下示例 - logging.file: my.log

其他有价值的端点

除了前面提到的 endpoints,Spring Boot 执行器还提供了大量方便的 Web 端点列表。正确的文档可从以下链接获得:https://docs.spring.io/spring-boot/docs/current/actuator-api/html

尽管如此,这里还是列出了最重要的一些简要说明(所有端点都位于基本 Actuator URL /actuator 下):

  • /configprops: This provides access to all possible configuration properties in the application.
  • /env: This provides access to environment variables.
  • /mappings: This provides access to all exposed web endpoints in the application.
  • /httptrace: This provides access to recorded HTTP interaction for server-side and client-side.
  • /auditevents: This gives access to all audited events in the application.
  • /beans: This provides the list of available beans in the Spring context.
  • /caches: This provides access to the cache management of the application.
  • /sessions: This provides a list of active HTTP sessions.
  • /threaddump: This makes it possible to get a thread dump for the application's JVM.
  • /heapdump: This makes it possible to generate and download a heap dump. Note that in that case, the resulting file will be in HPROF format: https://docs.oracle.com/javase/8/docs/technotes/samples/hprof.html.

编写自定义执行器端点

Spring Boot 执行器允许注册 自定义端点,这些端点不仅可以显示数据,还可以更新应用程序的行为。为此, 一个 Spring bean 应该用一个 @Endpoint 注解来装饰。要将读/写操作注册为执行器 端点,库提供了 @ReadOperation、 @WriteOperation和  @DeleteOperation 注解依次映射到 HTTP GETPOST 和 DELETE 分别。但请注意,生成的 REST 端点使用并生成以下内容类型:application/vnd.spring-boot.actuator.v2+json, application/json

为了演示如何在反应式应用程序中使用此功能,让我们创建一个自定义端点,该端点使用 网络时间协议<报告当前服务器时间和时差/span> (NTP) 服务器。有时,此类功能有助于解决由于服务器或 network 配置错误而导致的与错误时间相关的问题。这对于分布式系统特别方便。为此,我们需要注册一个装饰有 @Endpoint 注解和配置 ID 属性的 bean(在我们的例子中是 server-时间)。我们还将通过一个带有 @ReadOperation注解的方法返回感兴趣的数据,如下:

@Component
@Endpoint(id = "server-time")                                        // (1)
public class ServerTimeEndpoint {
   private Mono<Long> getNtpTimeOffset() {                           // (2)
      // Actual network call to get the current time offset
   }

   @ReadOperation                                                    // (3)
   public Mono<Map<String, Object>> reportServerTime() {             // (4)
      return getNtpTimeOffset()                                      // (5)
         .map(timeOffset -> {                                        // (6)
            Map<String, Object> rsp = new LinkedHashMap<>();         //
            rsp.put("serverTime", Instant.now().toString());         //   
            rsp.put("ntpOffsetMillis", timeOffset);                  //
            return rsp;
         });
   }

 

前面的代码 示例 展示了如何使用 Project Reactor 中的反应类型创建自定义执行器 @Endpoint。上述代码示例可以解释为 如下:

  1. 这是一个带有 @Endpoint 注释的端点类。在这里, @Endpoint 注解将 ServerTimeEndpoint类公开为 REST 端点。此外, id @Endpoint的参数 注解标识了端点的URL。 在这样的组合中,URL 是 /actuator/server-time
  2. 这是一个异步报告与 NTP 服务器上当前时间的偏移量的方法定义。  getNtpTimeOffset() 方法返回 Mono ,因此它可以以反应方式组合。
  3. 这是一个 @ReadOperation 注释,它将 reportServerTime() 方法标记为应该通过 Actuator 机制公开的方法REST GET 操作。
  4. 这是 reportServerTime() 方法,它返回一个反应类型 Mono ,所以它可以在 Spring WebFlux 应用程序中有效使用。
  5. 这里我们声明了对 NTP 服务器的异步调用,其结果定义了反应流的开始。
  6. 这是 transformation 操作的结果。当响应到达时,它会使用 map() 操作符进行转换,我们在映射中填写结果(当前服务器时间和 NTP 偏移量),该结果应该返回到用户。请注意, 第 4 章,  中介绍了所有花哨的错误处理技术Project Reactor - 反应式应用的基础 也可以在这里使用。

笔记

NTP (http://www.ntp.org) 是一个非常通过网络同步计算机时钟的流行协议。 NTP 服务器是公开可用的,但请注意不要以过多的请求滥用系统,因为 NTP 服务器可能会阻止我们的应用程序 IP 地址。

使用自定义端点,我们可以快速实现动态功能切换、自定义请求跟踪、操作模式(主/从)、旧会话失效或与成功应用程序操作相关的许多其他功能。现在也可以以反应方式执行此操作。

 

 

保护执行器端点

在前面的部分中,我们介绍了 Spring Boot 执行器的基本要素并实现了我们的自定义反应式执行器端点。请注意,在公开有用信息时,Spring Boot 执行器也可能会泄露敏感信息。访问环境变量、应用程序的结构、配置属性、进行堆和线程转储的能力以及其他因素可能会简化试图破解我们应用程序的坏人的生活。在这些情况下,Spring Boot 执行器也可能会暴露私人用户的数据。因此,我们必须关心所有执行器端点以及通常的 REST 端点的安全访问。

由于 Spring Boot 执行器与应用程序的其余部分共享安全模型,因此很容易在定义主要安全配置的地方配置访问权限。例如,以下代码 仅 允许拥有 ACTUATOR/actuator/ 端点> 权威:

@Beanpublic SecurityWebFilterChain securityWebFilterChain(
   ServerHttpSecurity http
){return http.authorizeExchange().pathMatchers("/actuator/").hasRole("ACTUATOR").anyExchange().authenticated().and().build();}

当然,我们可能会以不同的方式配置访问策略。通常,对 /actuator/info 和 /actuator/health endpoints 提供未经身份验证的访问是合适的,这通常用于应用程序识别和健康检查程序,以及保护可能保存一些敏感信息或可能在系统受到攻击期间提供帮助的信息的其他端点。

另一方面,我们可以将所有管理端点暴露在一个单独的端口上并配置网络访问规则。因此,所有管理将 仅 通过内部虚拟网络进行。要提供这样的配置,我们所要做的就是为 management.server.port属性提供所需的 HTTP 端口。

概括地说,Spring Boot 执行器带来了许多简化应用程序识别、监控和管理的特性。通过紧密的 Spring WebFlux 集成,Actuator 2.x 可以有效地使用资源,这要归功于它的大多数端点都支持响应式类型。 Actuator 2.x 简化了整个开发过程,而不会造成很多麻烦,扩展了应用程序功能,整合了更好的默认应用程序行为,并使 DevOps 团队的生活更轻松。

 

尽管 Spring Boot Actuator 本身非常有用,但与自动收集监控信息并表示此类 信息< /a> 通过图表、趋势和警报以视觉形式呈现。因此,在本章的后面,我们将介绍一个名为 Spring Boot Admin 的便捷模块,它允许通过一个漂亮的 UI 访问多个服务的所有重要管理信息。

千分尺

从 Spring Boot 2.0 开始,Spring Framework 更改了用于度量收集的默认库。这以前是 Dropwizard Metrics (https://metrics.dropwizard.io),但现在它是一个全新的库,名为 Micrometer (https://micrometer.io)。 Micrometer 是一个独立 库,具有最少的依赖项列表。这是作为一个单独的项目开发的,但它以 Spring Framework 作为其主要使用者。该库提供为最流行的监控系统提供客户端外观。 Micrometer 提供了一个供应商中立的监控 API,就像 SLF4J 提供这样一个用于日志记录的 API。目前,Micrometer 与 Prometheus、Influx、Netflix Atlas 以及其他十几个监控 系统完美集成。它还有一个嵌入式内存指标存储,即使没有外部监控系统,也允许 使用 库。

Micrometer 旨在支持维度度量,其中除了名称之外的每个度量都可能包含键/值中的标签。这种方法可以概览聚合值,也可以在需要时使用标签向下钻取。当目标监控系统不支持维度度量时,库会展平关联的标签并将它们添加到名称中。

笔记

要了解有关 Micrometer 设计、其 API 和支持的监控系统的更多信息,请参阅项目站点 https:// /micrometer.io

Spring Boot 执行器使用 Micrometer 通过 /actuator/metrics 端点报告应用程序指标。使用执行器模块,我们得到  MeterRegistry 类型 默认自动配置的bean。 这是一个Micrometer接口,用于保护 客户端代码针对所有指标的存储方式和位置的详细信息。每个受支持的监控后端都有一个专用的 MeterRegistry接口实现,因此为了支持 Prometheus,应用程序必须实例化 PrometheusMeterRegistry< /代码>类。当然,Micrometer 提供了一个CompositeMeterRegistry来一次向多个注册表报告指标。此外,MeterRegistry接口是允许从用户代码添加自定义应用程序指标的入口点。

 

有不同类型的指标(如 TimerCounterGauge, DistributionSummaryLongTaskTimerTimeGaugeFunctionCounter, FunctionTimer) 和所有这些扩展Meter接口.每个指标类型都有一个通用的 API,可以以所需的方式配置监控行为。

默认 Spring Boot 指标

默认情况下,Spring Boot 执行器 configures Micrometer 收集最常用的指标,例如进程正常运行时间、CPU 使用率、内存区域使用情况、GC 暂停、treads 计数、加载的类和打开的文件描述符的数量。它还将 Logback logger 事件作为计数器进行跟踪。所有这些行为都由执行器的 MetricsAutoConfiguration  定义,并提供了关于服务指标的非常明显的图片。

对于响应式应用程序,执行器提供WebFluxMetricsAutoConfiguration,它添加了一个专门的WebFilter。此过滤器将回调添加到ServerWebExchange,以与请求处理完成的时刻相交。可以报​​告请求时间。此外,在指标的报告中,该过滤器以标签的形式包含请求 URI、HTTP 方法、响应状态和异常类型(如果有)等信息。通过这种方式,可以使用 Micrometer 库轻松测量 HTTP 请求的反应式处理。度量结果使用名为http.server.requests 的度量来报告。

同样,Spring Boot 执行器检测RestTemplate并注册 http.client.requests指标以报告传出请求。从 Spring Boot 2.1 开始,执行器对 WebClient.Builder bean 执行相同的操作。尽管如此,即使对于 Spring Boot 2.0,为WebClient 相关工作流添加所需的指标仍然很容易。下一节将解释这种方法。

除了其他有用的东西之外,Spring Boot 执行器还可以将通用标签添加到应用程序的所有Meter。这在多节点服务部署中特别方便,因为它可以让我们通过标签清楚地区分服务节点。为此,应用程序应该注册一个实现MeterRegistryCustomizer接口的bean。

笔记

要了解有关 Spring Boot 执行器通过 Micrometer 库启用的自定义的更多信息,请阅读以下文章:https://docs.spring.io/spring-boot/docs/current/reference/htmlsingle/#production-ready-metrics-入门

 

监控反应流

从版本 3.2 开始,Project Reactor 本身添加了与 Micrometer 库的essential 集成。 Project Reactor 可以在没有 Micrometer 库的情况下正常工作,但如果它检测到应用程序的类路径中存在 Micrometer,它可能会报告一些元素指标,这些指标可能会揭示 value 关于应用程序运行条件的见解。现在是时候描述如何使用内置函数监控反应流以及如何为所需的操作方面添加自定义计量器了。

笔记

Project Reactor 3.2 与 Spring Framework 5.1 和 Spring Boot 2.1 捆绑在一起。但是,一般来说,即使对于使用 Spring Framework 5.0 和 Spring Boot 2.0 的应用程序,也可以提升反应器版本。此外,与自定义指标注册相关的所有代码都应该可以在 Project Reactor 的早期版本中正常工作。

监测反应堆流量

在 Project Reactor 3.2 中,响应式类型FluxMono获得了一个metrics()运营商。这会在调用时报告有关流的操作指标。 metrics() 运算符的行为类似于 log() 运算符。它与 name() 运算符合作,构建目标指标名称并添加标签。例如,以下 代码演示了如何将指标添加到传出的 SSE 流:

@GetMapping(
       path ="/temperature-stream",
       produces = MediaType.TEXTEVENTSTREAMVALUE)public Flux<Temperature>events(){return temperatureSensor.temperatureStream()// (1).name("temperature.stream")// (2).metrics();// (3)}

在这里, temperatureSensor.temperatureStream()返回Flux (1 ),而 name("temperature.stream")  ;方法 为监控点添加一个名称(2)metrics()方法(3) 将一个新指标注册到MeterRegistry 实例。

结果,Reactor 库注册了以下计数器——reactor.subscribedreactor.requested和以下计时器——reactor.flow.duration,reactor.onNext.delay,每一个都有一个标签(维度)叫做带有temperature.stream 值。仅这些指标就可以让我们跟踪流实例化的 number、请求元素的数量、流的最大和总时间存在以及onNext延迟。

笔记

请注意,Reactor 使用流的名称作为 Micrometer 度量的标签。由于 Micrometer 库对跟踪标签的数量有限制,因此配置该限制以收集有关所有计量流的信息非常重要。

监控反应堆调度程序

因为响应式 streams 通常在不同的 reactor 调度程序上运行,因此跟踪有关其运行的细粒度指标可能是有益的。在某些情况下,将自定义ScheduledThreadPoolExecutor 与手动指标检测一起使用是合理的。例如,假设我们有以下类:

publicclassMeteredScheduledThreadPoolExecutorextendsScheduledThreadPoolExecutor{// (1)publicMeteredScheduledThreadPoolExecutorShort(int corePoolSize,
      MeterRegistry registry                                         // (2)){super(corePoolSize);
      registry.gauge("pool.core.size",this.getCorePoolSize());// (3)
      registry.gauge("pool.active.tasks",this.getActiveCount());//
      registry.gauge("pool.queue.size",this.getQueue().size());//}}

在这里, MeteredScheduledThreadPoolExecutor类扩展了一个普通的ScheduledThreadPoolExecutor(1) 另外还接收一个MeterRegistry实例(2)。在构造函数中,我们注册了几个仪表(3) 来跟踪线程池的核心大小、活动任务数和队列大小。

只需稍加修改,此类执行器还可以跟踪成功和失败任务的数量以及执行这些任务所花费的时间。为此, 执行器的实现  还应该覆盖beforeExecute()afterExecute()方法。

现在我们可以在以下反应流中使用这样的 instrumented 执行器:

MeterRegistry meterRegistry =this.getRegistry();

ScheduledExecutorService executor =newMeteredScheduledThreadPoolExecutor(3, meterRegistry);

Scheduler eventsScheduler = Schedulers.fromExecutor(executor);

Mono.fromCallable(this::businessOperation).subscribeOn(eventsScheduler)....

尽管这种方法非常强大,但它不包括内置的调度程序,如parallel()弹性()。为了检测所有反应器调度程序,我们可以使用自定义Schedulers.Factory。例如,让我们看一下这样一个工厂的以下实现:

classMetersSchedulersFactoryimplementsSchedulers.Factory{// (1)privatefinal MeterRegistry registry;public ScheduledExecutorService decorateExecutorService(// (2)
      String type,// (2.1)
      Supplier<?extendsScheduledExecutorService> actual          // (2.2)){
      ScheduledExecutorService actualScheduler = actual.get();// (3)
      String metric ="scheduler."+ type +".execution";// (4)

      ScheduledExecutorService scheduledExecutorService =newScheduledExecutorService(){// (5)publicvoidexecute(Runnable command){// (6)
            registry.counter(metric,"tag","execute")// (6.1).increment();
            actualScheduler.execute(command);// (6.2)}public<T> Future<T>submit(Callable<T> task){// (7)
            registry.counter(metric,"tag","submit")// (7.1).increment();return actualScheduler.submit(task);// (7.2)}// other method overrides ...};

      registry.counter("scheduler."+ type +".instances")// (8).increment();return scheduledExecutorService;// (9)}}

上述代码解释如下:

  1. 这是反应堆的Schedulers.Factory 类的自定义实现。
  2. 在这里,我们有一个方法声明,它允许装饰调度的执行器服务,其中type(2.1) 表示一个类型调度器(parallel,elastic)和实际(2.2)包含对要修饰的实际服务的引用。
  3. 此时,实际执行器服务实例被提取,因为它在装饰器中使用。
  4. 这是计数器名称的定义,其中包含 Scheduler 的类型以增加指标的可读性。
  5. 这是声明,匿名ScheduledExecutorService 实例, 装饰实际的ScheduledExecutorService 并提供额外的行为。
  6. 这是重写的 execute(Runnable command)方法,它执行 MeterRegistry#counter 和附加的执行 tag (6.1)并将收集的指标数加一。然后, 委托调用实际方法的 实现 (6.2)
  7. 类似于 execute 方法,我们重写 the Future<T> submit(Callable<T> task)方法在这里。随着每个方法的执行,调用 MeterRegistry#counter方法并附加一个submit 标签,最后增加调用次数在注册表中 (7.1) . 在所有操作之后,我们将 方法的执行委托给实际的服务(7.2)
  8. 这是注册执行器服务的创建实例计数的声明。
  9. 在这一点上,我们最终返回了装饰的ScheduledExecutorService实例,并进行了额外的 Micrometer 指标处理。

要使用 MetersSchedulersFactory,我们必须注册工厂如下:

Schedulers.setFactory(newMeteredSchedulersFactory(meterRegistry));

现在所有 Reactor 调度程序(包括在MeteredScheduledThreadPoolExecutor 上定义的自定义调度程序)都使用 Micrometer 指标进行计量。这种方法提供了很大的灵活性,但需要一些纪律来防止指标收集成为应用程序中最需要资源的部分。例如,即使仪表注册表中的仪表(计数器/计时器/仪表)查找应该很快,但最好将检索到的引用保存到局部变量或实例字段以减少冗余查找。此外,并非应用程序执行 的所有方面都值得一组专门的指标。通常,应用程序域和使用我们的常识有助于选择要监控的基本应用程序特征。

添加自定义千分尺

在响应式流中,即使没有内置支持,我们也可以轻松添加自定义监控逻辑。例如,以下代码说明了如何为 WebClient invocation 计数器代码> that 没有任何默认检测:

WebClient.create(serviceUri)// (1).get().exchange().flatMap(cr -> cr.toEntity(User.class))// (2).doOnTerminate(()-> registry
               .counter("user.request","uri", serviceUri)// (3).increment())

在这里,我们为目标创建一个新的WebClientserviceUri(1) ,发出请求并将响应反序列化到Userentity(2)。当操作终止时,我们手动增加一个自定义标签uri的计数器,其中值代表serviceUri(3)

类似地,我们可以结合流钩子(如 .doOnNext(),.doOnComplete(),.doOnSubscribe(), or .doOnTerminate()) 与不同类型的千分尺(如 .counter().timer().gauge()) 来测量所需的操作反应流的特征,具有由代表不同维度的标签提供的足够详细程度的细节。

使用 Spring Boot Sleuth 进行分布式跟踪

成功反应式系统操作的另一个关键因素是了解事件如何在服务之间流动,请求如何堆栈-起来,这需要多长时间。如第 8 章所述, 使用 Cloud Streams 扩展 服务之间的通信是分布式系统运行的重要组成部分,有些问题没有数据流的全貌。但是,考虑到分布式系统中通信的所有复杂性 这种捕获是一项非常复杂的任务。 幸运的是,Spring Cloud 提供了一个出色的模块,称为spring-cloud-sleuth 。 Spring Cloud Sleuth 是一个与 Spring Boot 2.x 基础架构良好集成的项目,只需少量自动配置即可实现分布式跟踪。

笔记

想了解更多关于Spring Cloud Sleuth,请参考以下页面:https://cloud.spring.io/spring-cloud-sleuth/single/spring-cloud-sleuth.html

尽管 Zipkin 或 Brave 等大多数现代跟踪工具并不完全支持新的 Spring 响应式 Web 服务,但 Spring Cloud Sleuth 自然地填补了这一空白。

首先,WebFlux 有一个相同的 WebMVC 过滤器替代品,称为org.springframework.cloud.sleuth.instrument.web.TraceWebFilter。该过滤器可确保仔细分析所有传入的 HTTP 请求,并将找到的任何跟踪标头报告给 Zipkin。反过来,在 org.springframework.cloud.sleuth.instrument.web.client.TracingHttpClientInstrumentation 的支持下, 所有传出的请求也会使用额外的跟踪头进行调整。要跟踪传出请求,我们必须将WebClient注册为上下文bean,而不是每次都创建它;否则,不会发生正确的检测。

我们还必须阐明所有跟踪跨度是如何在反应式编程范式中存储和传递的。正如我们可能从前几章中了解到的,反应式编程并不能保证所有的转换都在同一个线程上执行。这意味着元数据通过 ThreadLocal 传输,主要 tracing 模式用于< span class="strong">WebMVC, 在这里的工作方式与在命令式编程范式中。然而,由于反应器的Context机制的组成(在Chapter 4中描述, Project Reactor - Reactive Apps 的基础),Reactor 的全局 Hooks,以及org.springframework.cloud.sleuth.instrument.web.client.TraceExchangeFilterFunction,可以在没有的情况下通过反应流传输额外的上下文元数据线程本地

最后,Spring Cloud Sleuth 提供了几种将收集到的跟踪信息报告给 Zipkin 服务器的方法。最常见的交付方法是通过 HTTP。不幸的是,目前这种方法是以阻塞的方式实现的。然而,默认的zipkin2.reporter.AsyncReporter在一个单独的线程上向 Zipkin服务器发送spans(跟踪信息片段) .因此,尽管具有阻塞性质,但即使在响应式应用程序中,这种技术也可能非常有效,因为响应式端的调用者受到保护,不会阻塞请求延迟或潜在异常。

除了传统的 HTTP 数据传输协议外,还有一个基于消息队列的选项。 Spring Cloud Sleuth 为 Apache Kafka 和 RabbitMQ 提供了出色的支持。尽管 Apache Kafka 的客户端支持异步、非阻塞消息传递,但底层机制仍然使用相同的AsyncReporter,将非阻塞通信降级为阻塞通信。

Spring Cloud Sleuth 为 Reactive Spring WebFlux 和 Spring Cloud Streams 带来了分布式 跟踪支持。此外,要开始在项目中使用这个伟大的工具,只需要在项目中要求以下依赖项(Gradle 配置):

compile('org.springframework.cloud:spring-cloud-starter-sleuth')compile('org.springframework.cloud:spring-cloud-starter-zipkin')

此外,依赖于项目依赖和可用环境,Spring Boot Autoconfiguration 在运行时准备好分布式跟踪所需的所有 bean。

笔记

当涉及到可用的 Message Broker 依赖项(例如org.springframework.amqp:spring-rabbit)时,通过消息代理的通信自动优于基于 HTTP 的通信。在 HTTP 是发送跟踪数据的首选选项的情况下,有一个名为 spring.zipkin.sender.type 的属性, 遵循与 Zipkin 通信的偏好——RABBITKAFKAWEB

带有 Spring Boot Admin 2.x 的漂亮 UI

从一开始, Spring Boot Admin项目(SBA) 旨在为 Spring Boot 应用程序的管理和监控 提供一个方便的管理界面。 Spring Boot Admin 以其美观且用户友好的 UI 脱颖而出,该 UI 构建在 Spring Actuator 端点之上。这允许访问所有必需的操作信息,例如应用程序健康状态、CPU 和内存指标、JVM 启动标志、应用程序类路径、应用程序指标、HTTP 跟踪和审计事件。它还可以检查和删除活动会话(使用 spring-session)、管理日志级别、进行线程和堆转储等等:

读书笔记《hands-on-reactive-programming-in-spring-5》最后,释放它!

图 10.4。 Spring Boot Admin UI 实时显示应用程序线程状态

一般来说,Spring Boot Admin 旨在帮助操作微服务应用程序。此外,Spring Boot Admin 由两部分组成:

  • The server part. The server acts as a central point of gathering information from all available microservices. Also, the server part exposes the UI through which the gathered information is displayed.
  • The client part. This part is included and runs within each service in the system and registers the host service to the Spring Boot Admin server.

尽管 Spring Boot Admin 的服务器部分被设计为独立应用程序(推荐配置),但可以将此角色分配给现有服务之一。要给服务 Spring Boot Admin 服务器一个角色,只需为应用程序提供 de.codecentric:spring-boot-admin-starter-server 依赖项并添加 <代码 class="literal">@EnableAdminServer 注释以启用所需的自动配置。反过来,为了将所有相邻服务与 Spring Boot Admin Server 集成,它们可能包含 de.codecentric:spring-boot-admin-starter-client依赖项并指向一个SBA 服务器或 SBA 可以利用基于 Eureka 或 Consul 的预先存在的 Spring Cloud Discovery 基础设施。或者,可以在 SBA 服务器端创建静态配置。

SBA Server 还支持集群复制,因此 SBA Server 不应成为高可用微服务应用程序中的单点故障:

读书笔记《hands-on-reactive-programming-in-spring-5》最后,释放它!

图 10.5。 Spring Boot Admin 发现和监控服务

在这种情况下,当 Spring Boot Admin 的 dashboard 配置正确时,它包含一个带有注册实例数量的服务网格.反过来,网格中的每个服务都会显示其健康状态,因此我们可以轻松 了解 系统的整体状态。此外,它还支持 Slack、Hipchat、Telegram 等浏览器的推送通知,因此它可以主动通知 DevOps 团队应用程序基础架构内的重大变化,或者它甚至可能是 Pager Duty 基础架构的一部分。

在 2.0 版之前,作为任何面向 Spring 的附加组件,Spring Boot Admin 都是基于 Servlet API。这意味着客户端和服务器应用程序都基于阻塞 I/O,这对于高负载反应式系统可能效率低下。在 2.0 中,从头开始重写 SBA 服务器以受益于 Spring WebFlux 并通过异步非阻塞 I/O 进行所有通信。

当然,Spring Boot Admin 提供了 options 来充分保护 SBA 服务器 UI,因为它可能会暴露一些敏感信息。 Spring Boot Admin 还提供与 Pivotal Cloud Foundry 的集成。如果 Spring Boot 管理服务器或客户端检测到云平台的存在,则会应用所有必要的配置。因此,简化了客户端和服务器发现。同样,SBA 有一个扩展点,因此完全可以将所需的行为添加到 SBA 基础架构中。例如,这可能包括一个具有切换或自定义审核功能的管理页面。

笔记

要了解有关 Spring Boot Admin 的更多信息,请点击以下链接:http: //codecentric.github.io/spring-boot-admin/current

总而言之,Spring Boot Admin 为 DevOps 团队提供了一个绝佳的机会,可以使用方便且可定制的 UI 轻松监控、操作和发展反应式系统,我们将利用异步非同步的所有优点来做到这一点。阻塞 I/O。

部署到云端


虽然软件开发很有趣,但软件 生命周期是生产。 这指的是 服务于实际的客户请求,进行业务流程,并改进通过提供令人惊叹的商业服务,整个行业。对于我们的应用程序,这个至关重要的步骤称为 release。现在,经过主要在开发者和测试者的监护下,我们的软件系统已经准备好迎接现实世界和  ;真实的生产环境,存在诸多隐患。

考虑到不同的目标环境和不同寻常的客户需求,或者需要什么样的操作质量,我们可能会注意到发布过程本身可能很复杂,至少值得自己看几本书。在这里,我们不涉及与交付软件相关的主题,而只是快速了解不同的软件部署选项以及这些选项如何影响反应式应用程序。

随着物联网的蓬勃发展,尽管我们的反应式应用程序可能很快就会在智能手表或心率传感器等可穿戴设备上运行,但我们在这里考虑的是最常见的目标环境,即内部部署和云端。云的引入改变了我们部署和操作应用程序的方式。从某种意义上说,云让我们在用户需求方面 反应性,因为我们现在可以 现在 在几分钟甚至有时 获得新的计算资源在本地数据中心时代,甚至是几秒钟,而不是几周或几个月。但是,从开发人员的角度来看,这两种方法都非常相似。主要区别在于,在前一种情况下,实际服务器位于建筑物中,而在后一种情况下,它们由云提供商托管。

 

我们使用计算资源来部署应用程序的方式出现了更关键的差异,这种差异与内部部署和云部署有关:

读书笔记《hands-on-reactive-programming-in-spring-5》最后,释放它!

图 10.6。软件的平台层次结构和部署选项

以下是上图的description

  • Hardware: These are bare-metal servers that do not run a hypervisor and consequently are not virtualized but still may be provided as a cloud service. Examples of these are Scaleway (https://www.scaleway.com) and Packet (https://www.packet.net). A user has to manage everything. Even though the setup does not provide a hypervisor, usually some OS is still installed on the server. From a developer's viewpoint, this option is not very different from IaaS.
  • Infrastructure as a Service (IaaS): This cloud provider gives a virtual machine with a Software Define Network and attached storage. Here, a user has to manage an environment and deploy applications. Deployment options choose the user. An example of these are AWS (https://aws.amazon.com) and Microsoft Azure (https://azure.microsoft.com).
  • Containers as a Service (CaaS): This is a form of virtualization where a cloud provider allows clients to deploy containers (for example, Docker or CoreOS Rocket containers). Container technology defines deployment mode, but inside a container, the client may use any technology stack. Examples of these are AWS Elastic Container Service (https://aws.amazon.com/ecs) and the Pivotal Container Service (https://pivotal.io/platform/pivotal-container-service).
  • Platform as a Service (PaaS): This cloud provider gives everything for an application up to runtime and build pipelines, but it also limits the set of technologies and libraries. Examples of these are Heroku (https://www.heroku.com) and the Pivotal Cloud Foundry (https://pivotal.io/platform).
  • Function as a Service (FaaS, Serverless): This is an extremely recent development. Here, a cloud provider manages all the infrastructure, and users deploy and use simple data transformation functions that are expected to start within milliseconds to handle individual requests. Examples of these are AWS Lambda (https://aws.amazon.com/lambda) and Google Cloud Function (https://cloud.google.com/functions).
  • Software as a Service (SaaS): With this on-demand software, everything is managed by a vendor. End users have to use this and cannot deploy their own applications there. Examples of these are Dropbox (https://www.dropbox.com) and Slack (https://slack.com).

笔记

详细了解IaaSPaaS和 之间的区别;并且 SaaS请参考这篇文章:http://www.bmc.com/blogs/saas-vs-paas-vs -iaas-whats-the-difference-and-how-to-chooseCaaS 的简短描述可以在这里找到:https://blog.docker.com/2016/02/containers-as-a-service-caas,而FaaS的介绍文章可以在这里找到:https://stackify.com/function-as-a-service-serverless-architecture

使用 Spring 堆栈构建的反应式应用程序可以部署到除 SaaS 之外的任何环境类型,因为它不提供部署选项。 第 8 章使用 Cloud Streams 扩展,描述了以 FaaS 形式运行反应式应用程序的潜在选项。本章还介绍了将 Spring 应用程序部署到 PaaS、CaaS 和 IaaS 环境的可用选项。此类目标环境之间的主要区别在于可部署工件的格式以及构建此类工件的方式。

仍然可以为针对手动安装的 JVM 应用程序构建复杂的安装程序。虽然可以使用自动化工具快速安装此类软件,例如 Chef (https:// www.chef.io/chef) 或 Terraform (https://www.terraform. io),这样的部署选项对于在云中快速启动应用程序并不是很有吸引力,因为这会带来额外的不必要的运营成本。

目前,Java 应用程序最通用的部署单元可能是 uber-jar,也称为 fat jar。在一个文件中,它不仅包含一个 Java 程序,还包含所有依赖项。尽管 uber-jar 通常会增加应用程序分发的占用空间,尤其是在有大量服务的情况下,但它仍然方便、易于使用并且具有通用方法。大多数其他分发选项都建立在 uber-jar 方法之上。有一个 spring-boot-maven-pluginMaven 插件,它使用 Spring Boot 应用程序构建一个 uber-jar,以及 spring-boot- gradle-plugin为 Gradle 设置提供相同的功能

笔记

要阅读有关 Spring Boot 的可执行 jar 的更多信息,请阅读以下文章:https://docs.spring.io/spring-boot/docs/current/reference/htmlsingle/#getting-started-first-application-executable-罐子。此外,第 8 章, 使用 Cloud Streams 扩展< /span> 描述了一个spring-boot-thin-launcher插件(https://github.com/dsyer/spring-boot-thin-launcher) 用于 Maven 和 Gradle,有助于减小工件的大小,这对于FaaS 用例。

部署到 Amazon Web Services

Amazon Web Services (AWS) 提供了几种不同的 software 部署选项,但服务的核心是 IaaS 模型。该模型施加了最小 amount 的限制,因此我们可以通过多种方式将应用程序部署到 AWS,但为了简而言之,在这里我们将 仅 考虑构建可直接在 AWS 或 VirtualBox 上执行的完全配置映像的选项。

Boxfuse(https://boxfuse.com) 项目正在做确切地 我们需要什么:它为直接在虚拟硬件上启动和运行的 Spring Boot 应用程序生成最小图像。Boxfuse 获取 uber-jar 并将其包装成最小的 VM 映像。它与 Spring Boot 具有出色的集成,并且可以利用 Spring Boot 配置文件中的信息来配置端口和健康检查。此外,它还内置了与 AWS 的集成,并为 Spring Boot 应用程序启用了简单的部署过程。

笔记

以下文章描述了将 Spring Boot 应用程序构建为 AWS 映像的步骤:https://boxfuse.com/blog/spring-boot-ec2.html

 

部署到 Google Kubernetes Engine

Google Kubernetes Engine (GKE) 是一个集群 manager 和 Google 在 Kubernetes 之上构建的编排系统 (https://kubernetes.io),他们的开源系统automated 容器化应用程序的部署、扩展和管理。 GKE 是 CaaS 平台的一个示例,因此是应用程序的部署单元。在这种情况下,这应该是一个 Docker 映像。

使用这种方法,我们最初使用应用程序构建一个胖 jar,然后将其包装到 Docker 映像中。为此,我们可以手动添加Dockerfile并运行 docker build命令,或者使用Maven或Gradle插件来完成作为标准构建管道的一部分。经过测试,我们可能会将镜像部署到 Google Cloud Container Registry (https:// /cloud.google.com/container-registry) 甚至使用 the Google Cloud Container Build 服务 (https://cloud.google.com/cloud-build) 执行实际的构建和容器部署过程。

笔记

以下文章概述了描述如何将 Spring Boot 应用程序部署到 GKE 的步骤:https://cloud.google.com/community/tutorials/kotlin-springboot-container-engine

除了拥有生产服务的容器外,我们还可以通过引用公共 Docker 镜像来部署所有监控基础设施。例如,我们可能会在同一个集群配置文件中引用 Prometheus、Grafana 和 Zipkin。使用基于 Kubernetes 的平台,通过嵌入式自动扩展机制很容易实现系统弹性。

同样,我们可以将应用程序部署到任何 CaaS 平台,包括但不限于 Amazon Elastic Container Service、Azure Container Service、Pivo​​tal Container Service,甚至是 OpenShift 等本地解决方案 (https://www.openshift.com) 或 Rancher (https://rancher.com)

部署到 Pivotal Cloud Foundry

在谈到 application 部署的 PaaS 选项时,Spring Boot 为 Google 云平台 (GCP)HerokuPivotal Cloud Foundry (PCF)。在这里,我们使用 PCF 介绍 integration,但其他选项的处理方式类似,并且具有几乎相同的详细程度。

作为使用 PCF 设置发布流程的第一步,必须了解 Spring 生态系统如何帮助将应用程序部署到 PaaS 平台上。

假设我们有一个微服务流应用程序,它包含三个主要部分:

  • A UI Service that provides a UI to users and additionally plays the role of a service gateway
  • A Storage Service that persists streaming data and transforms them into a user's view model so that a UI Service may handle it and multicast to each subscriber
  • A Connectors Service that is responsible for setting proper configurations between particular data sources and then transferring events from them to the storage service

所有这些服务都通过 message 队列相互交互,在我们的示例中,该队列是一个 RabbitMQ 代理。此外,为了持久保存接收到的数据,该应用程序使用 MongoDB 作为灵活、分区容错和快速的数据存储提供程序。

概括地说,我们有三个可部署的服务,它们通过 RabbitMQ 相互发送消息,其中一个与 MongoDB 通信。对于成功的操作,所有这些服务都应该运行。对于 IaaS 和 CaaS,我们有责任部署和支持 RabbitMQ 和 MongoDB。借助 PaaS,云提供商可以在需要时为这些服务提供服务,并进一步减少运营责任。

要将服务部署到 PCF,我们必须安装 Cloud Foundry CLI,使用mvn package 打包我们的服务,登录 PCF 并运行以下命令:

cf push <reactive-app-name> -p target/app-0.0.1.jar

在部署过程几秒钟后,应用程序应该可以在 http://<reactive-app-name>.cfapps.io 上使用。 CPF 识别 Spring Boot 应用程序并对服务的最佳配置做出有根据的猜测,但当然,开发人员可以使用  manifest.yml 文件定义他/她的偏好。

笔记

要了解更多关于将 Java 应用程序部署到 CPF,请阅读以下文章:https://docs.cloudfoundry.org/buildpacks/java/java-tips.html

该平台负责应用程序的启动和基本配置。另一方面,PaaS 对服务发现和网络拓扑非常固执己见,因此我们无法将所有服务部署在同一台服务器上或配置整体虚拟网络。因此,所有外部服务都可以通过 localhostURI 定位。这种限制导致我们失去了配置灵活性。幸运的是,现代 PaaS 提供商向已部署的应用程序公开平台信息和有关附加组件或连接服务的知识。因此,可以检索所有关键数据,然后可以立即完成所有必要的配置。但是,我们仍然需要编写客户端或特定的基础设施代码来与具体的 PaaS 提供商的 API 集成。那时,Spring Cloud Connectors (https://cloud.spring.io/ spring-cloud-connectors) 来救援:

Spring Cloud 连接器简化了在 Cloud Foundry 和 Heroku 等云平台中连接服务和获得操作环境意识的过程,尤其是对于 Spring 应用程序。

Spring Cloud 连接器减少了与云平台交互的 dedicated 样板代码。在这里,我们不概述服务配置的细节,因为这些信息将在项目的官方页面上描述。相反,我们将在部署到 PCF 的反应式应用程序中实现对反应式功能的开箱即用支持。我们还将描述在 PaaS 中运行 Reactive Spring 应用程序需要什么。

在 PCF 中发现 RabbitMQ

让我们回到我们的应用程序。整个 system 是基于 RabbitMQ 上的异步通信。正如我们可能从 第 8 章中了解到的, 使用 Cloud Streams 进行扩展< /em>,要建立与本地 RabbitMQ 实例的连接,我们所要做的就是提供两个额外的 Spring Boot 依赖项。此外,对于云基础设施,我们必须添加spring-cloud-spring-service-connectorspring-cloud-cloudfoundry-connector 依赖项。最后,我们必须提供如下代码片段中描述的配置和附加的@ScanCloud 注解:

@Configuration@Profile("cloud")publicclassCloudConfigextendsAbstractCloudConfig{@Beanpublic ConnectionFactory rabbitMQConnectionFactory(){returnconnectionFactory().rabbitConnectionFactory();}}

为了获得更好的灵活性,我们使用了@Profile("cloud") 注解, 它仅在云上运行时启用 RabbitMQ 配置,而不是在本地 在开发过程中。

笔记

对于 PCF 案例中的 RabbitMQ,无需提供额外配置,因为 Cloud Foundry 针对 Spring 生态系统进行了高度调整,因此所有必需的注入都在运行时进行,无需额外麻烦。但是,必须遵循这种做法(至少提供@ScanCloud注解)以与所有云提供商兼容。如果部署的Application崩溃,请检查PCF提供的 RabbitMQ服务是否与应用绑定。

在 PCF 中发现 MongoDB

除了简单的 RabbitMQ 配置外,还有一个稍微复杂的反应式数据存储配置。非反应式和反应式数据存储之间的概念差异之一是客户端或驱动程序。非反应式客户端和驱动程序很好地集成到 Spring 生态系统中,并经过许多解决方案的实战测试。相比之下,响应式客户端仍然是一个新事物。

在撰写本文时,PCF 版本 2.2 没有为 Reactive MongoDB(以及任何其他具有响应式客户端的 DB)提供开箱即用的配置。幸运的是,使用 Spring Cloud Connectors 模块,我们可以访问所需的信息并配置 Reactive MongoDB Client,如以下示例代码所示:

@Configuration@Profile("cloud")publicclassCloudConfigextendsAbstractCloudConfig{// (1)...@Configuration// (2)@ConditionalOnClass(MongoClient.class)//@EnableConfigurationProperties(MongoProperties.class)//publicclassMongoCloudConfigextendsMongoReactiveAutoConfiguration{...@Bean@Overridepublic MongoClient reactiveStreamsMongoClient(// (3)
      MongoProperties properties,
      Environment environment,
      ObjectProvider<List<MongoClientSettingsBuilderCustomizer>>
         builderCustomizers
    ){
      List<ServiceInfo> infos =cloud()// (3.1).getServiceInfos(MongoDbFactory.class);if(infos.size()==1){
         MongoServiceInfo mongoInfo =(MongoServiceInfo) infos.get(0);
         properties.setUri(mongoInfo.getUri());// (3.2)}returnsuper.reactiveStreamsMongoClient(// (3.3)
        properties,
        environment,
        builderCustomizers
      );}}}

以下是代码示例的说明:

  1. AbstractCloudConfig 类的这个扩展提供了对 MongoDB 位置的访问权限。
  2. 这是检查MongoClient是否存在于类路径中的常用方法。
  3. 这是一个响应式MongoClientbean 的配置,在该点(3.1)我们从云连接器获取有关 MongoDB 的信息包括连接 URI(3.2)。在(3.3),我们通过重用原始逻辑创建了一个新的反应流 MongoDB 客户端。

在这里,我们从org.springframework.boot.autoconfigure.mongo.MongoReactiveAutoConfiguration扩展了一个配置类,并动态定制MongoProperties关于可用的cloud()配置。

遵循 Cloud Foundry 说明后,https:/ /docs.run.pivotal.io/devguide/deploy-apps/deploy-app.html 并适当配置 MongoDB服务,存储服务应该可用并且流数据应该存储在MongoDB中。

使用 Spring Cloud Data Flow for PCF 进行无配置部署

尽管 Pivotal Cloud Foundry 简化了整体部署流程,Spring Cloud 生态系统提供了全面的帮助,以最小化内部的配置量应用程序,我们仍然需要处理其中的一些。但是,我们可能记得第 8 章, 使用 Cloud Streams 扩展,有一个很棒的解决方案可以简化云的应用程序开发。该解决方案称为 Spring Cloud Data Flow。该项目背后的中心思想是通过用户友好的界面简化反应式系统开发。除了该核心功能之外,Spring Cloud Data Flow 还为不同的云提供商提供了广泛的实现列表,可以在以下链接中找到:https://cloud.spring.io/spring-cloud-dataflow/#platform-implementations。对于我们的用例来说,最重要的是 PCF 的实现。 Spring Cloud Data Flow 可以安装在 PCF 上,并提供开箱即用的无服务器解决方案,用于将管道直接部署到 PCF。

笔记

有关在 PCF 上安装 Spring Cloud Data Flow 所需的所有文档都可以在此处找到:http://docs.spring.io/spring-cloud-dataflow-server-cloudfoundry/docs/current/reference/htmlsingle

总而言之,作为 PaaS 的 Pivotal Cloud Foundry 为简化应用程序部署过程提供了广泛的支持,而开发人员所需的工作量最少。它为我们的应用程序处理许多基础设施职责,例如配置消息代理或数据库实例。此外,PCF 与 Spring Cloud Data Flow 集成得很好。因此,Reactive Cloud Native Applications 的开发成为开发者的梦想。

Knative for FaaS over Kubernetes 和 Istio

2018 年年中,Google 和 Pivotal宣布 Knative 项目(https://pivotal.io/knative)。这旨在使 Kubernetes 能够部署和运行无服务器工作负载。对于通信服务之间的路由,Knative 使用 Istio project (https://istio.io),这可能 也 提供 动态路由配置,发布金丝雀,逐步升级版本,进行A/B测试。 Knative 的目标之一是在任何可能运行 Kubernetes(或内部部署)的云提供商上展开私有 FaaS 平台。

另一个名为 Project Riff 的 Pivotal 项目 (https://projectriff.io)建立在 Knative 之上. Project Riff 背后的主要思想是将函数打包为容器,部署到 Kubernetes,将这些函数与事件代理连接,并根据 incoming 事件。此外,Project Riff 支持可以在 Project Reactor 和 Spring Cloud Function 的支持下以反应方式处理流的功能。

笔记

Knative 和 Project Riff 仍处于开发的早期阶段,但以下文章描述了这些项目背后的动机、用例和想法:https://projectriff.io/blog/first-post-announcing-riff-0-0-2-release< /a>, https://content.pivotal.io/blog/knative-powerful-building-blocks-for-a-portable-function-platform, https://medium.com/google-cloud/knative-2-2-e542d71d531d。此外,以下文章逐步解释了如何使用 Riff:https:// www.sudoinit5.com/post/riff-intro

在某些方面,Knative 和 Project Riff 可能会扩展 Spring Cloud Data Flow 和 Spring Cloud Function 模块的能力,但在其他方面,它们是竞争对手。无论如何,围绕 Knative 的最有可能的举措将为我们带来一个更多的平台,用于部署在  FaaS 范式中实现的响应式 Applications .

成功部署应用程序的一些建议

简而言之,软件交付的成功过程包括许多步骤和行动,这些步骤和行动发生在规划和开发阶段。很明显,如果没有适当的自动化测试基础设施,我们将无法发布任何重要的应用程序,而且很明显,这些测试应该随着生产代码本身的发展而发展,从最初的原型到产品的结束。应用程序的生命。鉴于实际应用程序的复杂性,如今的测试场景还应包含用于性能测试的套件,以识别软件限制并充分计算基础设施(如服务器、网络、存储等)的费用。

 

为了简化产品发货,实现快速反馈循环,同时能够在最终用户注意到之前解决问题,持续交付等技术持续部署 至关重要。监控系统的所有重要指标、收集日志、跟踪错误并对系统行为是否是否需要。广泛的应用程序指标和实时报告让我们不仅可以了解当前状态,还可以打造能够自动扩展、自我修复和自我改进的自动化基础架构。

此外,正如本书所示,Spring Framework 的反应式堆栈为非常高效的应用程序提供了基础,与命令式应用程序相比,这些应用程序不受阻塞 I/O 的限制。 Spring Boot 自动配置带来了必要的经过良好测试的功能组合,因此开发人员很少需要手动进行 bean 配置。 A Spring Boot Actuator 根据最佳实践和标准为成功的应用程序操作提供了所有必要的工具。 Spring Cloud 模块可以轻松地将 Spring 应用程序与消息代理(RabbitMQ、Apache Kafka)、分布式跟踪(Spring Cloud Sleuth 和 Zipkin)等集成。此外,Spring Boot Admin 等社区项目满足了对软件的开箱即用解决方案的额外需求手术。 Spring 生态系统还为所有流行的软件部署选项提供出色的支持,包括 IaaS、CaaS、PaaS 和 FaaS。以正确的比例巧妙地结合前面描述的一些模式和技术应该允许设计、构建、操作和发展一个成功的反应系统。我们希望这本书能简化这样的挑战。

概括


在本章中,我们讨论了软件交付和操作的挑战。我们还列举了一些有助于简化软件产品发布和操作麻烦的技术和 Spring 模块。 Spring Boot Actuator 提供服务标识、健康状态、指标、配置信息、元素请求跟踪、动态更改日志级别的能力等等。 Spring Cloud Sleuth 和 Zipkin 还为我们的微服务系统带来了分布式跟踪,甚至是响应式组件。 Spring Boot Admin 2.x 提供了独特的 UI 体验,以图表和富有表现力的报告的形式公开所有指标。所有这些都显着简化了 DevOps 或运营团队的生活,使我们能够主要专注于业务任务,因为 Spring Boot 模块和插件涵盖了大部分样板。

除此之外,我们还介绍了将响应式 Spring 应用程序配置为在云中运行是多么容易,包括 IaaS、CaaS 和 PaaS 操作模式。我们已经描述了如何使用 Boxfuse 将应用程序部署到 AWS,使用 Docker 将应用程序部署到 GKE,以及从一开始就旨在正确运行 Spring 应用程序的 PCF。

综上所述,使用 Spring 堆栈构建的反应式系统已经具有所有关键元素,不仅可以有效利用资源,还可以在云中成功运行。