精通Spring Boot 3 : 11. Spring Boot 监控工具 (1)

目前,在软件开发和系统管理中,可见性和可观察性在确保应用程序和基础设施的顺畅运行、性能和可靠性方面起着至关重要的作用。尽管这两个术语常常可以互换使用,但它们之间存在着微妙而重要的区别:

  • 可见性:指从系统或应用程序中收集和监控数据的能力。这些数据包括指标、日志和事件,能够提供系统当前状态和行为的洞察。可见性工具帮助组织了解其系统内的动态,使他们能够识别潜在问题,并在问题升级为重大问题之前采取纠正措施。
  • 可观察性:不仅仅是可见性,它还包括理解系统内部运作及其组件之间关系的能力。可观察性工具提供更深入的上下文洞察,帮助开发人员和运维人员更有效地诊断问题、预测潜在故障并优化性能。

可见性和可观察性的优势远不止于识别和解决问题,它们还带来了以下好处:

  • 使组织能够应对潜在需求:通过了解用户与系统的互动方式,组织可以主动识别并解决可能的瓶颈和容量限制,从而在影响用户体验之前采取措施。
  • 使开发人员能够迅速行动:可观察性工具为开发人员提供实时洞察,帮助他们了解代码更改的影响,从而快速识别和修复错误,提高性能。
  • 加快开发和部署:通过减少故障排除和调试所花费的时间,可见性和可观察性工具帮助团队更快速地完成开发和部署周期。

在当今动态复杂的 IT 环境中,可见性和可观察性是确保系统健康、性能和可靠性的关键工具。通过深入洞察系统行为,这些工具使组织能够有效应对潜在需求,帮助开发人员迅速采取行动,从而加快开发和部署的进程。

Spring Boot Actuator 是什么?

