SpringBoot Admin使用及心跳检测原理分析

目录
  • 介绍
  • 使用
    • Server端
    • Client端
  • 心跳检测/健康检测原理
    • 原理
    • 调试准备
    • 客户端发起POST请求
    • 服务端定时轮询

介绍

Spring Boot Admin是一个Github上的一个开源项目,它在Spring Boot Actuator的基础上提供简洁的可视化WEB UI,是用来管理 Spring Boot 应用程序的一个简单的界面,提供如下功能:

  • 显示 name/id 和版本号
  • 显示在线状态
  • Logging日志级别管理
  • JMX beans管理
  • Threads会话和线程管理
  • Trace应用请求跟踪
  • 应用运行参数信息,如:

Java 系统属性

Java 环境变量属性

内存信息

Spring 环境属性

Spring Boot Admin 包含服务端和客户端,按照以下配置可让Spring Boot Admin运行起来。

使用

Server端

1、pom文件引入相关的jar包

新建一个admin-server的Spring Boot项目,在pom文件中引入server相关的jar包

   <dependency>
            <groupId>de.codecentric</groupId>
            <artifactId>spring-boot-admin-server</artifactId>
            <version>1.5.3</version>
        </dependency>
        <dependency>
            <groupId>de.codecentric</groupId>
            <artifactId>spring-boot-admin-server-ui</artifactId>
            <version>1.5.3</version>
        </dependency>
        <dependency>
            <groupId>de.codecentric</groupId>
            <artifactId>spring-boot-admin-starter-client</artifactId>
            <version>1.5.3</version>
        </dependency>

其中spring-boot-admin-starter-client的引入是让server本身能够发现自己(自己也是客户端)。

2、 application.yml配置

在application.yml配置如下,除了server.port=8083的配置是server 对外公布的服务端口外,其他配置是server本身作为客户端的配置,包括指明指向服务端的地址和当前应用的基本信息,使用@@可以读取pom.xml的相关配置。

在下面Client配置的讲解中,可以看到下面类似的配置。

server:
  port: 8083
spring:
  boot:
    admin:
      url: http://localhost:8083
info:
  name: server
  description: @project.description@
  version: @project.version@

3、配置日志级别

在application.yml的同级目录,添加文件logback.xml,用以配置日志的级别,包含的内容如下:

<?xml version="1.0" encoding="UTF-8"?>
<configuration>
    <include resource="org/springframework/boot/logging/logback/base.xml"/>
    <logger name="org.springframework.web" level="DEBUG"/>
    <jmxConfigurator/>
</configuration>

在此处配置成了DEBUG,这样可以通过控制台日志查看server端和client端的交互情况。

4、添加入口方法注解

在入口方法上添加@EnableAdminServer注解。

@Configuration
@EnableAutoConfiguration
@EnableAdminServer
public class ServerApplication {
    public static void main(String[] args) {
        SpringApplication.run(ServerApplication.class, args);
    }
}

5、启动项目

启动admin-server项目后,可以看到当前注册的客户端,点击明细,还可以查看其他明细信息。

Client端

在上述的Server端配置中,server本身也作为一个客户端注册到自己,所以client配置同server端配置起来,比较见到如下。

创建一个admin-client项目,在pom.xml添加相关client依赖包。

1、pom.xml添加client依赖

    <dependency>
            <groupId>de.codecentric</groupId>
            <artifactId>spring-boot-admin-starter-client</artifactId>
            <version>1.5.3</version>
        </dependency>

2、application.yml配置

在application.yml配置注册中心地址等信息:

spring:
  boot:
    admin:
      url: http://localhost:8083
info:
  name: client
  description: @project.description@
  version: @project.version@
endpoints:
  trace:
    enabled: true
    sensitive: false

3、配置日志文件

在application.yml的同级目录,添加文件logback.xml,用以配置日志的级别,包含的内容如下:

<?xml version="1.0" encoding="UTF-8"?>
<configuration>
    <include resource="org/springframework/boot/logging/logback/base.xml"/>
    <logger name="org.springframework.web" level="DEBUG"/>
    <jmxConfigurator/>
