SpringBoot应用监控

SpringBoot提供了应用监控和管理的功能,允许我们可以在系统运行时通过http、JMX、SSH等其他协议进行相关操作。一般检测系统健康以及各个资源相关信息时,还是比较有用的。目前公司运维会要求各个产品提供相应的健康指标进行系统健康监测,就时通过采用SpringBoot提供的监控体系完成的。

SpringBoot监控、管理的端点主要有:

端点名称 描述
actuator 所有端点的列表信息
autoconfig 系统所有的自动配置
beans 系统中所有的bean信息
configprops 系统中所有的配置属性
dump 当前系统的线程状态信息
env 当前系统的环境信息
health 系统的健康状态信息
info 系统信息
metrics 系统的各项指标信息
mappings 映射路径信息,@RequestMapping
shutdown 默认不起作用,通过端点可以关闭系统
trace 追踪信息,依赖最新的http请求

虽然SpringBoot支持多种协议访问和管理这些监控端点,不过最常用的应该还是http,可以通过API直接访问,运维有时会写一些脚本来监控这些API,检测系统各个健康指标,因此这里主要就以http的方式进行学习。

端点配置方式

创建一个SpringBoot项目,并添加spring-boot-starter-web引用。

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

SpringBoot版本为:2.2.4.RELEASE

