20 Service Monitoring How to Implement System Monitoring With Actuator Components

20 Service Monitoring - How to Implement System Monitoring with Actuator Components #

In this lecture, we will introduce a very distinctive topic in Spring Boot - system monitoring.

System monitoring is a new feature introduced in Spring Boot that effectively manages the running state of the application. The Spring Boot Actuator component mainly provides system monitoring functionality through a series of HTTP endpoints. Therefore, next, we will introduce the Spring Boot Actuator component and how to use it for system monitoring, as well as how to extend the Actuator endpoints.

Introducing the Spring Boot Actuator Component #

Before initializing the Spring Boot system monitoring feature, we first need to introduce the Spring Boot Actuator component. The specific operation is to add the following Maven dependency in the pom.xml:

<dependency>

    <groupId>org.springframework.boot</groupId>

    <artifactId>spring-boot-starter-actuator</artifactId>

</dependency>

Please note that not all endpoints are exposed after introducing the Spring Boot Actuator component. For example, when starting customer-service, we can find the following content in the startup log:

Exposing 2 endpoint(s) beneath base path '/actuator'

After accessing the /actuator endpoint at http://localhost:8080/actuator, we will also get the following result:

{

    "_links": {

        "self": {

            "href": "http://localhost:8080/actuator",

            "templated": false

        },

        "health-path": {

            "href": "http://localhost:8080/actuator/health/{*path}",

            "templated": true

        },

        "health": {

            "href": "http://localhost:8080/actuator/health",

            "templated": false

        },

        "info": {

            "href": "http://localhost:8080/actuator/info",

            "templated": false

        }

    }

}

This result is in the HATEOAS style of HTTP response. If we want to see all the endpoints that are not visible by default, we need to add the following configuration information in the application.yaml file:

management:

  endpoints:

    web:

      exposure:

        include: "*"  

After restarting the application, we can obtain all the endpoints exposed by Spring Boot Actuator, as shown in the following code:

