springboot actuator 自定义健康检查和端口

什么是Actuator

spring boot actuator模块提供了生产级别的功能,比如健康检查、审计、指标收集、http跟踪等,帮助我们监控和管理spring boot应用。这个模块是一个采集应用内部信息暴露给外部的模块,上述的功能都可以通过HTTP和JMX访问。

因为暴露内部信息的特征,Actuator也可以和一些外部的应用监控系统整合(prometheus、graphite、apollo、nacos等)

集成Actuator

只要加上了下列包,springboot 在运行时会自动开启/actuator/health和/actuator/info这两个endpoint

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

访问http://localhost:8081/actuator/health

{
    "status": "UP",
    "components": {
        "db": {
            "status": "UP",
            "details": {
                "database": "MySQL",
                "validationQuery": "select 1",
                "result": 1
            }
        },
        "discoveryComposite": {
            "status": "UP",
            "components": {
                "discoveryClient": {
                    "status": "UP",
                    "details": {
                        "services": [
                            "service-member"
                        }
                    }
                }
            }
        },
        "hystrix": {
            "status": "UP"
        },
        "livenessState": {
            "status": "UP"
        },
        "readinessState": {
            "status": "UP"
        },
        "tomcat": {
            "status": "UP",
            "details": {
                "threadPool": {
                    "currentThreadsBusy": 0,
                    "currentThreadCount": 10,
                    "maxThreads": 200
                }
            }
        }
    },
    "groups": [
        "liveness",
        "readiness"
    ]
}

访问http://localhost:8081/actuator暴露出来的端点

{
    "_links": {
        "self": {
            "href": "http://localhost:8081/actuator",
            "templated": false
        },
        "archaius": {
            "href": "http://localhost:8081/actuator/archaius",
            "templated": false
        },
        "start": {
            "href": "http://localhost:8081/actuator/start",
            "templated": false
        },
        "beans": {
            "href": "http://localhost:8081/actuator/beans",
            "templated": false
        },
        "caches": {
            "href": "http://localhost:8081/actuator/caches",
            "templated": false
        },
        "caches-cache": {
            "href": "http://localhost:8081/actuator/caches/{cache}",
            "templated": true
        },
        "health": {
            "href": "http://localhost:8081/actuator/health",
            "templated": false
        },
        "health-path": {
            "href": "http://localhost:8081/actuator/health/{*path}",
            "templated": true
        },
        "info": {
            "href": "http://localhost:8081/actuator/info",
            "templated": false
        },
        "conditions": {
            "href": "http://localhost:8081/actuator/conditions",
            "templated": false
        },
        "shutdown": {
            "href": "http://localhost:8081/actuator/shutdown",
            "templated": false
        },
        "configprops": {
            "href": "http://localhost:8081/actuator/configprops",
            "templated": false
        },
        "env-toMatch": {
            "href": "http://localhost:8081/actuator/env/{toMatch}",
            "templated": true
        },
        "env": {
            "href": "http://localhost:8081/actuator/env",
            "templated": false
        },
        "loggers": {
            "href": "http://localhost:8081/actuator/loggers",
            "templated": false
        },
        "loggers-name": {
            "href": "http://localhost:8081/actuator/loggers/{name}",
            "templated": true
        },
        "heapdump": {
            "href": "http://localhost:8081/actuator/heapdump",
            "templated": false
        },
        "threaddump": {
            "href": "http://localhost:8081/actuator/threaddump",
            "templated": false
        },
        "prometheus": {
            "href": "http://localhost:8081/actuator/prometheus",
            "templated": false
        },
        "metrics-requiredMetricName": {
            "href": "http://localhost:8081/actuator/metrics/{requiredMetricName}",
            "templated": true
        },
        "metrics": {
            "href": "http://localhost:8081/actuator/metrics",
            "templated": false
        },
        "scheduledtasks": {
            "href": "http://localhost:8081/actuator/scheduledtasks",
            "templated": false
        },
        "mappings": {
            "href": "http://localhost:8081/actuator/mappings",
            "templated": false
        },
        "refresh": {
            "href": "http://localhost:8081/actuator/refresh",
            "templated": false
        },
        "features": {
            "href": "http://localhost:8081/actuator/features",
            "templated": false
        },
        "service-registry": {
            "href": "http://localhost:8081/actuator/service-registry",
            "templated": false
        }
    }
}

Endpoints介绍

spring boot提供了所谓的endpoints(下文翻译为端点)给外部来与应用程序进行访问和交互
eg:/health端点提供了关于应用健康情况的一些基础信息。metrics端点提供了一些有用的应用程序指标(JVM内存使用、系统cpu使用等)

