Spring Boot Admin(SBA)核心流程-注册篇

SBA是什么

首先我们简单了解一下Spring Boot Admin(SBA),以下统一简称SBA是什么。借用官网的描述:

SBA 是 codecentric 公司开发的一款开源社区项目,目标是让用户更方便的管理以及监控 Spring Boot ® 应用。 应用可以通过我们的SBA客户端(通过HTTP的方式)或者使用Spring Cloud ®(比如Eureka,consul的方式)注册。 基于Spring Boot Actuator默认接口开发的。

简单了解了SBA是什么,我们就开始介绍SBA的核心流程,至于怎么搭建SBA客户端and服务端,可自行百度,这类的文章居多。建议阅读本文章之前,先搭建SBA使用。

SBA客户端如何注册到服务端

首先SBA分为客户端跟服务端两个部分,客户端为我们需要监控的应用,服务端则是可以看到各个应用的监控情况。需要实现这一点,首先我们需要将客户端注册到服务端。SBA有两种注册方式,一种通过http注册,一种通过注册中心比如Eureka,consul等。

客户端如何通过http的方式注册到服务端

我们先讲讲通过http注册的流程。

  1. 客户端首先需要引入SBA客户端的依赖,这里以2.7.10为例。客户端的依赖版本取决于你的应用的Spring Boot版本。我的应用版本是2.7.0,所以SBA客户端对应版本用的2.7.X。
<dependency>  
<groupId>de.codecentricgroupId>  
<artifactId>spring-boot-admin-starter-clientartifactId>  
<version>2.7.10version>  
dependency>


<dependency>  
<groupId>org.springframework.bootgroupId>  
<artifactId>spring-boot-starter-actuatorartifactId>  
dependency>

2.配置SBA参数

#开启SBA配置
spring.boot.admin.client.enabled=true

#注册到服务端的url
spring.boot.admin.client.url=http://localhost:9001  
#让actuator端点先全部暴露出来,正式环境请酌情配置
management.endpoints.web.exposure.include=*

接下来我们就从源码的角度,讲讲客户端如何注册到服务端。
这块的入口代码在RegistrationApplicationListener类中

@EventListener  
@Order(Ordered.LOWEST_PRECEDENCE)  
public void onApplicationReady(ApplicationReadyEvent event) {  
  if (autoRegister) {  
   startRegisterTask();  
  }  
}

从上面代码可以看出来在应用启动后,会执行一个定时任务。而这个定时任务就是客户端注册的任务。
核心代码如下:

@Override  
public boolean register() {  
//构建一个Application实例,其中包括应用的名称、应用的healthUrl、managementUrl、serviceUrl等。
Application application = this.applicationFactory.createApplication();  
boolean isRegistrationSuccessful = false;  
//这个adminUrls就是客户端配置的spring.boot.admin.client.url的值。可以用逗号分割,配置多个。
for (String adminUrl : this.adminUrls) {  
 LongAdder attempt = this.attempts.computeIfAbsent(adminUrl, (k) -> new LongAdder());  
 //核心注册方法,会通过HTTP的方法发送post请求到你配置的spring.boot.admin.client.url进行注册,
 //请求体就是构建的Application实例。
 boolean successful = register(application, adminUrl, attempt.intValue() == 0);  
  
if (!successful) {  
 attempt.increment();  
}  
else {  
 attempt.reset();  
 isRegistrationSuccessful = true;  
if (this.registerOnce) {  
 break;  
}  
}  
}  
  
return isRegistrationSuccessful;  
}

简而言之客户端启动成功后,会定时向服务端发起注册请求。服务端的url需要进行客户端进行配置,这个定时任务默认是10秒一次。

这个注册方式比较简单粗暴,个人感觉不如注册中心的方法好使。核心代码也比较简单。感兴趣的朋友可以自己阅读一下源码。

客户端如何通过服务发现的方式注册到服务端

重点讲解一下SBA客户端如何通过服务发现的方式注册到服务端。
本次讲解以Eureka为例。

  1. 启动一个Eureka
  2. 客户端依旧需要引入SBA client的依赖,参考http方式注册。
  3. 服务端跟客户端都要添加Eureka配置
eureka:  
  client:  
    register-with-eureka: true  
    fetch-registry: true  
    service-url:  
      defaultZone: http://localhost:8001/eureka/
 #测试用,先把端点全部放出来     
 management:  
  endpoints:  
    web:  
      exposure:  
        include: '*'     
     

