Spring Boot Admin(二) 应用的注册

前面的文章(Spring Boot Admin (一) 请求处理原理)我们介绍了 Spring Boot Admin 的 Server ,是如何以 Spring Boot 的形式完成了应用的加载以及请求的 Mapping 注册。

这一篇,我们来看看 Client 的信息是怎么样注册到 Server的,又是如何使用Reactive Stream完成反应式设计。

Client 注册

要说 Client 的注册,我们需要先来看下 Client 的 Spring Boot Config 里包含了一些什么内容

@Bean
@ConditionalOnMissingBean
public ApplicationRegistrator registrator(ClientProperties client,
ApplicationFactory applicationFactory,
RestTemplateBuilder restTemplBuilder) {
RestTemplateBuilder builder = restTemplBuilder.messageConverters(new MappingJackson2HttpMessageConverter())
.requestFactory(SimpleClientHttpRequestFactory.class)
.setConnectTimeout(client.getConnectTimeout())
.setReadTimeout(client.getReadTimeout());
if (client.getUsername() != null) {
builder = builder.basicAuthentication(client.getUsername(), client.getPassword());
}
return new ApplicationRegistrator(builder.build(), client, applicationFactory);
}

我们看到这里声明了一个ApplicationRegistrator 的Bean,这个Registrator 会实际进行 client 的注册。我们来看他里面的这个register方法:

    /**
     * Registers the client application at spring-boot-admin-server.
     *
     * @return true if successful registration on at least one admin server
     */
    public boolean register() {
        Application self = createApplication();
        boolean isRegistrationSuccessful = false;
        for (String adminUrl : client.getAdminUrl()) {
            LongAdder attempt = this.attempts.computeIfAbsent(adminUrl, k -> new LongAdder());
            boolean successful = register(self, adminUrl, attempt.intValue() == 0);


            if (!successful) {
                attempt.increment();
            } else {
                attempt.reset();
                isRegistrationSuccessful = true;
                if (client.isRegisterOnce()) {
                    break;
                }
            }
        }


        return isRegistrationSuccessful;
    }

这里的adminUrl,就是我们在上一篇的配置里增加的spring.boot.admin.client.url。实际应用的时候,会给它补上后缀:instances

public String[] getAdminUrl() {
String[] adminUrls = url.clone();
for (int i = 0; i < adminUrls.length; i++) {
adminUrls[i] += "/" + apiPath;
}
return adminUrls;
}

这里的apiPath 定义如下:

/**
* The admin rest-apis path.
*/
private String apiPath = "instances";

所以,通过这个,我们可以猜到, Client实际在注册的时候, 是相当于调用 Server 端 InstancesController.

实际注册的时候,是restTemplate 直接调用目标接口 ResponseEntity<Map<String, Object>> response = template.exchange(adminUrl, HttpMethod.POST, new HttpEntity<>(self, HTTP_HEADERS), RESPONSE_TYPE);

Server 端的处理

以下是 client 调用的 instanceController里的注册方法

/**
* Register an instance.
*
* @param registration registration info
* @param builder UriComponentsBuilder
* @return The registered instance id;
*/
@PostMapping(path = "/instances", consumes = MediaType.APPLICATION_JSON_VALUE)
public Mono<ResponseEntity<Map<String, InstanceId>>> register(@RequestBody Registration registration,
UriComponentsBuilder builder) {
Registration withSource = Registration.copyOf(registration).source("http-api").build();
LOGGER.debug("Register instance {}", withSource);
return registry.register(withSource).map(id -> {
URI location = builder.replacePath("/instances/{id}").buildAndExpand(id).toUri();
return ResponseEntity.created(location).body(Collections.singletonMap("id", id));
});
}

实际上, Client 请求会传过来类似下面的数据:

Registration(name=spring-boot-application, managementUrl=http://houshucengdembp.lan:8080/actuator, 
healthUrl=http://houshucengdembp.lan:8080/actuator/health, serviceUrl=http://houshucengdembp.lan:8080/, source=null)

这些数据,最终会生成一个Instance

public Instance register(Registration registration) {
Assert.notNull(registration, "'registration' must not be null");
if (!this.isRegistered()) {
return this.apply(new InstanceRegisteredEvent(this.id, this.nextVersion(), registration), true);
}


if (!Objects.equals(this.registration, registration)) {
return this.apply(new InstanceRegistrationUpdatedEvent(this.id, this.nextVersion(), registration), true);
}


return this;
}

注册过程,是以事件的形式进行传递处理。

private Instance apply(InstanceEvent event, boolean isNewEvent) {
List<InstanceEvent> unsavedEvents = appendToEvents(event, isNewEvent);


if (event instanceof InstanceRegisteredEvent) {
} else if (event instanceof InstanceRegistrationUpdatedEvent) {
} else if (event instanceof InstanceStatusChangedEvent) {
} else if (event instanceof InstanceEndpointsDetectedEvent) {
} else if (event instanceof InstanceInfoChangedEvent) {
} else if (event instanceof InstanceDeregisteredEvent) {
}
return this;
}

使用过领域驱动开发(DDD)的朋友应该比较熟悉,这里和 DDD 的 EventSourcing 一样,通过事件的形式进行不同逻辑的处理。

实际注册过程中,并没有看到保存数据到repository里,那实际这数据存哪了? 即使是内存结构,也必须有save操作能拿到。

这就是我们上一节提到的 Reactive Stream,对应到 Spring 5 里的 WebFlux

,是通过 Reactor 实现的 Reactive Stream,和 RxJava 类似。在Spring 里,会针对不同的数据类似,进行 returnValue 的后处理。从返回值的类型,选择不同的handler,和前期 DispatcherServlet 选择不同的 MVC Handler类似。

/**
* Process the given reactive return value and decide whether to adapt it
* to a {@link ResponseBodyEmitter} or a {@link DeferredResult}.
* @return an emitter for streaming, or {@code null} if handled internally
* with a {@link DeferredResult}
*/
@Nullable
public ResponseBodyEmitter handleValue(Object returnValue, MethodParameter returnType,
ModelAndViewContainer mav, NativeWebRequest request) throws Exception {
ReactiveAdapter adapter = this.reactiveRegistry.getAdapter(returnValue.getClass());
Assert.state(adapter != null, () -> "Unexpected return value: " + returnValue);


ResolvableType elementType = ResolvableType.forMethodParameter(returnType).getGeneric();
Class<?> elementClass = elementType.toClass();
Collection<MediaType> mediaTypes = getMediaTypes(request);
Optional<MediaType> mediaType = mediaTypes.stream().filter(MimeType::isConcrete).findFirst();


if (adapter.isMultiValue()) {
if (mediaTypes.stream().anyMatch(MediaType.TEXT_EVENT_STREAM::includes) ||
ServerSentEvent.class.isAssignableFrom(elementClass)) {
logExecutorWarning(returnType);
SseEmitter emitter = new SseEmitter(STREAMING_TIMEOUT_VALUE);
new SseEmitterSubscriber(emitter, this.taskExecutor).connect(adapter, returnValue);
return emitter;
}
if (CharSequence.class.isAssignableFrom(elementClass)) {
logExecutorWarning(returnType);
ResponseBodyEmitter emitter = getEmitter(mediaType.orElse(MediaType.TEXT_PLAIN));
new TextEmitterSubscriber(emitter, this.taskExecutor).connect(adapter, returnValue);
return emitter;
}
if (mediaTypes.stream().anyMatch(MediaType.APPLICATION_STREAM_JSON::includes)) {
logExecutorWarning(returnType);
ResponseBodyEmitter emitter = getEmitter(MediaType.APPLICATION_STREAM_JSON);
new JsonEmitterSubscriber(emitter, this.taskExecutor).connect(adapter, returnValue);
return emitter;
}
}


// Not streaming...
DeferredResult<Object> result = new DeferredResult<>();
new DeferredResultSubscriber(result, adapter, elementType).connect(adapter, returnValue);
WebAsyncUtils.getAsyncManager(request).startDeferredResultProcessing(result, mav);


return null;
}

所以 Flux、Mono 这种返回结构也能匹配我们需要的 application/json。

我们知道, Reactive Stream 和 观察者模式类似。那返回值做为一个 Flux类型,也是一个 Publisher。在处理了返回值转换后,后续其他的 subscriber 也会继续处理,环环相扣。

在 Spring Boot Admin 里默认使用的 Repository 是 Snapshot 形式的,内存存储。所有实例的信息,会保存到一个 ConcurrentHashMap中。

而 Repository 里会注册一个 subscriber,在returnValue后续的流程中,也会收到事件的通知。此时,相应的方法会被执行。下面这个方法就是事件处理的方法。

protected Mono<Void> updateSnapshot(InstanceEvent event) {
        return Mono.<Void>fromRunnable(() -> snapshots.compute(event.getInstance(), (key, old) -> {
            Instance instance = old != null ? old : Instance.create(key);
            return instance.apply(event);
        })).onErrorResume(ex -> {
            log.warn(
                "Error while updating the snapshot with event {}. Recomputing instance snapshot from event history.",
                event,
                ex
            );
            return recomputeSnapshot(event.getInstance());
        });
    }


subscribe的注册,是在启动时进行的。

public void start() {
        this.subscription = this.getEventStore()
                                .findAll()
                                .concatWith(this.getEventStore())
                                .concatMap(this::updateSnapshot)
                                .subscribe();
    }




整体调用了解后,我们再看下WebFlux的调用栈:这是处理returnValue时的栈,调用层级够深吧 :-)

