首先我们简单了解一下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有两种注册方式,一种通过http注册,一种通过注册中心比如Eureka,consul等。
我们先讲讲通过http注册的流程。
<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为例。
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
这个类。
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注册的逻辑就是相同的了。接下来我们看看共同的逻辑处理,服务端如何处理这些注册的应用。
不管是通过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