这些Actuator 模块本来就有的端点我们称之为原生端点。根据端点的作用

  • 应用配置类:获取应用程序中加载的应用配置、环境变量、自动化配置报告等与Spring Boot应用密切相关的配置类信息。
  • 度量指标类:获取应用程序运行过程中用于监控的度量指标,比如:内存信息、线程池信息、HTTP请求统计等。
  • 操作控制类:提供了对应用的关闭等操作类功能。

端点配置

#指定端口号
management.server.port=8081
#同时可以将health修改成healthcheck
management.endpoints.web.path-mapping.health=healthcheck
#這樣寫的話,原本內建的/actuator/xxx路徑,都會變成/manage/xxx,可以用來防止被其他人猜到
management.endpoints.web.base-path=/manage
# 可以這樣寫,就會開啟所有endpoints(不包含shutdown)
management.endpoints.web.exposure.include=*
# 也可以這樣寫,就只會開啟指定的endpoint,因此此處只會再額外開啟/actuator/beans和/actuator/mappings
management.endpoints.web.exposure.include=beans,mappings

# exclude可以用來關閉某些endpoints
# exclude通常會跟include一起用,就是先include了全部,然後再exclude /actuator/beans這個endpoint
management.endpoints.web.exposure.exclude=beans
management.endpoints.web.exposure.include=*

# 如果要開啟/actuator/shutdown,要額外再加這一行
management.endpoint.shutdown.enabled=true

自定义端口号

package com.xxxxxx.starter.autoconfigure.config;

import com.ctrip.framework.apollo.util.ConfigUtil;
import com.xxxxxx.starter.autoconfigure.health.ApolloHealthIndicator;
import com.xxxxxx.starter.autoconfigure.health.NacosHealthIndicator;
import com.xxxxxx.starter.autoconfigure.health.StartEndpoint;
import com.xxxxxx.starter.gray.core.config.NacosAutoGrayRuleConfigUpdater;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.env.ConfigurableEnvironment;

import javax.annotation.PostConstruct;

@Slf4j
@Configuration(proxyBeanMethods = false)
@ConditionalOnClass(ConfigUtil.class)
@ConditionalOnProperty(name = "xxxxxx.starter.health.enabled", havingValue = "true",
    matchIfMissing = true)
public class HealthIndicatorConfiguration {

    private final ApplicationContext applicationContext;
    @Autowired
    private  ConfigurableEnvironment environment;


    public HealthIndicatorConfiguration(ApplicationContext applicationContext) {
        this.applicationContext = applicationContext;
    }

    @PostConstruct
    public void init() {
        if (log.isInfoEnabled()) {
            log.info("ApolloHealthIndicatorConfiguration init completed");
        }
    }

//    @Bean
//    @ConditionalOnMissingBean(ApolloHealthIndicator.class)
//    public ApolloHealthIndicator apolloHealthIndicator() {
//        return new ApolloHealthIndicator();
//    }


    @Value("${gray.config.nacos-namespace.date-id:xxxxxx-go}")
    private String dateId;
    @Value("${gray.config.nacos-namespace.group:gray-dev}")
    private String group;

    @Value("${gray.config.nacos-namespace:01269cd6-7f0e-41a2-a492-18783d0075ee}")
    private String nacosNameSpace;

    @Value("${gray.config.nacos-user-name:nacos}")
    private String nacosUser;

    @Value("${gray.config.nacos-password:nacos}")
    private String nacosPassword;

    @Value("${gray.config.nacos-server:10.6.4.124:8848}")
    private String nacosServer;

    @Bean
    @ConditionalOnMissingBean(ApolloHealthIndicator.class)
    public StartEndpoint startEndpoint() {
        ApolloHealthIndicator apolloHealthIndicator = new ApolloHealthIndicator(environment);
        String nameSpace =  environment.getProperty("gray.config.namespace");
        nameSpace = StringUtils.isEmpty(nameSpace) ? "01269cd6-7f0e-41a2-a492-18783d0075ee" : nameSpace ;
        NacosHealthIndicator nacosHealthIndicator = new NacosHealthIndicator(dateId, group, nacosNameSpace, nacosUser, nacosPassword, nacosServer);
        return new StartEndpoint(applicationContext, apolloHealthIndicator, nacosHealthIndicator, environment);
    }

}

package com.xxxxxx.starter.autoconfigure.health;

