前面的文章(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 的实现。其中 Flux
、Mono
都是它的实现,顺着这个,我们也就能理解, Spring 5 的 响应式为什么会叫做 WebFlux
了。 因为毕竟是和 Flux 一脉相承的嘛。
相关阅读
更多常见问题,请关注公众号,在菜单「常见问题」中查看。
源码|实战|成长|职场
这里是「Tomcat那些事儿」
请留下你的足迹
我们一起「终身成长」
识别二维码,关注我
如有帮助,就给我一个“好看”吧