通过服务发现的方式注册最大的不同就是,客户端不需要配置服务的url.只需要配置注册中心,就能自动注册到服务端。下面我们从源码的角度讲解,SBA是如何实现的。

服务发现的核心代码在spring-boot-admin-server-cloud模块。核心逻辑主要是InstanceDiscoveryListener这个类。

  1. 服务端在启动的时候会创建InstanceDiscoveryListener这个类
        @Bean  
        @ConditionalOnMissingBean  
        @ConfigurationProperties(prefix = "spring.boot.admin.discovery")  
        public InstanceDiscoveryListener instanceDiscoveryListener(ServiceInstanceConverter serviceInstanceConverter,  
                        DiscoveryClient discoveryClient, InstanceRegistry registry, InstanceRepository repository) {  
                InstanceDiscoveryListener listener = new InstanceDiscoveryListener(discoveryClient, registry, repository);  
                listener.setConverter(serviceInstanceConverter);  
                return listener;  
        }

2.InstanceDiscoveryListener类中监听了心跳事件

@EventListener  
public void onParentHeartbeat(ParentHeartbeatEvent event) {  
       discoverIfNeeded(event.getValue());  
}

心跳的核心代码:

protected void discover() {  
    Flux.fromIterable(discoveryClient.getServices()).filter(this::shouldRegisterService)  
    .flatMapIterable(discoveryClient::getInstances).filter(this::shouldRegisterInstanceBasedOnMetadata)  
    .flatMap(this::registerInstance).collect(Collectors.toSet()).flatMap(this::removeStaleInstances)  
    .subscribe((v) -> {  
    }, (ex) -> log.error("Unexpected error.", ex));  
}

简单解释一下这段代码的意思

1、从 discoveryClient获取服务列表。注册到eureka的服务都获取到。

2、用 shouldRegisterService 方法来过滤服务列表中的一些服务,保留应该注册的服务。

3、对每个满足条件的实例,执行 registerInstance 方法进行注册。

注册的核心逻辑就在registerInstance 方法中。接下来我们从该类接着看

protected Mono<InstanceId> registerInstance(ServiceInstance instance) {  
    try {  
        // 拿到ServiceInstance转换成Registration进行注册。Registration包含应用name、managementUrl、serviceUrl等
        Registration registration = converter.convert(instance).toBuilder().source(SOURCE).build();  
        log.debug("Registering discovered instance {}", registration);  
        //进行注册
        return registry.register(registration);  
    }  
    catch (Exception ex) {  
        log.error("Couldn't register instance for discovered instance ({})", toString(instance), ex);  
        return Mono.empty();  
    }  
}

后面的注册逻辑跟通过http注册的逻辑就是相同的了。接下来我们看看共同的逻辑处理,服务端如何处理这些注册的应用。

SBA服务端如何处理注册的应用

不管是通过HTTP还是服务发现的方式注册到服务端,最后服务端处理注册的核心逻辑都是InstanceRegistry类。注册的核心代码如下:

public Mono<InstanceId> register(Registration registration) {  
    Assert.notNull(registration, "'registration' must not be null");  
    //生成一个实例ID,同一个实例ID总是相同的。
    InstanceId id = generator.generateId(registration);  
    Assert.notNull(id, "'id' must not be null");  
    //这是重点
    return repository.compute(id, (key, instance) -> {  
    if (instance == null) {  
        instance = Instance.create(key);  
    }  
    return Mono.just(instance.register(registration));  
    }).map(Instance::getId);  
}

compute方法是更新或创建实例。它首先尝试查找实例,如果找到了就更新实例,如果没有找到就创建新实例。然后,将更新或新创建的实例传递给 save 方法进行保存。

SBA的实例默认都是保存在内存中的,SBA服务端通过保存的实例信息,每间隔一段时间(可配置)会发送http请求去请求健康状态、端点信息等。如果这个文章数据不错的话,后续会考虑更新一下相关健康状态、端点信息相关源码。

相关对标产品MOSS也不错,有兴趣的同学可以去了解一下。MOSS扩展了更多的端点,使监控数据更加直观。缺点就是很久没有维护了。拿来学习学习还是不错的。
附一个MOSS的链接:
https://github.com/SpringCloud/Moss

你可能感兴趣的:(spring,boot,后端,java)