import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
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.boot.actuate.endpoint.web.WebEndpointResponse;
import org.springframework.boot.actuate.health.Health;
import org.springframework.boot.actuate.health.HealthComponent;
import org.springframework.boot.actuate.health.HealthIndicator;
import org.springframework.boot.actuate.health.Status;
import org.springframework.context.ApplicationContext;
import org.springframework.core.env.ConfigurableEnvironment;

import java.util.ArrayList;
import java.util.List;
import java.util.Map;


@Endpoint(id = "start")
@Slf4j
public class StartEndpoint {
    private final ApplicationContext applicationContext;
    private final ApolloHealthIndicator apolloHealthIndicator;
    private final  NacosHealthIndicator nacosHealthIndicator;

    private final ConfigurableEnvironment environment;

    public StartEndpoint(ApplicationContext applicationContext,ApolloHealthIndicator apolloHealthIndicator
                         ,NacosHealthIndicator nacosHealthIndicator,ConfigurableEnvironment environment) {
        this.applicationContext = applicationContext;
        this.apolloHealthIndicator = apolloHealthIndicator;
        this.nacosHealthIndicator = nacosHealthIndicator;
        this.environment = environment;
    }
    @ReadOperation
    public WebEndpointResponse<HealthComponent> getStartHealth() {
        return checkHealth();
    }

    public WebEndpointResponse<HealthComponent> checkHealth() {
        List<HealthIndicator> allHealthIndicators = getAllHealthIndicators();
        for (HealthIndicator healthIndicator : allHealthIndicators) {
            Health health = healthIndicator.health();

            log.info("health is {},class is {}",health.getDetails(), healthIndicator.getClass());
            boolean healthBoolean = health.getStatus().equals(Status.DOWN);
            log.info("healthStatus is {},healthBoolean is {}",health.getStatus(),healthBoolean);
            if (healthBoolean) {
                return new WebEndpointResponse(health, 503);
            }
        }

        String nacosProperty = environment.getProperty("xxxxxx.starter.nacos.health.enabled");
        if (StringUtils.isEmpty(nacosProperty) || "true".equals(nacosProperty)) {
            Health nacosHealth = nacosHealthIndicator.health();
            log.info("nacosHealth is {},nacosProperty is {}", nacosHealth ,nacosProperty);
            boolean nacosHealthBoolean = nacosHealth.getStatus().equals(Status.DOWN);
            log.info("nacosHealthStatus is {},nacosHealthBoolean is {}", nacosHealth.getStatus(), nacosHealthBoolean);
            if (nacosHealthBoolean) {
                return new WebEndpointResponse(nacosHealthBoolean, 503);
            }
        }

        String apolloProperty = environment.getProperty("xxxxxx.starter.apollo.health.enabled");
        if (StringUtils.isEmpty(apolloProperty) || "true".equals(apolloProperty)) {
            Health apolloHealth = apolloHealthIndicator.health();
            log.info("apolloHealth is {},apolloProperty is {}", apolloHealth, apolloProperty);
            boolean apolloHealthBoolean = apolloHealth.getStatus().equals(Status.DOWN);
            log.info("apolloHealthStatus is {},apolloHealthBoolean is {}", apolloHealth.getStatus(), apolloHealthBoolean);
            if (apolloHealthBoolean) {
                return new WebEndpointResponse(apolloHealthBoolean, 503);
            }
        }

        return new WebEndpointResponse(Health.up().build(), 200);
    }

    public List<HealthIndicator> getAllHealthIndicators() {
        List<HealthIndicator> healthIndicators = new ArrayList<>();

        Map<String, HealthIndicator> beansOfType = applicationContext.getBeansOfType(HealthIndicator.class);

        for (HealthIndicator healthIndicator : beansOfType.values()) {
            // 排除自定义的 HealthIndicatorProvider 类本身
            healthIndicators.add(healthIndicator);
        }

        return healthIndicators;
    }
}

apollo健康检查

package com.xxxxxx.starter.autoconfigure.health;


import com.ctrip.framework.apollo.ConfigService;
import com.ctrip.framework.apollo.build.ApolloInjector;
import com.ctrip.framework.apollo.core.ConfigConsts;
import com.ctrip.framework.apollo.internals.DefaultConfig;
import com.ctrip.framework.apollo.spring.config.PropertySourcesConstants;
import com.ctrip.framework.apollo.util.ConfigUtil;
import com.google.common.base.Joiner;
import com.google.common.base.Splitter;
import com.google.common.base.Strings;
import com.google.common.collect.Maps;
import com.google.common.escape.Escaper;
import com.google.common.net.UrlEscapers;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.actuate.health.Health;
import org.springframework.boot.actuate.health.HealthIndicator;
import org.springframework.core.env.ConfigurableEnvironment;
import org.springframework.web.client.RestTemplate;

