springboot admin+nacos+钉钉机器人实现服务监控

nacos做为注册中心和配置中心
springboot微服务
集成springboot admin做服务监控
钉钉机器人及时推送服务下线、上线通知

  • 摘要
    Spring Boot Admin是一个开源社区项目,用于管理和监控SpringBoot应用程序。 应用程序作为Spring Boot Admin Client向为Spring Boot Admin Server注册(通过HTTP)或使用SpringCloud注册中心(平台使用nacos)发现。 UI是的Vue.js应用程序,展示Spring Boot Admin Client的Actuator端点上的一些监控。服务端采用Spring WebFlux + Netty的方式。Spring Boot Admin为注册的应用程序提供以下功能:

显示健康状况
显示详细信息,例如
JVM和内存指标
micrometer.io指标
数据源指标
缓存指标
显示构建信息编号
关注并下载日志文件
查看jvm system-和environment-properties
查看Spring Boot配置属性
支持Spring Cloud的postable / env-和/ refresh-endpoint
轻松的日志级管理
与JMX-beans交互
查看线程转储
查看http-traces
查看auditevents
查看http-endpoints
查看计划任务
查看和删除活动会话(使用spring-session)
查看Flyway / Liquibase数据库迁移
下载heapdump
状态变更通知(使用了钉钉报警)
状态更改的事件日志(非持久性)
在这里插入图片描述

  • 1:首选搭建springboot admin服务

pom.xml 关键依赖如下:

<dependencies>
<!--  服务发现(注册中心) -->
        <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
            <version>2.1.0.RELEASE</version>
            <exclusions>
                <exclusion>
                    <groupId>com.google.guava</groupId>
                    <artifactId>guava</artifactId>
                </exclusion>
                <exclusion>
                    <groupId>com.netflix.archaius</groupId>
                    <artifactId>archaius-core</artifactId>
                </exclusion>
            </exclusions>
        </dependency>
        <!-- nacos配置中心 依赖 -->
        <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
            <version>2.1.0.RELEASE</version>
        </dependency>
        <dependency>
            <groupId>de.codecentric</groupId>
            <artifactId>spring-boot-admin-starter-server</artifactId>
            <version>2.1.4</version>
        </dependency>
<!--        <dependency>-->
<!--            <groupId>de.codecentric</groupId>-->
<!--            <artifactId>spring-boot-admin-server-ui</artifactId>-->
<!--            <version>2.1.4</version>-->
<!--        </dependency>-->

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

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

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

        <dependency>
            <groupId>com.deepblue.cloud</groupId>
            <artifactId>unity-common-lang</artifactId>
            <version>${unity.common.version}</version>
            <exclusions>
                <exclusion>
                    <groupId>de.codecentric</groupId>
                    <artifactId>spring-boot-admin-starter-client</artifactId>
                </exclusion>
                <exclusion>
                    <groupId>org.springframework.boot</groupId>
                    <artifactId>spring-boot-starter-web</artifactId>
                </exclusion>
            </exclusions>
        </dependency>

        <dependency>
            <groupId>log4j</groupId>
            <artifactId>log4j</artifactId>
            <version>1.2.16</version>
            <scope>compile</scope>
        </dependency>

        <dependency>
            <groupId>org.slf4j</groupId>
            <artifactId>slf4j-api</artifactId>
            <version>${slf4j.version}</version>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <version>${lombok.version}</version>
        </dependency>

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

    </dependencies>

注意:spring-boot-starter-parent为2.1.0.RELEASE版本,spring-cloud-starter-alibaba-nacos-discovery版本和其版本要一致,nacos官网有说明。

2.关键代码如下:
DingTalkNotifier.java

@Slf4j
public class DingTalkNotifier extends AbstractStatusChangeNotifier {


  @Autowired
  private AlarmDingTalkRobotClient alarmDingTalkRobotClient;
  @Autowired
  private NacosConfigService nacosConfigService;
  /**
   * 消息模板
   */
  private static final String template = "<<<%s>>> \n 【服务名】: %s(%s) \n 【状态】: %s(%s) \n 【服务ip】: %s \n 【详情】: %s";

  private String titleAlarm = "系统告警";

  private String titleNotice = "系统通知";

  private String[] ignoreChanges = new String[]{"UNKNOWN:UP","DOWN:UP","OFFLINE:UP"};

  public DingTalkNotifier(InstanceRepository repository) {
    super(repository);
  }