{

    "_links": {

        "self": {

            "href": "http://localhost:8080/actuator",

            "templated": false

        },

        "beans": {

            "href": "http://localhost:8080/actuator/beans",

            "templated": false

        },

        "health": {

            "href": "http://localhost:8080/actuator/health",

            "templated": false

        },

        "health-path": {

            "href": "http://localhost:8080/actuator/health/{*path}",

            "templated": true

        },

        "info": {

            "href": "http://localhost:8080/actuator/info",

            "templated": false

        },

        "conditions": {

            "href": "http://localhost:8080/actuator/conditions",

            "templated": false

        },

        "configprops": {

            "href": "http://localhost:8080/actuator/configprops",

            "templated": false

        },

        "env": {

            "href": "http://localhost:8080/actuator/env",

            "templated": false

        },

        "env-toMatch": {

            "href": "http://localhost:8080/actuator/env/{toMatch}",

            "templated": true
info:
  app:
    name: Account Service
    version: 1.0.0
  author:
    name: John
    email: john@example.com

这样,当我们访问 Info 端点时,就会得到如下所示的应用信息:

{
  "app": {
    "name": "Account Service",
    "version": "1.0.0"
  },
  "author": {
    "name": "John",
    "email": "john@example.com"
  }
}

我们也可以通过创建自定义的 InfoContributor 对象来扩展 Info 端点。只需要创建一个类实现 InfoContributor 接口,然后在其中根据需要添加自定义的信息即可。以下是一个示例:

@Component
public class CustomInfoContributor implements InfoContributor {

  @Override
  public void contribute(Builder builder) {
    builder.withDetail("custom", "This is a custom info");
  }
}

通过上述方式创建了一个自定义的 InfoContributor 对象,我们可以在访问 Info 端点时得到如下所示的信息:

{
  "custom": "This is a custom info"
}

扩展 Health 端点 #

Health 端点主要用来向外界展示应用程序的健康状况。在 Spring Boot 中,Health 端点使用 HealthIndicator 接口来暴露所需的健康指标。Spring Boot Actuator 默认提供了一系列的健康指标,比如内存使用情况、线程问题、数据库连接等。

在默认情况下,Health 端点会调用所有的 HealthIndicator 实现类的 health() 方法,并将所有指标的健康信息合并返回。当然,我们也可以通过配置来指定只调用特定的指标实现类。

以下是一个自定义的 HealthIndicator 的示例:

@Component
public class CustomHealthIndicator implements HealthIndicator {

  @Override
  public Health health() {
    int errorCode = check(); // 检查应用程序的健康状态,返回 0 表示正常
    if (errorCode != 0) {
      return Health.down().withDetail("Error Code", errorCode).build();
    }
    return Health.up().build();
  }

  private int check() {
    // 实现检查应用程序的逻辑
    // 返回 0 表示正常,非 0 表示异常
  }
}

通过以上示例,我们定义了一个自定义的健康指标 CustomHealthIndicator。我们可以根据具体的需求,自定义检查应用程序的健康状态的实现逻辑。在访问 Health 端点时,将得到如下所示的结果:

{
  "status": "UP"
}

如果我们在应用程序处于异常状态时,返回一个非 0 的 errorCode,则得到如下所示的结果:

{
  "status": "DOWN",
  "details": {
    "Error Code": 500
  }
}

使用自定义的 HealthIndicator 更加灵活,可以根据实际情况来定义应用程序的健康状态。我们可以在自定义 HealthIndicator 中实现对数据库、缓存、消息队列等资源的健康检查,从而更加全面地了解应用程序的运行状态。

经过上述介绍,我们可以看到通过扩展 Actuator 端点,我们可以方便地添加自定义的功能和信息。这使得我们可以根据应用的需求,灵活地调整和扩展监控功能。在实际开发过程中,我们可以根据具体业务场景,运用这些扩展能力,实现更加灵活、智能的监控系统。 info:

app:

	encoding: UTF-8

	java:

		source: 1.8.0_31

		target: 1.8.0_31

When accessing the Info endpoint now, we can get the following Environment information.

{

     "app":{

         "encoding":"UTF-8",

         "java":{

             "source":"1.8.0_31",

             "target":"1.8.0_31"

         }

     }

 }

At the same time, we can expand the Info properties during service construction instead of hardcoding these values. Assuming we are using Maven, we can rewrite the previous example with the following configuration to achieve the same effect.

info: 

app:

	encoding: @project.build.sourceEncoding@

	java:

	  source: @java.version@

	  target: @java.version@

Many times, the Info endpoint provided by Spring Boot itself cannot meet our business requirements, so we need to write a custom InfoContributor object.

The method is also simple. We can directly implement the contribute() method of the InfoContributor interface. For example, if we want to expose the build time of the application in the Info endpoint, we can use the following code.

@Component

public class CustomBuildInfoContributor implements InfoContributor {



  @Override

  public void contribute(Builder builder) {

      builder.withDetail("build", 

          Collections.singletonMap("timestamp", new Date())); 

  }

}

After rebuilding the application and accessing the Info endpoint, we can get the following information.

{

     "app":{

         "encoding":"UTF-8",

         "java":{

             "source":"1.8.0_31",

             "target":"1.8.0_31"

         }

     },

     "build":{

         "timestamp":1604307503710

     }

 }

Here we can see that the CustomBuildInfoContributor has added a time property to the Info endpoint.

Expanding the Health Endpoint #

The Health endpoint is used to check the health status of the running application, and the health status information is obtained from HealthIndicator objects from Spring’s ApplicationContext.

Like the Info endpoint, Spring Boot also provides a series of HealthIndicator objects for us to customize. By default, the HealthAggregator sorts each status based on the ordered list of HealthIndicator objects to get the final system status.

Common HealthIndicators are as follows:

HealthIndicator Name Description
DiskSpaceHealthIndicator Check if there is enough disk space
DataSourceHealthIndicator Check if it can obtain a DataSource connection
ElasticsearchHealthIndicator Check if the Elasticsearch cluster is started
JmsHealthIndicator Check if the JMS broker is started
MailHealthIndicator Check if the mail server is started
MongoHealthIndicator Check if the Mongo database is started
RabbitHealthIndicator Check if the RabbitMQ server is started
RedisHealthIndicator Check if the Redis server is started
SolrHealthIndicator Check if the Solr server is started

The level of detail in the Health endpoint information depends on the current environment of the application, and a real Health endpoint information is shown in the following code.

{

     "status":"UP",

     "components":{

         "db":{

             "status":"UP",

             "details":{

                 "database":"MySQL",

                 "result":1,

                 "validationQuery":"/* ping */ SELECT 1"

             }

         },

         "diskSpace":{

             "status":"UP",

             "details":{

                 "total":201649549312,

                 "free":3491287040,
"threshold":10485760

},

"ping":{

"status":"UP"

}

}

}

}

}

通过以上这些信息,我们就可以判断该环境中是否包含了 MySQL 数据库。

现在,我们还想在 Health 端点中暴露 customer-service 当前运行时状态。

为了进一步明确该服务的状态,我们可以自定义一个 CustomerServiceHealthIndicator 端点专门展示 customer-service 的状态信息,CustomerServiceHealthIndicator 的定义如下所示:

@Component

public class CustomerServiceHealthIndicator implements

HealthIndicator {

@Override

public Health health() {

try {

URL url = new

URL("http://localhost:8083/health/");

HttpURLConnection conn = (HttpURLConnection)

url.openConnection();

int statusCode = conn.getResponseCode();

if (statusCode >= 200 && statusCode < 300) {

return Health.up().build();

} else {

return Health.down().withDetail("HTTP Status Code", statusCode).build();

}

} catch (IOException e) {

return Health.down(e).build();

}

}

}

我们需要提供 health() 方法的具体实现并返回一个 Health 结果。该 Health 结果应该包括一个状态,并且可以根据需要添加任何细节信息。

以上代码中,我们使用了一种简单且直接的方式判断配置中心服务“customerservice”是否正在运行。然后我们构建一个 HTTP 请求,并根据 HTTP 响应得出了健康诊断的结论。

如果 HTTP 响应的状态码处于 200~300 之间,我们认为该服务正在运行,此时,Health.up().build() 方法就会返回一种 Up 响应,如下代码所示:

{

"status": "UP",

"details": {

"customerservice":{

"status": "UP"

}

…

}

}

如果状态码不处于这个区间(例如返回 404,代表服务不可用),Health.down().withDetail().build() 方法就会返回一个 Down 响应,并给出具体的状态码,如下代码所示:

{

"status": "DOWN",

"details": {

"customerservice":{

"status": "DOWN",

"details": {

"HTTP Status Code": "404"

}

},

…

}

}

如果 HTTP 请求直接抛出了异常,Health.down().build() 方法同样会返回一个 Down 响应,并返回异常信息,效果如下代码所示:

{

"status": "DOWN",

"details": {

"customerservice":{

"status": "DOWN",

"details": {

"error": "java.net.ConnectException: Connection refused: connect"

}

},

…

}

}

显然,通过扩展 Health 端点为我们实时监控系统中各个服务的正常运行状态提供了很好的支持,我们也可以根据需要构建一系列有用的 HealthIndicator 实现类,并添加报警等监控手段。

### 小结与预告

Spring Boot 内置的 Actuator 组件使得开发人员在管理应用程序运行的状态有了更加直接且高效的手段。

这一讲,我们引入了 Actuator 组件并介绍了该组件提供的一系列核心端点,同时重点分析了 Info 和 Health 这两个基础端点,并给出了对它们进行扩展的系统方法。

系统监控的一大目标是收集和分析系统运行时的度量指标,并基于这些指标判断当前的运行时状态,因此,21 讲我们将讨论如何在系统中嵌入自定义度量指标的实现技巧。