2.0后端点的地址均为/actuator/**模式,如/actuator/health

项目创建成功后,直接启动并访问http://localhost:8080/actuator,将得到如下结果。

{
  "_links": {
    "self": {
      "href": "http://localhost:8080/actuator",
      "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
    }
  }
}

此时直接访问http://localhost:8080/actuator/info,将得到空的JSON对象{},是因为没有配置任何的站点信息。配置以下信息(信息从POM中获取),这里只配置了站点名称,描述信息以及版本信息。

info:
  app:
    name: "@project.name@"
    description: "@project.description@"
    version: "@project.version@"

再次访问http://localhost:8080/actuator/info,得到如下信息。

{
  "app": {
    "name": "actuator",
    "description": "SpringBoot监控模块测试",
    "version": "0.0.1-SNAPSHOT"
  }
}

yml中通过@@包裹的字符串形式访问项目POM中的配置信息,如上面的name、description、version,按照路径的写法可以访问到POM中所有配置的信息。

properties文件通过${}形式访问POM中的配置信息。

默认创建项目后,直接配置启动此时显示的原始字符串,并没有读取到POM中的配置信息,需要在POM的build节点中添加以下信息

<resources>
    <resource>
        <directory>src/main/resources</directory>
        <filtering>true</filtering>
    </resource>
</resources>

此时再次编译启动,将对yml文件的@@字符使用POM中的信息进行替换,在编译后或打包后的文件中查看application.yml文件,发现@project.name等信息已被替换为真实数据信息。

在之前的列表中提到SpringBoot的端点默认有许多种,但是通过创建项目后检测发现默认只有actuator、health、info三种。这是因为从2.0后默认将其他的端点都关闭了,可以通过以下方式进行开启。

management:
  endpoints:
    web:
      exposure:
        include: "*"

重启项目后,再次访问http://localhost:8080/actuator,将得到更全的端点访问信息

{
  "_links": {
    "self": {
      "href": "http://localhost:8080/actuator",
      "templated": false
    },
    "beans": {
      "href": "http://localhost:8080/actuator/beans",
      "templated": false
    },
    "caches-cache": {
      "href": "http://localhost:8080/actuator/caches/{cache}",
      "templated": true
    },
    "caches": {
      "href": "http://localhost:8080/actuator/caches",
      "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
    },
    "loggers": {
      "href": "http://localhost:8080/actuator/loggers",
      "templated": false
    },
    "loggers-name": {
      "href": "http://localhost:8080/actuator/loggers/{name}",
      "templated": true
    },
    "heapdump": {
      "href": "http://localhost:8080/actuator/heapdump",
      "templated": false
    },
    "threaddump": {
      "href": "http://localhost:8080/actuator/threaddump",
      "templated": false
    },
    "metrics": {
      "href": "http://localhost:8080/actuator/metrics",
      "templated": false
    },
    "metrics-requiredMetricName": {
      "href": "http://localhost:8080/actuator/metrics/{requiredMetricName}",
      "templated": true
    },
    "scheduledtasks": {
      "href": "http://localhost:8080/actuator/scheduledtasks",
      "templated": false
    },
    "mappings": {
      "href": "http://localhost:8080/actuator/mappings",
      "templated": false
    }
  }
}

为了站点的安全,有时也不会开启所有的端点。如果只有有限的端点要开启,可以通过以下方式配置实现,除了默认的info、health外,开启了beans端点。

management:
  endpoints:
    web:
      exposure:
        include: [info, health, beans]

再次访问http://localhost:8080/actuator,此时开启的端点和预期的一致

{
  "_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
    }
  }
}

如果大部分端点要开启,只有少部分端点关闭,可以先开启所有的端点,并关闭需要的端点实现

management:
  endpoints:
    web:
      exposure:
        include: "*"
  endpoint:
    beans:
      enabled: false

这里只关闭了beans端点,再次访问http://localhost:8080/actuator,发现beans端点已无法访问。

开启shutdown端点

前面的配置过程中,无论如何配置都无法访问shutdown端点,如果开启该端点则可以通过API的形式将当前系统关闭,还是比较危险的。

通过以下配置方式开启该端点

management:
  endpoints:
    web:
      exposure:
        include: "*"
  endpoint:
    shutdown:
      enabled: true

重启系统,发现shutdown端点已开启,该端口是POST请求

返回的消息为Shutting down, bye...,此时查看系统发现已被关闭。

2020-02-02 23:45:28.657  INFO 16704 --- [      Thread-16] o.s.s.concurrent.ThreadPoolTaskExecutor  : Shutting down ExecutorService 'applicationTaskExecutor'
Process finished with exit code 0

端点的个性化配置

关闭、开启特定端点,开启或关闭所需端点等功能通过前面的使用已经都已完成。除了这些基本的功能外SpringBoot还提供了额外的配置方式,可以完成一些个性化的配置方法。

修改端点访问路径

SpringBoot2默认的访问路径(根路径)为/actuator,可以通过一些简单的配置实现个性化的根路径访问,主要是通过如下的配置management.endpoints.web.base-path

management:
  endpoints:
    web:
      base-path: /my-actuator

重启服务后,通过访问http://localhost:8080/my-actuator,和之前的/actuator结果一致。

{
  "_links": {
    "self": {
      "href": "http://localhost:8080/my-actuator",
      "templated": false
    }...
  }
}

修改端点名称

默认的端点都具有一个id,该id即为访问时的Mapping(默认的端点参见之前的表格)。之前修改了端点的访问路径,即完成了查看所有端点列表的id名称(将/actuator改为/my-actuator)。其他的端点也可以进行调整,其调整的方法如下management.endpoints.web.path-mapping.[id],其中id为所有默认的端点名称。

management:
  endpoints:
    web:
      path-mapping:
        beans: my-beans

上例中将默认的beans端点修改为了my-beans,重启后,原有的my-actuator/beans将无法访问,取代的是my-actuator/my-beans

{
  "_links": {
    "self": {
      "href": "http://localhost:8080/my-actuator",
      "templated": false
    },
    "beans": {
      "href": "http://localhost:8080/my-actuator/my-beans",
      "templated": false
    }...
  }
}

修改访问端口

通常情况下监控程序和系统主服务的端口是一致的,之前的使用过程中8080端口即使业务服务的端口也是监控模块的端口,两者共用。在实际工作中,为了系统的安全,毕竟各个端点会暴露很多服务器的内部信息,需要隐藏断点,此时可以考虑业务端口和监控端口分离的配置方式。

其配置过程如下management.server.port

management:
  server:
    port: 8081

这里将监控端口调整为了8081,此时再访问http://localhost:8080/my-actuator将报404,通过访问http://localhost:8081/my-actuator,得到如下信息

{
  "_links": {
    "self": {
      "href": "http://localhost:8081/my-actuator",
      "templated": false
    },
    "beans": {
      "href": "http://localhost:8081/my-actuator/my-beans",
      "templated": false
    }...
  }
}

在配置端口时,还有一个隐藏的功能,即将端口配置为负数,此时呈现的功能和关闭整个监控断点效果一致。并且配置更方便

management:
  server:
    port: -1

prot配置为-1,将默认关闭所有断点。

开启Health详细信息

默认情况下health信息都是被隐藏的,只能先是一个up|down信息,可以通过如下配置开启展示详细信息。

management:
  endpoint:
    health:
      show-details: always

自定义端点进行健康检查

SpringBoot提供了一系列的默认端点,并且也默认提供了一些健康检查的工具,通过这些端点以及健康检查工具可以完成大部分的监控需求。但总有一些情况下,这些默认的断点和工具无法满足我们的需求,当我们需要对这些特殊需求进行监控时,就可以通过自定义的端点及健康检查工具来满足需求。

在这里描述一个虚拟需求,属于业务级别,而当前系统的最主要功能就是该业务,默认的功能都无法实现健康检查,因此需要通过自定义端点以及自定义健康检查去实现此需求。

现有一个图书销售平台,自研了一个分布式分发存储平台。其具体功能是资源加工工具完成图书加工后将元数据文件、图片文件、图书文件等上传到分发存储平台;分发存储平台完成数据存储,资源加密,以及将此资源分发到子节点的工作。

不关心加工工具的处理过程,也不关心真实分发到子节点的情况(所有的子节点都是分发存储系统,其健康检查过程一致,只要每个系统都按同样的方式进行监控,那么就可以确认整个分布式系统的稳定性),那么就需要通过以下业务健康检查,以确保核心业务是可正常运行的一个状态

  • 分发存储系统内部模拟上传,保证上传及存储流程功能正确性(这里通过上传一个虚拟图书,图书文件为空)

  • 对图书文件的加密流程

  • 分发流程,自身下发流程OK即可

其他的注入消息、数据库、磁盘等监控由SpirngBoot默认健康管理流程实现即可,这里只做业务监测。

自定义端点

这里先定义一个新的端点,用于描述业务的健康检查情况,通过该端点的状态就能轻松的判定核心流程是否正确。

SpringBoot2中实现一个新的端点比较简单,使用@EndPoint@WebEndPoint@JmxEndPoint注解实现一个新的端点。

  • EndPoint端点可以生成支持http和Jmx两种模式的端点,一般情况下使用该注解即可
  • WebEndPoint只能生成支持http模式的端点
  • JmxEndPoint只能生成jmx模式的端点

一旦使用了EndPoint即可创建一个端点,如@EndPoint(id = 'myHealth'),则创建了一个新的端点myHealth,访问时通过/my-actuator/myHealth进行访问。

此外端点指定的方法,可以使用下列三种方法注解,分别可以实现不同的HttpMethod

Operation Method
@ReadOperation Get请求,最常用
@WriteOperation POST请求,往服务器写入数据
@DeleteOperation DELETE请求,删除数据

断点的创建方法清楚后,也知道了访问的端点配置方式,还有一处需要明确:当一个端点实现一组访问断点时,如何实现。比如/health断点,该组有两个访问端点:/my-actuator/health/my-actuator/health/{*path}。查看一下HealthEndPoint

@Endpoint(id = "health")
public class HealthEndpoint extends HealthEndpointSupport<HealthContributor, HealthComponent> {
    // ...略
    @ReadOperation
    public HealthComponent health() {
        HealthComponent health = health(ApiVersion.V3, EMPTY_PATH);
        return (health != null) ? health : DEFAULT_HEALTH;
    }

    @ReadOperation
    public HealthComponent healthForPath(@Selector(match = Match.ALL_REMAINING) String... path) {
        return health(ApiVersion.V3, path);
    }
    // ...略
}

SpringBoot内置端点来看,当使用Operation的三种定义进行注解一个方法,将产生一个端点,当函数无参数时,则为默认的EndPint,当出现@Selector时,则为子断点,如上例中的/my-actuator/health/{*path}

在实现自定义端点前,先创建一个自检的服务,用于实现需求中的三点程序检查功能。

@Service
@Getter
@Setter
@ToString
public class StorageStatusService {
    private String status;
}

该服务最终给为提供一个状态status,其值为running(正常)、stoping(服务异常)。这里暂时不实现自建功能。

创建新的断点/my-actuator/storageStatus

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.actuate.endpoint.annotation.Endpoint;
import org.springframework.boot.actuate.endpoint.annotation.ReadOperation;
import org.springframework.boot.actuate.endpoint.annotation.Selector;
import org.springframework.stereotype.Component;

@Endpoint(id = "storageStatus")
@Component
public class StorageStatusEndPoint {
    @Autowired
    StorageStatusService service;

    @ReadOperation
    public String storageStatus() {
        return "当前服务状态:" + service.getStatus();
    }

    @ReadOperation
    public String storageStatusForName(@Selector String name) {
        return "当前服务:" + name + ",当前服务状态:" + service.getStatus();
    }
}

@Endpoint(id = "storageStatus")指明了端点名为storageStatusstorageStatus()方法为默认的端点,为GET请求ReadOperation。这个例子中只要这一个端点即可,为了演示又增加了一个/storageStatus/{name}的端点,也是GET请求。

这里由于没有地方设置StorageStatusService状态的值,可以在系统启动时给定一个,用于演示,也可以创建一个接口,由外部提供改变,如下所示

@Controller
public class StatusCtrl {
    @Autowired
    StorageStatusService service;

    @GetMapping("/storage/status")
    public String status(String status) {
        service.setStatus(status);
        return "success";
    }
}

启动服务,访问http://localhost:8080/my-actuator/storageStatus

# 刚开始没有状态
当前服务状态:null
# 设置状态后,再次访问
当前服务状态:running
# 不同设置值
当前服务状态:stoping

自定义健康检查

helath端点的所有信息都是从系统中所有的HealthIndicator中收集并展示出来的。Spring已经内置了大量的HealthIndicator,这使得我们不需要自己定义新的HealthIndicator,只要应用了数据库、缓存、消息等常规服务后,health端点能够直接给我们提供健康检查。这些内置的HealthIndicator我们比较常用的主要有

名称 描述
DataSourceHealthIndicator 数据源连接检测
DiskSpaceHealthIndicator 磁盘低空间检测
JmsHealthIndicator JMS消息搭理是否正常检测
ElasticsearchJestHealthIndicator Elasticsearch运行状态检测
MongoHealthIndicator MongoDb是否正常运行监测
RabbitHealthIndicator RabbitMq是否正常运行监测
RedisHealthIndicator Redis是否正常运行监测
SolrHealthIndicator Solr是否正常运行监测

接着上面的需求,我们要实现业务的自检功能,已经有了一个状态自检服务StorageStatusService,为了创建一个自定义的HealthIndicator,一种方式是类似内置的对象,集成AbstractHealthIndicator,还有就是直接实现接口HealthIndicator

  • 这里接着上述的需求,最终也依据StorageStatusService的status状态进行显示健康状况信息,如running为正常,其他状态为失败。为了更好的完成健康自检,模拟图书上传、加密、分发,并且能够得到错误信息并展示到健康检查中,对StorageStatusService进行完善。
@Service
@Getter
@Setter
@ToString
public class StorageStatusService {
    private String status;
    /**
     * 错误的点
     */
    private String errorPoint = "notRuning";
    /**
     * 错误信息
     */
    private String errorMsg = "系统未正常启动";

    /**
     * 自检
     */
    public void selfChecking(String status, String point) {
        this.status = status;
        this.errorPoint = point;
        if ("running".equalsIgnoreCase(status)) {
            return;
        }
        // 第一步上传自检失败
        if ("upload".equalsIgnoreCase(point)) {
            this.errorMsg = "图书资源上传失败";
        }
        if ("encrypt".equalsIgnoreCase(point)) {
            this.errorMsg = "图书资源加密失败";
        }
        if ("distribute".equalsIgnoreCase(point)) {
            this.errorMsg = "图书资源分发失败";
        }
    }
}