Spring Boot Actuator 是一个功能强大的工具,可以在多个方面帮助开发者,包括:

  • 监控应用程序的健康状况:Actuator 提供了一系列端点,展示有关 Spring Boot 应用程序健康的关键指标,如 CPU 使用率、内存消耗和线程池利用率。这些信息可以帮助开发人员识别潜在的性能问题,并在影响用户之前采取相应的措施。
  • 调试与故障排除:Actuator 提供了端点,允许开发人员转储线程堆栈、检查环境变量和查看日志。这些信息在调试和排查运行中应用程序的问题时非常宝贵。
  • 启用远程管理:可以配置执行器通过 HTTPS 公开端点,从而使开发人员能够远程管理和监控他们的应用程序。这对于在生产环境中部署的应用程序尤其有帮助。
  • 与外部监控工具集成:Actuator 可以配置为将指标导出到外部监控工具,例如 Prometheus 或 Grafana。这使开发人员能够集中监控数据,从而更深入地了解应用程序的性能。这一切都得益于 Micrometer 技术 (https://micrometer.io/)。
  • 自定义监控端点:Actuator 提供了灵活的 API,允许开发者创建自定义端点,以便从他们的应用程序中暴露更多信息或功能。

作为开发者,Spring Boot Actuator 可以帮助我们处理这些任务:

  • 识别性能瓶颈:开发人员可以通过监控 CPU、内存和线程池的使用情况,找出应用程序中的性能瓶颈。
  • 检测内存泄漏:Actuator 的 dump 端点使开发人员能够检查堆内存并识别内存泄漏。
  • 故障排除数据库连接问题:Actuator 的连接池指标可以帮助开发人员识别数据库连接的相关问题。
  • 跟踪应用程序的启动与关闭:Actuator 的生命周期事件端点提供了关于应用程序启动和关闭行为的深入见解。
  • 监控应用程序的安全性:Actuator 的安全端点提供有关应用程序安全配置的详细信息。

总的来说,Spring Boot Actuator 是一个非常有价值的工具,可以帮助开发者监控、调试、排查问题和管理他们的 Spring Boot 应用。让我们开始将 Spring Boot Actuator 添加到我们的主要应用中。

基于 Spring Boot Actuator 的用户应用程序

让我们从用户应用程序开始。您可以在 11-actuator/users 文件夹中找到源代码。如果您想从头开始,我们将使用带有 JPA 的用户应用程序。在 Spring Initializr(https://start.spring.io)中,将 Group 字段设置为 com.apress,将 Artifact 和 Name 字段都设置为 users,并添加 Web、Validation、Data JPA、Actuator、H2、PostgreSQL 和 Lombok 依赖项。生成并下载项目,解压缩后导入到您喜欢的 IDE 中。

我们来查看一下 build.gradle 文件,参见清单 11-1。

plugins {
    id 'java'
    id 'org.springframework.boot' version '3.2.3'
    id 'io.spring.dependency-management' version '1.1.4'
}
group = 'com.apress'
version = '0.0.1-SNAPSHOT'
sourceCompatibility = '17'
configurations {
    compileOnly {
        extendsFrom annotationProcessor
    }
}
repositories {
    mavenCentral()
}
dependencies {
    implementation 'org.springframework.boot:spring-boot-starter-web'
    implementation 'org.springframework.boot:spring-boot-starter-validation'
    implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
    implementation 'org.springframework.boot:spring-boot-starter-actuator'
    runtimeOnly 'com.h2database:h2'
    runtimeOnly 'org.postgresql:postgresql'
    compileOnly 'org.projectlombok:lombok'
    annotationProcessor 'org.projectlombok:lombok'
    annotationProcessor 'org.springframework.boot:spring-boot-configuration-processor'
    // Web
    implementation 'org.webjars:bootstrap:5.2.3'
    testImplementation 'org.springframework.boot:spring-boot-starter-test'
}
tasks.named('test') {
    useJUnitPlatform()
}

列表 11-1 的 build.gradle 文件

唯一的新依赖是 spring-boot-starter-actuator。只需添加这个依赖,Spring Boot 的自动配置就会设置所有 Actuator 的默认值。这意味着您可以立即使用生产就绪的功能!Spring Boot Actuator 定义了多个端点,让您可以轻松获得这些开箱即用的生产就绪功能,例如指标、环境变量、事件、会话、线程转储、计划任务、日志记录器、健康指标等。

/控制器

默认情况下,Spring Boot Actuator 将 /actuator 端点作为其他 actuator 端点的前缀。唯一启用的 actuator 端点映射到 /actuator/health 端点,负责显示应用程序的健康状态信息。

如果你从零开始,可以复制包含 JPA 技术的用户应用程序版本。

让我们来运行这个应用程序。无需进行任何修改,只需添加 spring-boot-starter-actuator 依赖即可。您可以通过 IDE 或使用以下命令行来运行用户应用程序:

./gradlew bootRun
...
...
INFO 11418 --- [main] o.s.b.a.e.web.EndpointLinksResolver      : Exposing 1 endpoint(s) beneath base path '/actuator'
...
...

请注意有关暴露 /actuator 端点的日志。

现在,打开浏览器,访问 http://localhost:8080/actuator,您应该能看到 /actuator 端点(前缀)的响应,如图 11-1 所示。

图 11-1 默认前缀 /actuator (http://localhost:8080/actuator)

/actuator 端点(前缀)的响应是 HATEOAS(超媒体作为应用程序状态的引擎)响应。其主要特点之一是包含可以直接在浏览器中访问的资源链接,这些链接可以用于更专业的任务,例如网络爬虫,以了解应用程序如何定义其 API,等等。因此,点击最后的链接 http://localhost:8080/actuator/health,您将看到以下响应:

{
  "status": "UP"
}

/actuator/health 端点提供有关应用程序健康状况的详细信息。我将在后面的部分中为您介绍这个端点的更多细节。目前,这个端点是默认启用的唯一端点。

配置 / 监控端点

您可以配置、覆盖、启用执行器端点,并进行更多操作。正如您所见,执行器的前缀是 /actuator,您可以通过以下属性轻松进行覆盖:

管理端点的网络基本路径

您可以将该属性添加到 application.properties 文件中,并将其值设置为 /users-system-management。请参见列表 11-2。

# Spring Properties
spring.h2.console.enabled=true
spring.datasource.generate-unique-name=false
spring.datasource.name=test-db
# Actuator Config
management.endpoints.web.base-path=/users-system-management
# Spring Info
spring.application.name=Users App

列表 11-2 源文件:src/main/resources/application.properties

列表 11-2 显示我们还添加了一个新属性 spring.application.name,值为 Users App,这将在本章后面测试 Spring Boot Actuator 的一些功能时非常有用。

在前面的属性更改后,重新启动用户应用程序,并在浏览器中输入 http://localhost:8080/users-system-management。请参见图 11-2。

图 11-2 http://localhost:8080/users-system-management

属性管理的 endpoints.web.* 有更多方式可以覆盖默认设置,我们会很快对这些进行审查。

接下来,我们通过在属性声明前加上#来注释掉刚刚添加的属性,这样我们就可以只使用/actuator(一个简短的前缀)。

使用 Spring Profiles 访问 Actuator 端点

在继续使用 Spring Boot Actuator 之前,让我们通过 Spring 配置文件来测试 Actuator 的功能。在 src/main/resources 文件夹中创建一个名为 application-actuator.properties 的空文件,确保它与 application.properties 文件位于同一位置。

启用监控端点

到目前为止,我们只使用了默认的 /actuator/health 端点。现在是时候回顾其他端点了。Spring Boot Actuator 约定使用 /actuator/{id} 的模式来访问端点,其中 {id} 是您想要访问的端点;换句话说,每个功能都有自己的 id,我们需要通过以下属性来启用这些端点:

management.endpoints.web.exposure.[include|exclude]=<id>[,<id>]|*

因此,你可以选择使用这种语法来启用所有选项

management.endpoints.web.exposure.include=*

您也可以单独启用某个执行器功能,如果想启用多个功能,可以用逗号分隔。例如:

management.endpoints.web.exposure.include=health,info,env,shutdown,beans,metrics

默认情况下,这些端点也通过 Java 管理扩展(JMX)进行公开。您还可以通过以下方式选择性地包含或排除 JMS 的监控端点。

management.endpoints.jmx.exposure.[include|exclude]=[*|<id>[,<id>]

例如,要包含所有的控制器端点:

management.endpoints.jmx.exposure.include=*

/actuator/info

这个执行器端点提供了关于您应用程序的一般信息,访问它只需对 /actuator/info 发送一个 GET 请求。通过这个端点,您可以获取 Git(分支、提交等)和构建(工件、版本、组)的相关信息,甚至可以添加您自己关于应用程序的信息。换句话说,您可以在属性 info.* 后添加任何对您的应用程序有意义的内容。

因此,在 application-actuator.properties 文件中添加列表 11-3 中所示的属性。

management.endpoints.web.exposure.include=health, info
# Actuator Info
management.info.env.enabled=true
info.application.name=${spring.application.name}
info.developer.name=Felipe
info.developer.email=felipe@email.com
info.api.version=1.0

列表 11-3 源文件:src/main/resources/application-actuator.properties

列表 11-3 展示了添加到 application-actuator.properties 文件中的属性。首先,请注意我们只暴露健康和信息端点。此外,我们启用了信息环境(management.info.env.enabled=true),并通过 info.*属性为我们的用户应用程序添加了有用的信息。我们使用 info.application.name 属性来调用基础配置(application.properties)。

接下来,让我们运行应用程序,但首先请确保将配置文件添加为环境变量(SPRING_PROFILES_ACTIVE=actuator)或作为参数。如果您使用的是 IDE,请查阅相关文档以了解如何设置配置文件。在 IntelliJ 中,您可以在顶部菜单选择“运行” -> “编辑配置”,找到“活动配置文件”字段,或者在操作系统中设置环境变量。如果您是从命令行运行应用程序,可以执行以下命令:

./gradlew bootRun --args='--spring.profiles.active=actuator'

现在,如果你在浏览器中输入 http://localhost:8080/actuator/info,你将看到类似于图 11-3 中所示的内容。

图 11-3/执行器信息接口

为了启用 Git 和构建信息,我们需要在 build.gradle 文件中添加一个 Git 信息插件(com.gorylenko.gradle-git-properties)以及调用构建信息(buildInfo())。请参见清单 11-4。

plugins {
    id 'java'
    id 'org.springframework.boot' version '3.2.3'
    id 'io.spring.dependency-management' version '1.1.4'
    id "com.gorylenko.gradle-git-properties" version "2.4.1"
}
group = 'com.apress'
version = '0.0.1-SNAPSHOT'
sourceCompatibility = '17'
configurations {
    compileOnly {
        extendsFrom annotationProcessor
    }
}
repositories {
    mavenCentral()
}
springBoot {
    buildInfo()
}
dependencies {
    implementation 'org.springframework.boot:spring-boot-starter-web'
    implementation 'org.springframework.boot:spring-boot-starter-validation'
    implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
    implementation 'org.springframework.boot:spring-boot-starter-actuator'
    runtimeOnly 'com.h2database:h2'
    runtimeOnly 'org.postgresql:postgresql'
    compileOnly 'org.projectlombok:lombok'
    annotationProcessor 'org.projectlombok:lombok'
    annotationProcessor 'org.springframework.boot:spring-boot-configuration-processor'
    // Web
    implementation 'org.webjars:bootstrap:5.2.3'
    testImplementation 'org.springframework.boot:spring-boot-starter-test'
}
tasks.named('test') {
    useJUnitPlatform()
}

列表 11-4 的 build.gradle 文件

列表 11-4 展示了我们在 build.gradle 文件中添加了获取 Git 信息所需的插件以及构建信息的调用。

接下来,您可以重新启动用户应用程序。请记得 actuator 配置文件。从现在开始,我们将使用这个配置文件,请做好准备。重新启动应用程序后,请访问 http://localhost:8080/actuator/info。您将看到类似于图 11-4 的内容。

图 11-4/执行器/信息,包括 Git 和构建信息

图 11-4 展示了/actuator/info 端点的 Git 和构建信息。您可以从 Git 信息中获取更多细节,包括用户、消息、远程、标签等。要启用完整的详细信息,您可以添加以下属性:

management.info.git.mode=full

/actuator/env

这个执行器端点提供了关于应用程序环境的信息。当您想查看应用程序可以访问哪些环境变量时,这非常有用。要进行测试,您可以将其添加到端点列表中:

management.endpoints.web.exposure.include=health,info,env

您可以通过向 /actuator/env 发送以下 GET 请求来访问此端点,您应该会看到与这里显示的输出类似的内容:

curl -s http://localhost:8080/actuator/env | jq .
{
  "activeProfiles": [
    "actuator"
  ],
  "propertySources": [
    {
      "name": "server.ports",
      "properties": {
        "local.server.port": {
          "value": "******"
        }
      }
    },
    {
      "name": "servletContextInitParams",
      "properties": {}
    },
    {
      "name": "systemProperties",
      "properties": {
        "java.specification.version": {
          "value": "******"
        },
        "sun.jnu.encoding": {
          "value": "******"
        },
...
...
}

/actuator/beans

该端点提供有关应用程序 Spring Bean 的信息。您可以通过向 /actuator/beans 发送以下 GET 请求来访问此端点。当您想检查配置是否正确加载了声明的 Bean,或者是否有任何条件 Bean 需要根据业务条件存在时,这非常有用。

curl -s http://localhost:8080/actuator/beans | jq .
{
  "contexts": {
    "Users App": {
      "beans": {
        "userLogs": {
          "aliases": [],
          "scope": "singleton",
          "type": "com.apress.users.events.UserLogs",
          "resource": "file [/Users/felipeg/Progs/Books/pro-spring-boot-3rd/java/11-actuator/users/build/classes/java/main/com/apress/users/events/UserLogs.class]",
          "dependencies": [
            "logEventEndpoint"
          ]
        ...
        ...
        "jdbcTemplate": {
          "aliases": [],
          "scope": "singleton",
          "type": "org.springframework.jdbc.core.JdbcTemplate",
          "resource": "class path resource [org/springframework/boot/autoconfigure/jdbc/JdbcTemplateConfiguration.class]",
          "dependencies": [
            "dataSourceScriptDatabaseInitializer",
            "org.springframework.boot.autoconfigure.jdbc.JdbcTemplateConfiguration",
            "dataSource",
            "spring.jdbc-org.springframework.boot.autoconfigure.jdbc.JdbcProperties"
          ]
        }
      }
    }
  }
}

/actuator/conditions

该端点提供有关配置和自动配置类中条件评估的信息。换句话说,所有标记为@Configuration 注解及其他条件注解的类都会被评估,相关信息将在此处显示。要访问此端点,您可以对/actuator/conditions 发送以下 GET 请求:

curl -s http://localhost:8080/actuator/conditions | jq .
{
  "contexts": {
    "Users App": {
      "positiveMatches": {
     ...
     ...
        "BeansEndpointAutoConfiguration": [
          {
            "condition": "OnAvailableEndpointCondition",
            "message": "@ConditionalOnAvailableEndpoint marked as exposed by a 'management.endpoints.jmx.exposure' property"
          }
        ],
    ...
    ...
        "JacksonAutoConfiguration.JacksonObjectMapperConfiguration": [
          {
            "condition": "OnClassCondition",
            "message": "@ConditionalOnClass found required class 'org.springframework.http.converter.json.Jackson2ObjectMapperBuilder'"
          }
        ],
    ...
    ...
  }
}

/actuator/configprops

该端点提供有关配置属性的类的所有信息,也就是说,标记为@ConfigurationProperties 注解的类。您可以对/actuator/configprops 发送 GET 请求:

curl -s http://localhost:8080/actuator/configprops | jq .
{
  "contexts": {
    "Users App": {
      "beans": {
        "spring.jpa-org.springframework.boot.autoconfigure.orm.jpa.JpaProperties": {
          "prefix": "spring.jpa",
          "properties": {
            "mappingResources": ,
            "showSql": "******",
            "generateDdl": "******",
            "properties": {}
          },
          "inputs": {
            "mappingResources": [],
            "showSql": {},
            "generateDdl": {},
            "properties": {}
          }
        },
        ...
       ...
        "dataSource": {
          "prefix": "spring.datasource.hikari",
          "properties": {
            "error": "******"
          },
          "inputs": {
            "error": {}
          }
        },
        ...
         ....
      }
    }
  }
}

/actuator/heapdump

该端点提供正在运行的应用程序 JVM 的堆转储。要访问这些信息,您可以向/actuator/heapdump 发送以下 GET 请求。需要注意的是,这个端点包含大量信息,响应格式为二进制。因此,您可能更倾向于在 curl 命令中使用-O 选项,以生成一个名为 heapdump 的文件:

curl -s http://localhost:8080/actuator/heapdump -O

有多种软件工具可用于分析 Java 堆转储文件。以下是一些最受欢迎的选项(还有其他选择):Eclipse 内存分析器(MAT)、VisualVM 和 Jhat。

/actuator/threaddump

该端点以 JSON 格式提供正在运行的应用程序 JVM 的线程转储。要获取此信息,您可以向/actuator/threaddump 发送 GET 请求:

curl -s http://localhost:8080/actuator/threaddump | jq .
{
  "threads": [
    {
      "threadName": "Reference Handler",
      "threadId": 2,
      "blockedTime": -1,
      "blockedCount": 7,
      "waitedTime": -1,
      "waitedCount": 0,
      "lockOwnerId": -1,
      "daemon": true,
      "inNative": false,
      "suspended": false,
      "threadState": "RUNNABLE",
      "priority": 10,
      "stackTrace": [
        {
          "moduleName": "java.base",
          "moduleVersion": "17.0.5",
          "methodName": "waitForReferencePendingList",
          "fileName": "Reference.java",
          "lineNumber": -2,
          "nativeMethod": true,
          "className": "java.lang.ref.Reference"
        },
        {
          "moduleName": "java.base",
          "moduleVersion": "17.0.5",
          "methodName": "processPendingReferences",
          "fileName": "Reference.java",
          "lineNumber": 253,
          "nativeMethod": false,
          "className": "java.lang.ref.Reference"
        },
        {
          "moduleName": "java.base",
          "moduleVersion": "17.0.5",
          "methodName": "run",
          "fileName": "Reference.java",
          "lineNumber": 215,
          "nativeMethod": false,
          "className": "java.lang.ref.Reference$ReferenceHandler"
        }
      ],
      "lockedMonitors": [],
      "lockedSynchronizers": []
    },
   ...
   ...
   ]
}

/actuator/mappings

该端点提供有关请求映射的所有信息,包括所有标记为@RestController 或@Controller 的内容以及手动定义的内容。要访问这些信息,您可以向/actuator/mappings 发送 GET 请求:

curl -s http://localhost:8080/actuator/mappings | jq .
{
  "contexts": {
    "Users App": {
      "mappings": {
        "dispatcherServlets": {
          "dispatcherServlet": [
            {
              "handler": "Actuator web endpoint 'configprops'",
              "predicate": "{GET [/actuator/configprops], produces [application/vnd.spring-boot.actuator.v3+json || application/vnd.spring-boot.actuator.v2+json || application/json]}",
              "details": {
               ...
              }
            },
             ...
             ...
            {
              "handler": "Actuator web endpoint 'health'",
              "predicate": "{GET [/actuator/health], produces [application/vnd.spring-boot.actuator.v3+json || application/vnd.spring-boot.actuator.v2+json || application/json]}",
              "details": {
               ....
              }
            },
            ...
            ....
            {
              "handler": "com.apress.users.web.UsersController#save(String)",
              "predicate": "{DELETE [/users/{email}]}",
              "details": {
                ...
              }
            },
            {
              "handler": "com.apress.users.web.UsersController#getAll()",
              "predicate": "{GET [/users]}",
              "details": {
                   ...
              }
            },
            {
              "handler": "ResourceHttpRequestHandler [classpath [META-INF/resources/webjars/]]",
              "predicate": "/webjars/**"
            },
            {
              "handler": "ResourceHttpRequestHandler [classpath [META-INF/resources/], classpath [resources/], classpath [static/], classpath [public/], ServletContext ",
              "predicate": "/**"
            }
          ]
        },
        "servletFilters": [
            ...
            ...
        ]
      }
    }
  }
}

/actuator/loggers

该端点提供有关应用程序设置的日志级别的信息。要获取这些信息,您可以向 /actuator/loggers 发送 GET 请求:

curl -s http://localhost:8080/actuator/loggers | jq .
{
  "levels": [
    "OFF",
    "ERROR",
    "WARN",
    "INFO",
    "DEBUG",
    "TRACE"
  ],
  "loggers": {
    "ROOT": {
      "configuredLevel": "INFO",
      "effectiveLevel": "INFO"
    },
    ...
    ...
  },
  "groups": {
    "web": {
      "members": [
        "org.springframework.core.codec",
        "org.springframework.http",
        "org.springframework.web",
        "org.springframework.boot.actuate.endpoint.web",
        "org.springframework.boot.web.servlet.ServletContextInitializerBeans"
      ]
    },
    "sql": {
      "members": [
        "org.springframework.jdbc.core",
        "org.hibernate.SQL",
        "org.jooq.tools.LoggerListener"
      ]
    }
  }
}

/actuator/metrics


该端点提供了一个指标名称列表,您可以通过在端点末尾添加相应的名称来访问这些指标。这些指标包含有关您的应用程序、系统和 JVM 的有用信息。此外,如果您添加了带有 Prometheus 和 Grafana 的 Micrometer 依赖项,您也可以导出这些指标。这很有趣,对吧?我们将在接下来的部分中实际演示这一点。现在,请查看下面的示例。要访问这些信息,您可以对/actuator/metrics 发送 GET 请求:

curl -s http://localhost:8080/actuator/metrics | jq .
{
  "names": [
    "application.ready.time",
    "application.started.time",
    "disk.free",
    "disk.total",
    "executor.active",
    "executor.completed",
    "executor.pool.core",
    "executor.pool.max",
    "executor.pool.size",
    "executor.queue.remaining",
    "executor.queued",
    "hikaricp.connections",
    "hikaricp.connections.acquire",
    "hikaricp.connections.active",
    "hikaricp.connections.creation",
    "hikaricp.connections.idle",
    "hikaricp.connections.max",
    "hikaricp.connections.min",
    "hikaricp.connections.pending",
    "hikaricp.connections.timeout",
    "hikaricp.connections.usage",
    "http.server.requests",
    "http.server.requests.active",
    "jdbc.connections.active",
    "jdbc.connections.idle",
    "jdbc.connections.max",
    "jdbc.connections.min",
    "jvm.buffer.count",
    "jvm.buffer.memory.used",
    "jvm.buffer.total.capacity",
    "jvm.classes.loaded",
    "jvm.classes.unloaded",
    "jvm.compilation.time",
    "jvm.gc.live.data.size",
    "jvm.gc.max.data.size",
    "jvm.gc.memory.allocated",
    "jvm.gc.memory.promoted",
    "jvm.gc.overhead",
    "jvm.gc.pause",
    "jvm.info",
    "jvm.memory.committed",
    "jvm.memory.max",
    "jvm.memory.usage.after.gc",
    "jvm.memory.used",
    "jvm.threads.daemon",
    "jvm.threads.live",
    "jvm.threads.peak",
    "jvm.threads.started",
    "jvm.threads.states",
    "logback.events",
    "process.cpu.usage",
    "process.files.max",
    "process.files.open",
    "process.start.time",
    "process.uptime",
    "spring.data.repository.invocations",
    "system.cpu.count",
    "system.cpu.usage",
    "system.load.average.1m",
    "tomcat.sessions.active.current",
    "tomcat.sessions.active.max",
    "tomcat.sessions.alive.max",
    "tomcat.sessions.created",
    "tomcat.sessions.expired",
    "tomcat.sessions.rejected"
  ]
}

例如,您可以使用 /jvm.info 来获取 JVM 的指标信息:

curl -s http://localhost:8080/actuator/metrics/jvm.info | jq .
{
  "name": "jvm.info",
  "description": "JVM version info",
  "measurements": [
    {
      "statistic": "VALUE",
      "value": 1.0
    }
  ],
  "availableTags": [
    {
      "tag": "vendor",
      "values": [
        "GraalVM Community"
      ]
    },
    {
      "tag": "runtime",
      "values": [
        "OpenJDK Runtime Environment"
      ]
    },
    {
      "tag": "version",
      "values": [
        "17.0.5+8-jvmci-22.3-b08"
      ]
    }
  ]
}

很快会有关于这个端点的更多信息!

/actuator/shutdown

该端点可以优雅地关闭应用程序。要使 /shutdown 功能正常,您不仅需要将其包含在 management.endpoints.web.exposure.include 属性中,还需要启用它;例如:

management.endpoints.web.exposure.include=health,info,event-config,env,shutdown
management.endpoint.shutdown.enabled=true

完成这些设置后,您可以向 /actuator/shutdown 发送 HTTP POST 请求:

curl -s -XPOST http://localhost:8080/actuator/shutdown
{"message":"Shutting down, bye..."}

你需要非常小心关闭设置。最好的方法是确保它的安全性。只有管理员才能进行此操作。是的,我们可以为此添加安全措施。我将在下一节中向你展示。

本节介绍了最常用的执行器端点。虽然还有许多其他端点,但它们会在正确的依赖项到位并执行自动配置后启用。接下来的部分将讨论其中的几个端点。

增强安全性

任何执行器端点都可能包含您不希望共享的敏感信息,因此最佳的做法是保护您的网站和执行器端点。接下来,让我们为用户应用程序添加安全措施。

在 build.gradle 文件中添加以下依赖项:

implementation 'org.springframework.boot:spring-boot-starter-security'

如果您运行该应用程序并尝试访问 actuator 端点,系统会提示您输入用户名和密码,用户名为 user,密码是控制台中显示的那个。

...
Using generated security password: a84b06a6-1c99-4fb8-969d-6b969f73580a
This generated password is for development use only. Your security configuration must be updated before running your application in production.
INFO 25992 --- [           main] o.s.b.a.e.web.EndpointLinksResolver      : Exposing 16 endpoint(s) beneath base path '/actuator'
...
...

当然,你已经在第 8 章中了解到这一点。不过,我们可以添加一些安全配置,这样每次重启应用时就不需要依赖密码了。请打开或创建 UserSecurityAuditConfiguration 类,参见列表 11-5。

package com.apress.users.config;
import org.springframework.boot.actuate.audit.AuditEventRepository;
import org.springframework.boot.actuate.audit.InMemoryAuditEventRepository;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.Customizer;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.provisioning.InMemoryUserDetailsManager;
import org.springframework.security.provisioning.UserDetailsManager;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.servlet.util.matcher.MvcRequestMatcher;
import org.springframework.web.servlet.handler.HandlerMappingIntrospector;
@Configuration
public class UserSecurityAuditConfiguration {
    @Bean
    public SecurityFilterChain securityFilterChain(HttpSecurity http, HandlerMappingIntrospector introspector) throws Exception {
        MvcRequestMatcher.Builder mvcMatcherBuilder = new MvcRequestMatcher.Builder(introspector);
        http
                .csrf(csrf -> csrf.disable())
                .authorizeHttpRequests( auth -> auth
                        .requestMatchers(mvcMatcherBuilder.pattern("/actuator/**")).hasRole("ACTUATOR")
                        .anyRequest().authenticated())
                .formLogin(Customizer.withDefaults())
                .httpBasic(Customizer.withDefaults());
        return http.build();
    }
    @Bean
    UserDetailsManager userDetailsManager(PasswordEncoder passwordEncoder){
        UserDetails admin = User
                .builder()
                .username("admin")
                .password(passwordEncoder.encode("admin"))
                .roles("ADMIN","USER","ACTUATOR")
                .build();
        UserDetails manager = User
                .builder()
                .username("manager")
                .password(passwordEncoder.encode("manager"))
                .roles("ADMIN","USER")
                .build();
        UserDetails user = User
                .builder()
                .username("user")
                .password(passwordEncoder.encode("user"))
                .roles("USER")
                .build();
        return new InMemoryUserDetailsManager(manager,user,admin);
    }
    @Bean
    PasswordEncoder passwordEncoder(){
        return new BCryptPasswordEncoder();
    }
}

列表 11-5 源代码:src/main/java/com/apress/users/config/UserSecurityAuditConfiguration.java

在 UserSecurityAuditConfiguration 类中,我们设置了一个名为 ACTUATOR 的角色,该角色只能访问/actuator/**端点,并且只有用户 admin 拥有这个角色。因此,如果您重新运行 Users App,/actuator/**端点将会是安全的。

/actuator/auditevents

当启用 Spring Security 时,Spring Boot Actuator 提供了/actuator/auditevents 端点,这使您能够审计每次身份验证成功、失败或访问被拒绝的事件。因此,要使用此端点,您需要定义一个 AuditEventRepository bean,利用 InMemoryAuditEventRepository 类(AuditEventRepository 接口的实现)方便地将这些事件存储在内存中。

因此,在 UserSecurityAuditConfiguration 类中(参见清单 11-5),请添加以下 bean 声明:

@Bean
public AuditEventRepository auditEventRepository() {
     return new InMemoryAuditEventRepository();
}

接下来,重新启动用户应用程序,尝试使用不同的用户和错误的密码进行访问。然后,您可以查看以下事件:

curl -s -u admin:admin http://localhost:8080/actuator/auditevents | jq .
{
  "events": [
    {
      "timestamp": "2023-11-20T19:04:28.683151Z",
      "principal": "manager",
      "type": "AUTHENTICATION_SUCCESS",
      "data": {
        "details": {
          "remoteAddress": "0:0:0:0:0:0:0:1"
        }
      }
    },
    {
      "timestamp": "2023-11-20T19:04:34.975087Z",
      "principal": "manager",
      "type": "AUTHORIZATION_FAILURE",
      "data": {
        "details": {
          "remoteAddress": "0:0:0:0:0:0:0:1"
        }
      }
    },
    {
      "timestamp": "2023-11-20T19:04:54.906022Z",
      "principal": "admin",
      "type": "AUTHENTICATION_SUCCESS",
      "data": {
        "details": {
          "remoteAddress": "0:0:0:0:0:0:0:1"
        }
      }
    }
  ]
}

请记住,admin/admin 是唯一拥有 ACTUATOR 角色的用户。如果您希望在生产环境中使用您的应用程序,您需要实现自己的 AuditEventRepository 类,并可能将其保存到数据库中。

如果你想要更好地控制,可以随时监听这些事件。这些事件使用了一个 AuditApplicationEvent 类,你也可以对其进行监听。因此,如果你愿意,可以将以下代码添加到 UserLogs 类中(我们稍后也会使用它):

@EventListener
public void on(AuditApplicationEvent event) {
    log.info("Audit: {}", event);
}

在源代码中,您可以在 11-actuator/users 文件夹找到完整的类 (src/main/java/com/apress/users/events/UserLogs.java)。

如果您在此更改后重新运行应用程序并尝试登录,您将看到类似于以下的输出:

INFO 26910 --- [nio-8080-exec-1] com.apress.users.events.UserLogs         : Audit Event: org.springframework.boot.actuate.audit.listener.AuditApplicationEvent[source=AuditEvent [timestamp=2023-11-20T20:00:02.559298Z, principal=admin, type=AUTHENTICATION_SUCCESS, data={details=WebAuthenticationDetails [RemoteIpAddress=127.0.0.1, SessionId=null]}]]

现在,你知道如何为执行器端点添加安全措施,并利用审计事件。