java.lang.Thread.State: RUNNABLE
at de.codecentric.boot.admin.server.eventstore.ConcurrentMapEventStore.doAppend(ConcurrentMapEventStore.java:82)
at de.codecentric.boot.admin.server.eventstore.ConcurrentMapEventStore.lambda$append$2(ConcurrentMapEventStore.java:70)
at de.codecentric.boot.admin.server.eventstore.ConcurrentMapEventStore$$Lambda$718.279738908.run(Unknown Source:-1)
at reactor.core.publisher.MonoRunnable.call(MonoRunnable.java:73)
at reactor.core.publisher.MonoRunnable.call(MonoRunnable.java:32)
at reactor.core.publisher.MonoIgnoreThen$ThenIgnoreMain.drain(MonoIgnoreThen.java:160)
at reactor.core.publisher.MonoIgnoreThen.subscribe(MonoIgnoreThen.java:56)
at reactor.core.publisher.MonoFlatMap$FlatMapMain.onNext(MonoFlatMap.java:150)
at reactor.core.publisher.FluxSwitchIfEmpty$SwitchIfEmptySubscriber.onNext(FluxSwitchIfEmpty.java:67)
at reactor.core.publisher.Operators$ScalarSubscription.request(Operators.java:2070)
at reactor.core.publisher.Operators$MultiSubscriptionSubscriber.set(Operators.java:1878)
at reactor.core.publisher.Operators$MultiSubscriptionSubscriber.onSubscribe(Operators.java:1752)
at reactor.core.publisher.MonoJust.subscribe(MonoJust.java:54)
at reactor.core.publisher.MonoDefer.subscribe(MonoDefer.java:52)
at reactor.core.publisher.Mono.subscribe(Mono.java:3694)
at reactor.core.publisher.FluxSwitchIfEmpty$SwitchIfEmptySubscriber.onComplete(FluxSwitchIfEmpty.java:75)
at reactor.core.publisher.MonoFlatMap$FlatMapMain.onComplete(MonoFlatMap.java:174)
at reactor.core.publisher.Operators.complete(Operators.java:131)
at reactor.core.publisher.MonoEmpty.subscribe(MonoEmpty.java:45)
at reactor.core.publisher.MonoDefer.subscribe(MonoDefer.java:52)
at reactor.core.publisher.MonoFlatMap.subscribe(MonoFlatMap.java:60)
at reactor.core.publisher.MonoSwitchIfEmpty.subscribe(MonoSwitchIfEmpty.java:44)
at reactor.core.publisher.MonoFlatMap.subscribe(MonoFlatMap.java:60)
at reactor.core.publisher.Mono.subscribe(Mono.java:3694)
at reactor.core.publisher.FluxRetryWhen.subscribe(FluxRetryWhen.java:85)
at reactor.core.publisher.MonoRetryWhen.subscribe(MonoRetryWhen.java:50)
at reactor.core.publisher.MonoMap.subscribe(MonoMap.java:55)
at reactor.core.publisher.MonoMap.subscribe(MonoMap.java:55)
at reactor.core.publisher.Mono.subscribe(Mono.java:3694)
at org.springframework.web.servlet.mvc.method.annotation.ReactiveTypeHandler$DeferredResultSubscriber.connect(ReactiveTypeHandler.java:440)
at org.springframework.web.servlet.mvc.method.annotation.ReactiveTypeHandler.handleValue(ReactiveTypeHandler.java:155)
at org.springframework.web.servlet.mvc.method.annotation.ResponseBodyEmitterReturnValueHandler.handleReturnValue(ResponseBodyEmitterReturnValueHandler.java:139)
at org.springframework.web.method.support.HandlerMethodReturnValueHandlerComposite.handleReturnValue(HandlerMethodReturnValueHandlerComposite.java:82)
at org.springframework.web.servlet.mvc.method.annotation.ServletInvocableHandlerMethod.invokeAndHandle(ServletInvocableHandlerMethod.java:119)
at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.invokeHandlerMethod(RequestMappingHandlerAdapter.java:895)
at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.handleInternal(RequestMappingHandlerAdapter.java:800)
at org.springframework.web.servlet.mvc.method.AbstractHandlerMethodAdapter.handle(AbstractHandlerMethodAdapter.java:87)
at org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:1038)
at org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:942)
at org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:1005)
at org.springframework.web.servlet.FrameworkServlet.doPost(FrameworkServlet.java:908)

总结

我们简单总结下,整体 Client 到 Server 的注册,是通过 HTTP 调用的形式,将注册数据发送到 Server, Server 再根据具体事件不能, 以 Event 的形式传递,最终影响到 Repository中的存储。

难点在于理解 returnValue 的处理之后再进行 repository 的存储操作。

后续我们单独开一篇来了解 io.projectreactor 的 Reactive Stream 的实现。其中 FluxMono 都是它的实现,顺着这个,我们也就能理解, Spring 5 的 响应式为什么会叫做 WebFlux了。 因为毕竟是和 Flux 一脉相承的嘛。

相关阅读

Spring Boot Admin (一) 请求处理原理

如何开发自己的Spring Boot Starter

一览Spring Bean 定义和映射全貌的神器

  更多常见问题,请关注公众号,在菜单「常见问题」中查看。

源码|实战|成长|职场

这里是「Tomcat那些事儿」

请留下你的足迹

我们一起「终身成长」

识别二维码,关注我

                                                                  如有帮助,就给我一个“好看”吧

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值