</configuration>

配置为DEBUG的级别,可以输出和服务器的通信信息,以便我们在后续心跳检测,了解Spring Boot Admin的实现方式。

4、启动Admin-Client应用

启动客户端项目,在服务端监听了客户端的启动,并在页面给出了消息提示,启动后,服务端的界面显示如下:(两个客户端都为UP状态)

以上就可以使用Spring Boot Admin的各种监控服务了,下面谈一谈客户端和服务端怎么样做心跳检测的。

心跳检测/健康检测原理

原理

在Spring Boot Admin中,Server端作为注册中心,它要监控所有的客户端当前的状态。要知道当前客户端是否宕机,刚发布的客户端也能够主动注册到服务端。

服务端和客户端之间通过特定的接口通信(/health接口)通信,来监听客户端的状态。因为客户端和服务端不能保证发布顺序。

有如下的场景需要考虑:

  • 客户端先启动,服务端后启动
  • 服务端先启动,客户端后启动
  • 服务端运行中,客户端下线
  • 客户端运行中,服务端下线

所以为了解决以上问题,需要客户端和服务端都设置一个任务监听器,定时监听对方的心跳,并在服务器及时更新客户端状态。

上文的配置使用了客户端主动注册的方法。

调试准备

为了理解Spring Boot Admin的实现方式,可通过DEBUG 和查看日志的方式理解服务器和客户端的通信(心跳检测)

  • 在pom.xml右键spring-boot-admin-server和spring-boot-admin-starter-client,Maven-> DownLoad Sources and Documentation
  • 在logback.xml中设置日志级别为DEBUG

客户端发起POST请求

客户端相关类

  • RegistrationApplicationListener
  • ApplicationRegistrator

在客户端启动的时候调用RegistrationApplicationListener的startRegisterTask,该方法每隔 registerPeriod = 10_000L,(10秒:默认)向服务端POST一次请求,告诉服务器自身当前是有心跳的。

RegistrationApplicationListener

    @EventListener
    @Order(Ordered.LOWEST_PRECEDENCE)
    public void onApplicationReady(ApplicationReadyEvent event) {
        if (event.getApplicationContext() instanceof WebApplicationContext && autoRegister) {
            startRegisterTask();
        }
    }
    public void startRegisterTask() {
        if (scheduledTask != null && !scheduledTask.isDone()) {
            return;
        }
        scheduledTask = taskScheduler.scheduleAtFixedRate(new Runnable() {
            @Override
            public void run() {
                registrator.register();
            }
        }, registerPeriod);
        LOGGER.debug("Scheduled registration task for every {}ms", registerPeriod);
    }

ApplicationRegistrator

 public boolean register() {
        boolean isRegistrationSuccessful = false;
        Application self = createApplication();
        for (String adminUrl : admin.getAdminUrl()) {
            try {
                @SuppressWarnings("rawtypes") ResponseEntity<Map> response = template.postForEntity(adminUrl,
                        new HttpEntity<>(self, HTTP_HEADERS), Map.class);
                if (response.getStatusCode().equals(HttpStatus.CREATED)) {
                    if (registeredId.compareAndSet(null, response.getBody().get("id").toString())) {
                        LOGGER.info("Application registered itself as {}", response.getBody());
                    } else {
                        LOGGER.debug("Application refreshed itself as {}", response.getBody());
                    }
                    isRegistrationSuccessful = true;
                    if (admin.isRegisterOnce()) {
                        break;
                    }
                } else {
                    if (unsuccessfulAttempts.get() == 0) {
                        LOGGER.warn(
                                "Application failed to registered itself as {}. Response: {}. Further attempts are logged on DEBUG level",
                                self, response.toString());
                    } else {
                        LOGGER.debug("Application failed to registered itself as {}. Response: {}", self,
                                response.toString());
                    }
                }
            } catch (Exception ex) {
                if (unsuccessfulAttempts.get() == 0) {
                    LOGGER.warn(
                            "Failed to register application as {} at spring-boot-admin ({}): {}. Further attempts are logged on DEBUG level",
                            self, admin.getAdminUrl(), ex.getMessage());
                } else {
                    LOGGER.debug("Failed to register application as {} at spring-boot-admin ({}): {}", self,
                            admin.getAdminUrl(), ex.getMessage());
                }
            }
        }
        if (!isRegistrationSuccessful) {
            unsuccessfulAttempts.incrementAndGet();
        } else {
            unsuccessfulAttempts.set(0);
        }
        return isRegistrationSuccessful;
    }