这里新增了一个自检方法selfChecking(String status, String point),这场情况下应该是定期执行的一个方法,这里为了简单,能够被外部接口调用,因此直接传递了执行的结果状态,以及那个错误点发生错误。并对错误点和错误信息添加了默认值。

如果系统运行正常,则status=running,当有一步自检出错时,则将status置为非running,并设置哪一步出错,显示到健康检查中,给运维人员进行提醒。

  • 测试状态变更接口进行调整,能够制定错误点。
@GetMapping("/storage/status")
public String status(String status, String point) {
    service.selfChecking(status, point);
    return "success";
}
  • 创建自定义HealthIndicator
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.actuate.health.Health;
import org.springframework.boot.actuate.health.HealthIndicator;
import org.springframework.stereotype.Component;

@Component
public class StorageHealthIndicator implements HealthIndicator {
    @Autowired
    StorageStatusService service;

    @Override
    public Health health() {
        if (!"running".equalsIgnoreCase(service.getStatus())) {
            return Health.down().withDetail(service.getErrorPoint(), service.getErrorMsg()).build();
        }

        return Health.up().build();
    }
}

采用继承HealthIndicator接口的实现方式,重写health()方法

health()方法中,直接检查StorageStatusService的状态信息,当不为running时,直接返回down信息,此时整个系统的健康状态为down。并将错误点以及错误信息作为详细信息返回。

  • 启动服务,进行测试