import java.util.List;
import java.util.Map;
import java.util.Set;

@Slf4j
public class ApolloHealthIndicator implements HealthIndicator {
    private final RestTemplate restTemplate = new RestTemplate();
    private final ConfigUtil m_configUtil = ApolloInjector.getInstance(ConfigUtil.class);

    private static final Escaper queryParamEscaper = UrlEscapers.urlFormParameterEscaper();

    private static final Joiner.MapJoiner MAP_JOINER = Joiner.on("&").withKeyValueSeparator("=");

    private static final Splitter NAMESPACE_SPLITTER = Splitter.on(",").omitEmptyStrings().trimResults();
    private ConfigurableEnvironment environment;

    public ApolloHealthIndicator(ConfigurableEnvironment environment) {
        this.environment = environment;
    }

//    @Autowired
//    public void setEnvironment(ConfigurableEnvironment environment) {
//        this.environment = environment;
//    }
    @Override
    public Health health() {
        try {
            restTemplate.getForObject(assembleMetaServiceUrl(), String.class);
            String namespaces = environment.getProperty(PropertySourcesConstants.APOLLO_BOOTSTRAP_NAMESPACES, ConfigConsts.NAMESPACE_APPLICATION);
            List<String> namespaceList = NAMESPACE_SPLITTER.splitToList(namespaces);

            for (String namespace : namespaceList) {
                DefaultConfig config = (DefaultConfig) ConfigService.getConfig(namespace);
                Set<String> propertyNames = config.getPropertyNames();
                if (propertyNames.isEmpty()) {
                    log.error("ApolloHealthIndicator namespace is {} empty",namespace);
                    continue;
                    //return Health.down().build();
                } else {
                    return Health.up().build();
                }
            }
            return Health.down().build();
        } catch (Exception e) {
            log.error("health error",e);
            return Health.down(e).build();
        }
    }

    private String assembleMetaServiceUrl() {
        String domainName = m_configUtil.getMetaServerDomainName();
        String appId = m_configUtil.getAppId();
        String localIp = m_configUtil.getLocalIp();

        Map<String, String> queryParams = Maps.newHashMap();
        queryParams.put("appId", queryParamEscaper.escape(appId));
        if (!Strings.isNullOrEmpty(localIp)) {
            queryParams.put("ip", queryParamEscaper.escape(localIp));
        }

        return domainName + "/services/config?" + MAP_JOINER.join(queryParams);
    }
}


nacos健康检查

package com.xxxxxx.starter.autoconfigure.health;

import com.alibaba.nacos.api.NacosFactory;
import com.alibaba.nacos.api.PropertyKeyConst;
import com.alibaba.nacos.api.config.ConfigService;
import com.alibaba.nacos.api.exception.NacosException;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.boot.actuate.health.Health;
import org.springframework.boot.actuate.health.HealthIndicator;

import java.util.Properties;

@Slf4j
public class NacosHealthIndicator implements HealthIndicator {

    private final String dateId;
    private final String group;
    private final String nacosNameSpace;
    private final String nacosUser;
    private final String nacosPassword;
    private final String nacosServer;

    public NacosHealthIndicator(String dateId,String group,String nacosNameSpace,String nacosUser,String nacosPassword,String nacosServer) {
        this.dateId = dateId;
        this.group = group;
        this.nacosNameSpace = nacosNameSpace;
        this.nacosUser = nacosUser;
        this.nacosPassword = nacosPassword;
        this.nacosServer = nacosServer;
    }

    @Override
    public Health health() {
        ConfigService configService = getConfig();
        try {
            String content = configService.getConfig(dateId, group, 3000L);
            if (!StringUtils.isEmpty(content)) {
                return Health.up().build();
            }
        } catch (NacosException e) {
            log.error("NacosHealthIndicator error is ",e);
            return Health.down().build();
        }
        return Health.down().build();
    }

    public ConfigService getConfig() {
        Properties properties = new Properties();
        properties.put(PropertyKeyConst.USERNAME, nacosUser);
        properties.put(PropertyKeyConst.PASSWORD, nacosPassword);
        properties.put(PropertyKeyConst.SERVER_ADDR, nacosServer);
        properties.put(PropertyKeyConst.NAMESPACE, nacosNameSpace);

        log.info("灰度配置信息: data-id:{}, group:{}, namespace:{}", dateId, group, nacosNameSpace);
        try {
            return NacosFactory.createConfigService(properties);
        } catch (NacosException e) {
            log.error("getConfig error",e);
        }
        return null;
    }
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值