在主要的register()方法中,向服务端POST了Restful请求,请求的地址为/api/applications

并把自身信息带了过去,服务端接受请求后,通过sha-1算法计算客户单的唯一ID,查询hazelcast缓存数据库,如第一次则写入,否则更新。

服务端接收处理请求相关类

RegistryController

    @RequestMapping(method = RequestMethod.POST)
    public ResponseEntity<Application> register(@RequestBody Application application) {
        Application applicationWithSource = Application.copyOf(application).withSource("http-api")
                .build();
        LOGGER.debug("Register application {}", applicationWithSource.toString());
        Application registeredApp = registry.register(applicationWithSource);
        return ResponseEntity.status(HttpStatus.CREATED).body(registeredApp);
    }

ApplicationRegistry

public Application register(Application application) {
        Assert.notNull(application, "Application must not be null");
        Assert.hasText(application.getName(), "Name must not be null");
        Assert.hasText(application.getHealthUrl(), "Health-URL must not be null");
        Assert.isTrue(checkUrl(application.getHealthUrl()), "Health-URL is not valid");
        Assert.isTrue(
                StringUtils.isEmpty(application.getManagementUrl())
                        || checkUrl(application.getManagementUrl()), "URL is not valid");
        Assert.isTrue(
                StringUtils.isEmpty(application.getServiceUrl())
                        || checkUrl(application.getServiceUrl()), "URL is not valid");
        String applicationId = generator.generateId(application);
        Assert.notNull(applicationId, "ID must not be null");
        Application.Builder builder = Application.copyOf(application).withId(applicationId);
        Application existing = getApplication(applicationId);
        if (existing != null) {
            // Copy Status and Info from existing registration.
            builder.withStatusInfo(existing.getStatusInfo()).withInfo(existing.getInfo());
        }
        Application registering = builder.build();
        Application replaced = store.save(registering);
        if (replaced == null) {
            LOGGER.info("New Application {} registered ", registering);
            publisher.publishEvent(new ClientApplicationRegisteredEvent(registering));
        } else {
            if (registering.getId().equals(replaced.getId())) {
                LOGGER.debug("Application {} refreshed", registering);
            } else {
                LOGGER.warn("Application {} replaced by Application {}", registering, replaced);
            }
        }
        return registering;
    }

HazelcastApplicationStore (缓存数据库)

在上述更新状态使用了publisher.publishEvent事件订阅的方式,接受者接收到该事件,做应用的业务处理,在这块使用这种方式个人理解是为了代码的复用性,因为服务端定时轮询客户端也要更新客户端在服务器的状态。

pulishEvent设计到的类有:

  • StatusUpdateApplicationListener->onClientApplicationRegistered
  • StatusUpdater–>updateStatus

这里不详细展开,下文还会提到,通过日志,可以查看到客户端定时发送的POST请求:

服务端定时轮询

在服务器宕机的时候,服务器接收不到请求,此时服务器不知道客户端是什么状态,(当然可以说服务器在一定的时间里没有收到客户端的信息,就认为客户端挂了,这也是一种处理方式),在Spring Boot Admin中,服务端通过定时轮询客户端的/health接口来对客户端进行心态检测。

这里设计到主要的类为:

StatusUpdateApplicationListene

