• 甲方不让用开源【监控软件】?大不了我自己写一个


    相信在公司的小伙伴,有很多都经历过这样一种情况:公司对于某些开源软件禁止使用,或者说需要修改源码,去掉其logo或者身份信息,各种监控软件就是其受害者。对于较大的系统,或者组件较多的系统,拥有一个可靠的监控系统将是非常有必要的。

    warriors体验地址:http://122.112.181.245/

    背景

    之前我曾就职于一家面向三大运营商的传统行业公司,所有的系统都是内网部署,且对于第三方的开源软件并不完全提倡使用。

    比如springboot自带的监组件springboot-admin,在使用的时候,我还专门去修改器源码,将logo替换为自己公司的logo,重新打包部署,说实话,挺痛苦的。而对于我一直比较推崇的skywalkingprometheus,根本就不给你部署的机会。

    但是一个好的指标监控系统,对于项目的平稳运行还是能起到很关键的作用的。所以虽然不在之前的公司了,但是我始终有一个搭建自己的监控体系的想法,可以按照公司的需要自行接入,而与原本的系统做到毫无违和。

    简介

    鉴于前面提到的背景,我在休息的时间,缓慢的开始了我自己的监控系统的建设,因为我是库里的球迷,所以暂时将此监控系统命名为勇士监控系统,即warriors,系统的色调,也采用了勇士蓝黄组合。

    勇士监控系统提供各种常用组件的接入,因为业余时间较少,目前仅仅接入了redis,作为给大家的演示。提供组件的各种常用指标,以报表的形式给用户提供直观的展示。

    界面介绍

    目前主要提供两个界面,首页简介,以及具体的监控页面。

    • 首页

      首页主要简单的为大家介绍勇士监控系统:

      此界面可以手动原则将要查看的组件性能信息:

    • 监控界面

      选择"redis"后,即进入redis监控页面,页面将会对具体的指标信息进行展示,包含但不限于以下的指标,可以随时添加新的指标。

    架构介绍

    勇士监控系统以简单为主,只有一个服务,组件的监控代码通过stater的形式进行引入,整体架构如下所示:

    如上图所示,整个监控系统由四部分组成,由上至下分别是:

    • ui界面:采用vue3实现
    • 服务端:采用springboot实现
    • 组件依赖:通过stater定义不同组件的不同监控指标
    • 基础依赖:提供各组件需要的基础类

    实现

    后台实现

    基于简单的架构,组件的实现也非常的简单。代码结构如下所示:

    上图当中还包含了代码生成器的部分,方便我们快速开发。

    对于常见的一些组件,比如redis,我们可以通过它自带的api去完成对其性能的监控,我这里使用的是RedisTemplate,一起简单看下其实现:

    • 首选,看下公共依赖都包含什么?

      如上图所示,从上至下分别是:

      • 常量:公共常量
      • 枚举:返回枚举
      • 工具类:bean copye工具类,分页对象,返回结果

      需要特别关注的spring.factories,它是stater能够实现的核心配置,后面单独讲解。

    • 引入redis依赖

      1. <dependency>
      2. <groupId>org.springframework.bootgroupId>
      3. <artifactId>spring-boot-starter-data-redisartifactId>
      4. dependency>
      5. <dependency>
      6. <groupId>org.apache.commonsgroupId>
      7. <artifactId>commons-pool2artifactId>
      8. dependency>
      9. <dependency>
      10. <groupId>com.wjbgn.warriorsgroupId>
      11. <artifactId>warriors-base-starterartifactId>
      12. dependency>
      13. 复制代码
    • 看看redis-stater包含什么?

      如上图所示,从上直线分别是:

      • redis配置文件:用来方便redis操作实例化对象:

        1. /**
        2. * description: 默认情况下RedisTemplate模板只能支持字符串,我们自定义一个RedisTemplate,
        3. * 设置序列化器,这样我们可以很方便的操作实例对象。
        4. *
        5. * @return:
        6. * @author: weirx
        7. * @time: 2021/3/26 13:58
        8. */
        9. @Configuration
        10. public class RedisConfig {
        11. @Bean
        12. public RedisTemplate<String, Object> redisTemplate(LettuceConnectionFactory connectionFactory) {
        13. RedisTemplate<String, Object> redisTemplate = new RedisTemplate<>();
        14. redisTemplate.setKeySerializer(new StringRedisSerializer());
        15. redisTemplate.setValueSerializer(new GenericJackson2JsonRedisSerializer());
        16. redisTemplate.setConnectionFactory(connectionFactory);
        17. return redisTemplate;
        18. }
        19. }
        20. 复制代码
      • 实体类:用来封装监控指标的属性,比如服务信息如下所示:

        1. package com.wjbgn.warriors.redis.entity;
        2. import lombok.Data;
        3. /**
        4. * @description: 服务信息
        5. * @author:weirx
        6. * @date:2022/6/23 17:41
        7. * @version:3.0
        8. */
        9. @Data
        10. public class ServerInfo {
        11. /**
        12. * 版本
        13. */
        14. private String version;
        15. /**
        16. * 模式:单例,集群
        17. */
        18. private String mode;
        19. /**
        20. * 操作系统
        21. */
        22. private String os;
        23. private String port;
        24. /**
        25. * 运行时间
        26. */
        27. private String updateInDays;
        28. /**
        29. * 配置文件路径
        30. */
        31. private String config_path;
        32. }
        33. 复制代码
      • 工具类:redis各类指标采集的工具类,以服务信息的代码为例:

        1. ```java
        2. package com.wjbgn.warriors.redis.util;
        3. import cn.hutool.core.util.ObjectUtil;
        4. import com.wjbgn.warriors.base.util.Result;
        5. import com.wjbgn.warriors.redis.entity.ServerInfo;
        6. import lombok.extern.slf4j.Slf4j;
        7. import org.springframework.stereotype.Component;
        8. import java.util.Arrays;
        9. import java.util.HashMap;
        10. import java.util.List;
        11. import java.util.Map;
        12. import static com.wjbgn.warriors.base.constants.CommonConstants.FLAG;
        13. /**
        14. * @description: redis服务信息
        15. * @author:weirx
        16. * @date:2022/6/23 17:19
        17. * @version:3.0
        18. */
        19. @Slf4j
        20. @Component
        21. public class RedisServerInfoUtil extends AbstractRedisInfoUtil{
        22. /**
        23. * description: 获取服务信息
        24. * @return: com.wjbgn.warriors.base.util.Result
        25. * @author: weirx
        26. * @time: 2022/6/24 10:21
        27. */
        28. public Result serverInfo() {
        29. Object serverInfo = getInfo("server");
        30. if (ObjectUtil.isNotEmpty(serverInfo)) {
        31. String s = serverInfo.toString().replaceAll("[\t\n\r]", FLAG);
        32. ServerInfo parse = this.parse(s);
        33. return Result.success(parse);
        34. }
        35. return Result.failed();
        36. }
        37. /**
        38. * description: 解析服务信息
        39. * @param s
        40. * @return: com.wjbgn.warriors.redis.entity.ServerInfo
        41. * @author: weirx
        42. * @time: 2022/6/24 10:21
        43. */
        44. public ServerInfo parse(String s) {
        45. String[] split = s.split(FLAG + FLAG);
        46. List list = Arrays.asList(split);
        47. ServerInfo serverInfo = new ServerInfo();
        48. Map map = new HashMap<>();
        49. list.stream().forEach(str -> {
        50. String[] strings = str.split(":");
        51. if (strings.length > 1) {
        52. map.put(strings[0], strings[1]);
        53. }
        54. });
        55. map.forEach((k, v) -> {
        56. if (k.equals("redis_version")) {
        57. serverInfo.setVersion(v);
        58. } else if (k.equals("redis_mode")) {
        59. serverInfo.setMode(v);
        60. } else if (k.equals("os")) {
        61. serverInfo.setOs(v);
        62. } else if (k.equals("tcp_port")) {
        63. serverInfo.setPort(v);
        64. } else if (k.equals("uptime_in_days")) {
        65. serverInfo.setUpdateInDays(v);
        66. } else if (k.equals("config_file")) {
        67. serverInfo.setConfig_path(v);
        68. }
        69. });
        70. return serverInfo;
        71. }
        72. }
        73. ```
        74. 复制代码
      • 仍然是很重要的spring.factories

    前台实现

    前台代码的实现非常简单,采用vue3 + element-plus为基础进行开发,标准的vue3结构,如下所示:

    组件实际只引用了:

    • element-plus
    • router
    • echarts
    • axios

    具体监控界面,采用elment-plus的布局:

    1. <el-card>
    2. <el-row>
    3. <el-col :span="4">
    4. Redis版本: {{redisServerInfo.redisVersion}}
    5. el-col>
    6. <el-col :span="4">
    7. 模式: {{redisServerInfo.redisMode}}
    8. el-col>
    9. <el-col :span="4">
    10. 端口: {{redisServerInfo.redisPort}}
    11. el-col>
    12. <el-col :span="4">
    13. 运行时间: {{redisServerInfo.redisUpdateInDays}} 天
    14. el-col>
    15. <el-col :span="4">
    16. 总内存: {{parseFloat((redisMemoryInfo.totalSystemMemory - redisMemoryInfo.usedMemory)/(1024*1024*1024)).toFixed(2) }} G
    17. el-col>
    18. <el-col :span="4">
    19. 客户端: {{redisClientsInfo.connectionClientsNum}}
    20. el-col>
    21. el-row>
    22. el-card>
    23. 复制代码

    使用echarts实现报表的展示:

    1. function drawHistogram(){
    2. // 基于准备好的dom,初始化echarts实例
    3. let myChart = echarts.init(document.getElementById('histogram'));
    4. // 绘制图表
    5. myChart.setOption({
    6. title: {
    7. text: '客户端信息'
    8. },
    9. tooltip: {},
    10. xAxis: {
    11. data: ['连接数','集群数','阻塞数','超时数']
    12. },
    13. yAxis: {},
    14. series: [
    15. {
    16. name: '',
    17. type: 'bar',
    18. data: [
    19. redisClientsInfo.connectionClientsNum,
    20. redisClientsInfo.clusterConnections,
    21. redisClientsInfo.blockedClients,
    22. redisClientsInfo.clientsInTimeoutTable
    23. ]
    24. }
    25. ]
    26. });
    27. }
    28. 复制代码

    spring.factories是什么?

    前面提到的spring.factories,单独拿出一部分来讲解。其内部的构成如下所示:

    1. org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
    2. com.wjbgn.warriors.redis.util.RedisSlowLogUtil,\
    3. com.wjbgn.warriors.redis.util.RedisServerInfoUtil,\
    4. com.wjbgn.warriors.redis.util.RedisClientsInfoUtil,\
    5. com.wjbgn.warriors.redis.util.RedisMemoryInfoUtil,\
    6. com.wjbgn.warriors.redis.util.RedisPersistenceInfoUtil,\
    7. com.wjbgn.warriors.redis.util.RedisStatsInfoUtil
    8. 复制代码

    如上所示,在第一行的类:EnableAutoConfiguration,相信熟悉springboot的小伙伴都很熟悉,它是sopringboot自动装载配置的注解。所以我们能够知道,这个文件就是配合自动装配进行使用的。

    在向下,则是配置文件,和我们定义的一系列的redis指标获取工具类。我们可以根据自己的项目需要进行配置制定的util。

    不配置spring.factories行吗?

    答案是**可以配置,可以不配置*。

    但是需要分为两种情况:

    • 不需要配置:工具类和依赖它的服务在同一个包名下。
    • 需要配置:工具类和依赖它的服务不在同一个包名下。

    造成这种问题的本质原因是什么呢?

    比如warriors这个系统,我完全可以不进行配置,因为我的包路径,全部都是com.wjbgn.warriors

    我们需要知道的是,springboot的启动类,在启动的时候,会默认扫描和其同一级包名,和其包名下的类,这些类需要带有指定的注解,比如:@Service@Component等等。所以即使不指定spring.factories,也可以扫描到我们定义的配置文件和工具类,冰江他们注入到的spring容器当中,供我们直接使用。

    但是当我们的redis-starter是在其他的服务当中被依赖了呢?

    他们想要使用此starter去做一些定制化的开发,那么就必须要配置spring.factories

    spring.factoriesEnableAutoConfiguration相互配合,从而将其需要的bean进行自动配置。

    上面提到的这种方式,我们可以称之为SPI,(Service Provider Interface)

    总结

    到此为止,warriors监控就介绍完成了,无论是使用的技术,还是实现方案,都是我们经常使用,并且很实用的方式,能够尽量简单的完成自定义监控系统的研发。也非常适合初学者小伙伴上手去学习java相关的知识,同时能够使你更加清晰的认识各种组件的性能指标

    目前还处于开发阶段,进度取决于我的空闲时间,但是后续的规划还是有的:

    • 接入邮件等告警模块。
    • 实现页面的动态配置方式。
    • 完善更多的组件监控。

    相比于市面大多数的监控软件,warriors还是一个襁褓里的孩子。但是意义在于给大家提供一个监控的最简单思路,让你能够快速的打造完全属于公司自己的监控体系。

     

  • 相关阅读:
    binder hwbinder vndbinder
    写给 Java 程序员的前端 Promise 教程
    Centos 7 安装 Docker Enginee
    Linux上docker部署Mysql备份与恢复
    Mqtt入门:在线调试连接阿里云
    集合_HashMap_初始容量为什么是2的n次方数
    【Spring boot 普通类调用 Bean】
    【算法】记忆化搜索
    自动化测试解决了什么问题,看看这些行业大牛给出的回答
    使用容器运行nginx及docker命令介绍
  • 原文地址:https://blog.csdn.net/YYniannian/article/details/126134723