当系统启动后,由于StorageStatusService为模拟状态,没有后台程序运行,初始时,状态为null。此时访问/my-actuator/health,显示如下信息

{
  "status": "DOWN",
  "components": {
    "storage": {
      "status": "DOWN",
      "details": {
        "notRuning": "系统未正常启动"
      }
    }
  }
}

其中storage节点描述了自定义的HealthIndicator,此时显示系统未正常启动,通过接口启动服务

curl http://localhost:8080/storage/status?status=running

再次访问/my-actuator/health,此时系统正常运行

{
  "status": "UP",
  "components": {
    "storage": {
      "status": "UP"
    }
  }
}

认为设置自检服务第二步加密失败

curl http://localhost:8080/storage/status?status=stoping&point=encrypt

再次访问/my-actuator/health,此时系统宕机,且提示加密模块出现问题

{
  "status": "DOWN",
  "components": {
    "storage": {
      "status": "DOWN",
      "details": {
        "encrypt": "图书资源加密失败"
      }
    }
  }
}
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,319评论 5 459
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,801评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,567评论 0 319
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,156评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,019评论 4 355
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,090评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,500评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,192评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,474评论 1 290
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,566评论 2 309
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,338评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,212评论 3 312
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,572评论 3 298
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,890评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,169评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,478评论 2 341
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,661评论 2 335

推荐阅读更多精彩内容