@EventListener
    public void onApplicationReady(ApplicationReadyEvent event) {
        if (event.getApplicationContext() instanceof WebApplicationContext) {
            startStatusUpdate();
        }
    }
    public void startStatusUpdate() {
        if (scheduledTask != null && !scheduledTask.isDone()) {
            return;
        }
        scheduledTask = taskScheduler.scheduleAtFixedRate(new Runnable() {
            @Override
            public void run() {
                statusUpdater.updateStatusForAllApplications();
            }
        }, updatePeriod);
        LOGGER.debug("Scheduled status-updater task for every {}ms", updatePeriod);
    }

StatusUpdater

    public void updateStatusForAllApplications() {
        long now = System.currentTimeMillis();
        for (Application application : store.findAll()) {
            if (now - statusLifetime > application.getStatusInfo().getTimestamp()) {
                updateStatus(application);
            }
        }
    }
public void updateStatus(Application application) {
        StatusInfo oldStatus = application.getStatusInfo();
        StatusInfo newStatus = queryStatus(application);
        boolean statusChanged = !newStatus.equals(oldStatus);
        Application.Builder builder = Application.copyOf(application).withStatusInfo(newStatus);
        if (statusChanged && !newStatus.isOffline() && !newStatus.isUnknown()) {
            builder.withInfo(queryInfo(application));
        }
        Application newState = builder.build();
        store.save(newState);
        if (statusChanged) {
            publisher.publishEvent(
                    new ClientApplicationStatusChangedEvent(newState, oldStatus, newStatus));
        }
    }

这里就不详细展开,如有不当之处,欢迎大家指正。以上为个人经验,希望能给大家一个参考,也希望大家多多支持我们。

(0)