  @Override
  protected boolean shouldNotify(InstanceEvent event, Instance instance) {
    if (!(event instanceof InstanceStatusChangedEvent)) {
      return false;
    } else {
      InstanceStatusChangedEvent statusChange = (InstanceStatusChangedEvent)event;
      String from = this.getLastStatus(event.getInstance());
      String to = statusChange.getStatusInfo().getStatus();
      return Arrays.binarySearch(this.ignoreChanges, from + ":" + to) < 0 && Arrays.binarySearch(this.ignoreChanges, "*:" + to) < 0 && Arrays.binarySearch(this.ignoreChanges, from + ":*") < 0;
    }
  }

  @Override
  protected Mono<Void> doNotify(InstanceEvent event, Instance instance) {

    return Mono.fromRunnable(() -> {
      if (!nacosConfigService.getIsopen()){
        return;
      }
      String name=instance.getRegistration().getName();
      String watchapplications = nacosConfigService.getWatchapplications();
      Boolean flag=watchapplications.contains(instance.getRegistration().getName());
      if (!flag){
        return;
      }
      if (event instanceof InstanceStatusChangedEvent) {
        log.info("Instance {} ({}) is {}", instance.getRegistration().getName(),
            event.getInstance(),
            ((InstanceStatusChangedEvent) event).getStatusInfo().getStatus());

        String status = ((InstanceStatusChangedEvent) event).getStatusInfo().getStatus();
        String messageText = null;
        switch (status) {
          // 健康检查没通过
          case "DOWN":
            log.info("发送 健康检查没通过 的通知!");
            messageText = String
                .format(template,titleAlarm, instance.getRegistration().getName(), event.getInstance(),
                    ((InstanceStatusChangedEvent) event).getStatusInfo().getStatus(), "健康检查没通过",
                    instance.getRegistration().getServiceUrl(), JSONObject.toJSONString(instance.getStatusInfo().getDetails()));
            alarmDingTalkRobotClient.sendMarkdownMessage(titleAlarm, messageText, true);
            break;
          // 服务离线
          case "OFFLINE":
            log.info("发送 服务离线 的通知!");
            messageText = String
                .format(template,titleAlarm, instance.getRegistration().getName(), event.getInstance(),
                    ((InstanceStatusChangedEvent) event).getStatusInfo().getStatus(), "服务离线",
                    instance.getRegistration().getServiceUrl(), JSONObject.toJSONString(instance.getStatusInfo().getDetails()));
            alarmDingTalkRobotClient.sendMarkdownMessage(titleAlarm, messageText, true);
            break;
          //服务上线
          case "UP":
            log.info("发送 服务上线 的通知!");
            messageText = String
                .format(template,titleNotice, instance.getRegistration().getName(), event.getInstance(),
                    ((InstanceStatusChangedEvent) event).getStatusInfo().getStatus(), "服务上线",
                    instance.getRegistration().getServiceUrl(), JSONObject.toJSONString(instance.getStatusInfo().getDetails()));
            alarmDingTalkRobotClient.sendMarkdownMessage(titleNotice, messageText, true);
            break;
          // 服务未知异常
          case "UNKNOWN":
            log.info("发送 服务未知异常 的通知!");
            messageText = String
                .format(template,titleAlarm, instance.getRegistration().getName(), event.getInstance(),
                    ((InstanceStatusChangedEvent) event).getStatusInfo().getStatus(), "服务未知异常",
                    instance.getRegistration().getServiceUrl(), JSONObject.toJSONString(instance.getStatusInfo().getDetails()));
            alarmDingTalkRobotClient.sendMarkdownMessage(titleAlarm, messageText, true);
            break;
          default:
            break;
        }
      } else {
        log.info("Instance {} ({}) {}", instance.getRegistration().getName(), event.getInstance(),
            event.getType());
      }
    });
  }


}

注意:一定要重写shouldNotify得方法,并自定义ignoreChanges,因为AbstractStatusChangeNotifier中得ignoreChanges只有"UNKNOWN:UP"得状态,如果不重写改方法,会导致服务监控不到新上线得服务,无法发送上线服务通知到钉钉机器人得问题。 其中AlarmDingTalkRobotClient类是钉钉报警自己封装得类,这里不做详细描述,大家可以根据自己业务需求,自行封装。

NacosWatch.java

@Slf4j
public class NacosWatch implements ApplicationEventPublisherAware, SmartLifecycle {

  /**
   * watch delay,duration to pull new service from nacos server.
   */
  private long watchDelay = 30000;

  private final NacosDiscoveryProperties properties;
  private final TaskScheduler taskScheduler;
  private final AtomicLong nacosWatchIndex;
  private final AtomicBoolean running;
  private ApplicationEventPublisher publisher;
  private ScheduledFuture<?> watchFuture;
  private Set<String> cacheServices;
  private HashMap<String, EventListener> subscribeListeners;

  public NacosWatch(NacosDiscoveryProperties properties) {
    this(properties, getTaskScheduler());
  }

