springboot集成Admin「自定义告警」

488 阅读3分钟

不久前写过一篇Admin服务监控报警的文章springboot集成Admin「告警通知」

这实现很简单,只需增加继承 AbstractStatusChangeNotifier 配置报警机制就可以。但是这个报警只是简单的服务状态的报警,上线、下线通知,这满足不了我的预警目标。我的目标是实现更详细的报警信息,比如说服务CPU使用情况、Memory使用情况和Thread使用情况等。

使用过Admin的开发者都知道,Admin页面可以看到非常详细的服务状态,CPU、JVM、THREAD等等。本贴主要是在Admin服务端编写自定义定时任务,定时查询每个服务实例的状态,推送服务报警信息。

刚开始的实现思路,因为我的Admin服务端已引入了springboot-admin的pom依赖,完全可以直接调用admin源码去实现,这个思路确实没毛病,也能实现。

翻阅源码后,实现了一半后,发现实现服务实例的详细状态信息,需要构造参数,实现的时间成本比较大。所以换了一个思路,直接使用httpclient调用admin服务接口实现。

如果通过请求接口的方式实现,在未登录的状态下,会直接跳转到登录页面,所以Admin服务端存在登录鉴权,直接调用接口去使用的话,还得去搞一下鉴权问题,回忆一下集成Admin服务端的帖子。

在集成Admin服务端的时候,有一段鉴权的代码

http.authorizeRequests()
                .antMatchers(adminContextPath + "/login",
                        adminContextPath + "/assets/**",
                        adminContextPath + "/manage/**",
                        /*新增鉴权开放接口配置-start*/
                        adminContextPath + "/applications/**",
                        adminContextPath + "/instances/**",
                        /*新增鉴权开放接口配置-end*/
                        adminContextPath + "/actuator/**").permitAll()
                .anyRequest().authenticated()
                .and()
                .formLogin().loginPage(adminContextPath + "/login").successHandler(successHandler)
                .and()
                .logout().logoutUrl(adminContextPath + "/logout")
                .and()
                .csrf().disable();
    }

实现我的预警目标的话,需要调用的接口有:

  • applications/**:获取服务实例列表
  • /instances/**:获取实例详情,包括cpu、memory、thread等数据。
process.cpu.usage进程的cpu使用率
system.cpu.usage系统的cpu使用率
jvm.memory.max最大内存数
jvm.memory.used已使用的内存数
jvm.threads.live当前活跃的线程数量,包括守护线程和非守护线程
jvm.threads.peak自Java虚拟机启动以来的活跃线程数峰值
jvm.threads.states当前处于终止状态的线程数

直接上代码

/**
 * 实现思路:
 * 定时调用springboot-admin接口,解析JSON响应数据
 * 获取服务实例列表
 * 获取每个实例的指标数据
 * 根据自定义的阈值推送服务报警信息
 */

@Configuration
@EnableScheduling
public class SchedulingTask {

    @Resource
    private RestTemplate restTemplate;

    //Admin服务端地址
    private static final String SERVER_HOST = "http://localhost:7001";

    private static final Logger log = LoggerFactory.getLogger(SchedulingTask.class);

    @Scheduled(cron = "0 0/1 * * * ?")
    public void task(){
        String bodyData = getReqBody("/applications");
        JSONArray bodyDataArray = JSONArray.parseArray(bodyData);
        for(int i=0; i<bodyDataArray.size(); i++){
            JSONObject serviceObject = bodyDataArray.getJSONObject(i);
            //服务名称
            String serviceName = serviceObject.getString("name");
            //服务实例列表
            JSONArray instanceArray = (JSONArray) serviceObject.get("instances");
            for(int j=0; j<instanceArray.size(); j++){
                JSONObject instanceObject = instanceArray.getJSONObject(j);
                //服务-实例ID
                String instanceId = instanceObject.getString("id");
                /**
                 * 获取实例CPU、MEMORY、THREAD...
                 * 下面以获取CPU指标数据为例
                 */
                //Java虚拟机进程的“最近cpu使用率”
                BigDecimal processCpuUsage = (BigDecimal) getDetails(instanceId, "process.cpu.usage");
                //整个系统的“最近cpu使用率”
                BigDecimal systemCpuUsage = (BigDecimal) getDetails(instanceId, "system.cpu.usage");
                log.info("serviceName:{},instanceId:{},processCpuUsage:{},systemCpuUsage:{}",
                        serviceName,instanceId,processCpuUsage,systemCpuUsage);
                //自定义阈值
                if(processCpuUsage.doubleValue() > 0.95){
                    log.error("服务CPU使用过高,推送告警邮件");
                    //推送邮件、钉钉、企业微信或公司OA等。
                }
            }
        }
    }
    /**
     * 请求封装方法
     * @param path
     * @return
     */
    private String getReqBody(String path){
        ResponseEntity<String> entity = restTemplate.getForEntity(SERVER_HOST + path, String.class);
        return entity.getBody();
    }

    /**
     * 获取指标数据封装方法
     * @param id
     * @param pathSuffix
     * @return
     */
    public Object getDetails(String id,String pathSuffix){
        /**
         * /instances/id//actuator/metrics/xxx.xxx.xxx
         */
        String bodyData = getReqBody("/instances/"+ id +"/actuator/metrics/" + pathSuffix);
        JSONObject jsonObject = JSONObject.parseObject(bodyData);
        JSONArray measurements = jsonObject.getJSONArray("measurements");
        // 这个数组目前看可能只有一个数据
        for(int i=0; i<measurements.size(); i++){
             return measurements.getJSONObject(i).get("value");
        }
        return null;
    }

}

实现的方式很简单、很粗暴,但是也实现自己想要的结果。

查看一段时间看看效果!!!!!!!!!