相关推荐

  • SpringBoot整合Netty心跳机制过程详解

    前言 Netty 是一个高性能的 NIO 网络框架,本文基于 SpringBoot 以常见的心跳机制来认识 Netty. 最终能达到的效果: 客户端每隔 N 秒检测是否需要发送心跳. 服务端也每隔 N 秒检测是否需要发送心跳. 服务端可以主动 push 消息到客户端. 基于 SpringBoot 监控,可以查看实时连接以及各种应用信息. IdleStateHandler Netty 可以使用 IdleStateHandler 来实现连接管理,当连接空闲时间太长(没有发送.接收消息)时则会触发一个

  • 详解SpringBoot健康检查的实现原理

    SpringBoot自动装配的套路,直接看 spring.factories 文件,当我们使用的时候只需要引入如下依赖 <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-actuator</artifactId> </dependency> 然后在 org.springframework.boot.sprin

  • Spring Boot Admin 的使用详解

    一.前言 Spring Boot Admin 用于监控基于 Spring Boot 的应用.官方文档在这里(v1.3.4):<Spring Boot Admin Reference Guide> 实践的过程中,感觉这个 User Guide 结构上还是说的不太明白.所以我就大概写一遍我的实践过程与理解. 阅读此文前提条件是: 使用过 Maven. 你跑过基于 Spring Boot 的 hello world 程序. 第三节需要你会点 Spring Cloud 的 Eureka Server

  • SpringBoot Admin使用及心跳检测原理分析

    目录 介绍 使用 Server端 Client端 心跳检测/健康检测原理 原理 调试准备 客户端发起POST请求 服务端定时轮询 介绍 Spring Boot Admin是一个Github上的一个开源项目,它在Spring Boot Actuator的基础上提供简洁的可视化WEB UI,是用来管理 Spring Boot 应用程序的一个简单的界面,提供如下功能: 显示 name/id 和版本号 显示在线状态 Logging日志级别管理 JMX beans管理 Threads会话和线程管理 Tra

  • 浅谈web上存漏洞及原理分析、防范方法(文件名检测漏洞)

    我们通过前篇:<浅谈web上存漏洞及原理分析.防范方法(安全文件上存方法)>,已经知道后端获取服务器变量,很多来自客户端传入的.跟普通的get,post没有什么不同.下面我们看看,常见出现漏洞代码.1.检测文件类型,并且用用户上存文件名保存 复制代码 代码如下: if(isset($_FILES['img'])){    $file = save_file($_FILES['img']); if($file===false) exit('上存失败!'); echo "上存成功!&qu

  • Springboot动态切换数据源的具体实现与原理分析

    目录 前言 具体实现: 原理分析: 总结 前言 在springboot项目中只需一句代码即可实现多个数据源之间的切换: // 切换sqlserver数据源: DataSourceContextHolder.setDataBaseType(DataSourceEnum.SQLSERVER_DATASOURCE); ...... // 切换mysql数据源 DataSourceContextHolder.setDataBaseType(DataSourceEnum.MYSQL_DATASOURCE)

  • SpringBoot自动配置原理分析

    目录 前言 一.启动类 1.1.@SpringBootConfiguration 1.2.@EnableAutoConfiguration 1.3.@ComponentScan 1.4.探究方向 二.@SpringBootConfiguration 三.@EnableAutoConfiguration 3.1.@AutoConfigurationPackage 3.2.@Import(AutoConfigurationImportSelector.class) 3.2.1.getCandidat

  • Rabbitmq heartbea心跳检测机制原理解析

    前言 使用rabbitmq的时候,当你客户端与rabbitmq服务器之间一段时间没有流量,服务器将会断开与客户端之间tcp连接. 而你将在服务器上看这样的日志: missed heartbeats from client, timeout: xxs 这个间隔时间就是心跳间隔. heartbeat通常用来检测通信的对端是否存活(未正常关闭socket连接而异常crash).其基本原理是检测对应的socket连接上数据的收发是否正常,如果一段时间内没有收发数据,则向对端发送一个心跳检测包,如果一段时

  • 基于JS对象创建常用方式及原理分析

    前言 俗话说"在js语言中,一切都对象",而且创建对象的方式也有很多种,所以今天我们做一下梳理 最简单的方式 JavaScript创建对象最简单的方式是:对象字面量形式或使用Object构造函数 对象字面量形式 var person = new Object(); person.name = "jack"; person.sayName = function () { alert(this.name) } 使用Object构造函数 var person = { na

  • VBS脚本病毒原理分析与防范

    网络的流行,让我们的世界变得更加美好,但它也有让人不愉快的时候.当您收到一封主题为"I Love You"的邮件,用兴奋得几乎快发抖的鼠标去点击附件的时候:当您浏览一个信任的网站之后,发现打开每个文件夹的速度非常慢的时候,您是否察觉病毒已经闯进了您的世界呢?2000年5月4日欧美爆发的"爱虫"网络蠕虫病毒.由于通过电子邮件系统传播,爱虫病毒在短短几天内狂袭全球数百万计的电脑.微软.Intel等在内的众多大型企业网络系统瘫痪,全球经济损失达几十亿美元.而去年爆发的新欢

  • java 中ThreadPoolExecutor原理分析

    java 中ThreadPoolExecutor原理分析 线程池简介 Java线程池是开发中常用的工具,当我们有异步.并行的任务要处理时,经常会用到线程池,或者在实现一个服务器时,也需要使用线程池来接收连接处理请求. 线程池使用 JDK中提供的线程池实现位于java.util.concurrent.ThreadPoolExecutor.在使用时,通常使用ExecutorService接口,它提供了submit,invokeAll,shutdown等通用的方法. 在线程池配置方面,Executor

  • Python构建网页爬虫原理分析

    既然本篇文章说到的是Python构建网页爬虫原理分析,那么小编先给大家看一下Python中关于爬虫的精选文章: python实现简单爬虫功能的示例 python爬虫实战之最简单的网页爬虫教程 网络爬虫是当今最常用的系统之一.最流行的例子是 Google 使用爬虫从所有网站收集信息.除了搜索引擎之外,新闻网站还需要爬虫来聚合数据源.看来,只要你想聚合大量的信息,你可以考虑使用爬虫. 建立一个网络爬虫有很多因素,特别是当你想扩展系统时.这就是为什么这已经成为最流行的系统设计面试问题之一.在这篇文章中

  • Springboot 2使用外部Tomcat源码分析

    Springboot 使用外部 Tomcat 1.修改 pom.xml,改为打 war 包 <packaging>war</packaging> 2.将 Springboot 内置 tomcat 作用域改为provided <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-tomcat</artifac

随机推荐