  public NacosWatch(NacosDiscoveryProperties properties, TaskScheduler taskScheduler) {
    this.nacosWatchIndex = new AtomicLong(0L);
    this.running = new AtomicBoolean(false);
    this.cacheServices = new HashSet();
    this.subscribeListeners = new HashMap();
    this.properties = properties;
    this.taskScheduler = taskScheduler;
  }

  private static ThreadPoolTaskScheduler getTaskScheduler() {
    ThreadPoolTaskScheduler taskScheduler = new ThreadPoolTaskScheduler();
    taskScheduler.initialize();
    return taskScheduler;
  }

  @Override
  public void setApplicationEventPublisher(ApplicationEventPublisher publisher) {
    this.publisher = publisher;
  }

  @Override
  public boolean isAutoStartup() {
    return true;
  }

  @Override
  public void stop(Runnable callback) {
    this.stop();
    callback.run();
  }

  @Override
  public void start() {
    if (this.running.compareAndSet(false, true)) {
      this.watchFuture =
          this.taskScheduler.scheduleWithFixedDelay(this::nacosServicesWatch, watchDelay);
    }

  }

  @Override
  public void stop() {
    if (this.running.compareAndSet(true, false) && this.watchFuture != null) {
      this.watchFuture.cancel(true);
    }

  }

  @Override
  public boolean isRunning() {
    return false;
  }

  @Override
  public int getPhase() {
    return 0;
  }

  public void nacosServicesWatch() {
    try {
      boolean changed = false;
      NamingService namingService = this.properties.namingServiceInstance();
      ListView<String>
          listView = this.properties.namingServiceInstance().getServicesOfServer(1, 2147483647);
      List<String> serviceList = listView.getData();
      Set<String> currentServices = new HashSet(serviceList);
      currentServices.removeAll(this.cacheServices);
      if (currentServices.size() > 0) {
        changed = true;
      }

      Iterator var6;
      String serviceName;
      if (this.cacheServices.removeAll(new HashSet(serviceList)) && this.cacheServices.size() > 0) {
        changed = true;
        var6 = this.cacheServices.iterator();

        while (var6.hasNext()) {
          serviceName = (String) var6.next();
          namingService
              .unsubscribe(serviceName, (EventListener) this.subscribeListeners.get(serviceName));
          this.subscribeListeners.remove(serviceName);
        }
      }

      this.cacheServices = new HashSet(serviceList);
      var6 = this.cacheServices.iterator();

      while (var6.hasNext()) {
        serviceName = (String) var6.next();
        if (!this.subscribeListeners.containsKey(serviceName)) {
          EventListener eventListener = (event) -> {
            this.publisher
                .publishEvent(new HeartbeatEvent(this, this.nacosWatchIndex.getAndIncrement()));
          };
          this.subscribeListeners.put(serviceName, eventListener);
          namingService.subscribe(serviceName, eventListener);
        }
      }

      if (changed) {
        this.publisher
            .publishEvent(new HeartbeatEvent(this, this.nacosWatchIndex.getAndIncrement()));
      }
    } catch (Exception var9) {
      log.error("Error watching Nacos Service change", var9);
    }

  }
}

注意:就版本得nacos中可能没有监听服务上线通知,需要自己本地写一下NacosWatch类,来监听服务上线。eureka和console作为注册中心是有该实现得。

NacosWatchAutoConfiguration.java


@Configuration
public class NacosWatchAutoConfiguration {
  @Bean
  @ConditionalOnMissingBean
  @ConditionalOnProperty(value = "spring.cloud.nacos.discovery.watch.enabled", matchIfMissing = true)
  public NacosWatch nacosWatchDeepBlue(NacosDiscoveryProperties nacosDiscoveryProperties) {
    return new NacosWatch(nacosDiscoveryProperties);
  }

}

AdminApplication.java

@SpringBootApplication
@EnableDiscoveryClient
@EnableAdminServer
@EnableFeignClients
public class AdminApplication {

  @Bean
  public DingTalkNotifier dingDingNotifier(InstanceRepository repository) {
    return new DingTalkNotifier(repository);
  }

  /**
   * 启动的时候要注意,由于我们在controller中注入了RestTemplate,所以启动的时候需要实例化该类的一个实例
   */
  @Autowired
  private RestTemplateBuilder builder;

  /**
   *使用RestTemplateBuilder来实例化RestTemplate对象,spring默认已经注入了RestTemplateBuilder实例
   */
  @Bean
  public RestTemplate restTemplate() {
    return builder.build();
  }

  public static void main(String[] args) {
    SpringApplication.run(AdminApplication.class, args);
  }

}

其他代码为实现钉钉机器人得相关代码,这里不做过多描述。

在这里插入图片描述
在这里插入图片描述

  • 0
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 5
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值