-
Notifications
You must be signed in to change notification settings - Fork 718
Fixes issues in HealthCheckServiceInstanceListSupplier #685
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 2 commits
2e553a0
c1d1675
a1b60a1
5905dfb
66d2cd6
bceb947
6849d9c
a1fd01a
b2305b2
515c5b9
bab10c2
16bbe53
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -16,18 +16,20 @@ | |
|
|
||
| package org.springframework.cloud.loadbalancer.core; | ||
|
|
||
| import java.time.Duration; | ||
| import java.util.ArrayList; | ||
| import java.util.Collections; | ||
| import java.util.List; | ||
| import java.util.concurrent.TimeUnit; | ||
| import java.util.concurrent.CopyOnWriteArrayList; | ||
|
|
||
| import org.apache.commons.logging.Log; | ||
| import org.apache.commons.logging.LogFactory; | ||
| import reactor.core.Disposable; | ||
| import reactor.core.publisher.Flux; | ||
| import reactor.core.publisher.FluxSink; | ||
| import reactor.core.publisher.Mono; | ||
| import reactor.core.scheduler.Schedulers; | ||
|
|
||
| import org.springframework.beans.factory.DisposableBean; | ||
| import org.springframework.cloud.client.ServiceInstance; | ||
| import org.springframework.cloud.client.loadbalancer.reactive.LoadBalancerProperties; | ||
| import org.springframework.http.HttpStatus; | ||
|
|
@@ -40,10 +42,11 @@ | |
| * {@link WebClient} to ping the <code>health</code> endpoint of the instances. | ||
| * | ||
| * @author Olga Maciaszek-Sharma | ||
| * @author Roman Matiushchenko | ||
| * @since 2.2.0 | ||
| */ | ||
| public class HealthCheckServiceInstanceListSupplier | ||
| implements ServiceInstanceListSupplier { | ||
| implements ServiceInstanceListSupplier, DisposableBean { | ||
|
|
||
| private static final Log LOG = LogFactory | ||
| .getLog(HealthCheckServiceInstanceListSupplier.class); | ||
|
|
@@ -56,11 +59,11 @@ public class HealthCheckServiceInstanceListSupplier | |
|
|
||
| private final String defaultHealthCheckPath; | ||
|
|
||
| private List<ServiceInstance> instances = Collections | ||
| .synchronizedList(new ArrayList<>()); | ||
| private List<ServiceInstance> instances = Collections.emptyList(); | ||
|
|
||
| private List<ServiceInstance> healthyInstances = Collections | ||
| .synchronizedList(new ArrayList<>()); | ||
| private volatile List<ServiceInstance> healthyInstances = Collections.emptyList(); | ||
|
|
||
| private Disposable healthCheckDisposable; | ||
|
|
||
| public HealthCheckServiceInstanceListSupplier(ServiceInstanceListSupplier delegate, | ||
| LoadBalancerProperties.HealthCheck healthCheck, WebClient webClient) { | ||
|
|
@@ -74,32 +77,33 @@ public HealthCheckServiceInstanceListSupplier(ServiceInstanceListSupplier delega | |
| } | ||
|
|
||
| private void initInstances() { | ||
| delegate.get().subscribe(delegateInstances -> { | ||
| instances.clear(); | ||
| instances.addAll(delegateInstances); | ||
| }); | ||
|
|
||
| Flux<List<ServiceInstance>> healthCheckFlux = healthCheckFlux(); | ||
|
|
||
| healthCheckFlux.subscribe(verifiedInstances -> { | ||
| healthyInstances.clear(); | ||
| healthyInstances.addAll(verifiedInstances); | ||
| }); | ||
| healthCheckDisposable = delegate.get().doOnNext(delegateInstances -> { | ||
|
||
| instances = Collections.unmodifiableList(new ArrayList<>(delegateInstances)); | ||
| }) | ||
| .thenMany(healthCheckFlux()).subscribeOn(Schedulers.parallel()) | ||
| .subscribe(verifiedInstances -> healthyInstances = verifiedInstances); | ||
| } | ||
|
|
||
| protected Flux<List<ServiceInstance>> healthCheckFlux() { | ||
| return Flux.create(emitter -> Schedulers | ||
| .newSingle("Health Check Verifier: " + getServiceId(), true) | ||
| .schedulePeriodically(() -> { | ||
| List<ServiceInstance> verifiedInstances = new ArrayList<>(); | ||
| Flux.fromIterable(instances).filterWhen(this::isAlive) | ||
| .subscribe(serviceInstance -> { | ||
| verifiedInstances.add(serviceInstance); | ||
| emitter.next(verifiedInstances); | ||
| }); | ||
| }, healthCheck.getInitialDelay(), healthCheck.getInterval().toMillis(), | ||
| TimeUnit.MILLISECONDS), | ||
| FluxSink.OverflowStrategy.LATEST); | ||
| return Flux.defer(() -> { | ||
| List<ServiceInstance> result = new CopyOnWriteArrayList<>(); | ||
|
|
||
| List<Mono<ServiceInstance>> checks = new ArrayList<>(); | ||
| for (ServiceInstance instance : instances) { | ||
| Mono<ServiceInstance> alive = isAlive(instance) | ||
| .onErrorResume(throwable -> Mono.empty()) | ||
|
||
| .timeout(healthCheck.getInterval(), Mono.empty()).filter(it -> it) | ||
| .map(check -> instance); | ||
|
|
||
| checks.add(alive); | ||
| } | ||
| return Flux.merge(checks).map(alive -> { | ||
| result.add(alive); | ||
| return result; | ||
|
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. as far as List<ServiceInstance> result = new CopyOnWriteArrayList<>();
AtomicBoolean first = new AtomicBoolean(false);
return Flux.merge(checks).<List<ServiceInstance>>handle((alive, sink) -> {
result.add(alive);
if (first.compareAndSet(false, true)) {
sink.next(result);
}
})
.defaultIfEmpty(result);There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Why not just
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. this question was addressed to my intermediate implementation which mutated |
||
| }).defaultIfEmpty(result); | ||
| }) | ||
| .repeatWhen(restart -> restart.delayElements(healthCheck.getInterval())) | ||
| .delaySubscription(Duration.ofMillis(healthCheck.getInitialDelay())); | ||
| } | ||
|
|
||
| @Override | ||
|
|
@@ -109,16 +113,16 @@ public String getServiceId() { | |
|
|
||
| @Override | ||
| public Flux<List<ServiceInstance>> get() { | ||
| if (!healthyInstances.isEmpty()) { | ||
| return Flux.defer(() -> Flux.fromIterable(healthyInstances).collectList()); | ||
| } | ||
| // If there are no healthy instances, it might be better to still retry on all of | ||
| // them | ||
| if (LOG.isWarnEnabled()) { | ||
| LOG.warn( | ||
| "No verified healthy instances were found, returning all listed instances."); | ||
| } | ||
| return Flux.defer(() -> Flux.fromIterable(instances).collectList()); | ||
| return Flux.defer(() -> { | ||
| List<ServiceInstance> it = new ArrayList<>(healthyInstances); | ||
| if (it.isEmpty()) { | ||
| if (LOG.isWarnEnabled()) { | ||
| LOG.warn("No verified healthy instances were found, returning all listed instances."); | ||
| } | ||
| it = instances; | ||
| } | ||
| return Flux.just(it); | ||
| }); | ||
| } | ||
|
|
||
| protected Mono<Boolean> isAlive(ServiceInstance serviceInstance) { | ||
|
|
@@ -130,7 +134,14 @@ protected Mono<Boolean> isAlive(ServiceInstance serviceInstance) { | |
| .uri(UriComponentsBuilder.fromUri(serviceInstance.getUri()) | ||
| .path(healthCheckPath).build().toUri()) | ||
| .exchange() | ||
| .map(clientResponse -> HttpStatus.OK.equals(clientResponse.statusCode())); | ||
| .flatMap(clientResponse -> clientResponse.releaseBody() | ||
| .thenReturn(HttpStatus.OK.equals(clientResponse.statusCode())) | ||
|
||
| ); | ||
| } | ||
|
|
||
| @Override | ||
| public void destroy() { | ||
| this.healthCheckDisposable.dispose(); | ||
| } | ||
|
|
||
| } | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I've tried to make minimal changes to the current logic but
I've found testing of ping functionality a little bit inconvenient
HealthCheckServiceInstanceListSupplierstarts ping implicitly and eventually I test just another stream which uses common state...IMO It signals that this functionality must be either moved to a new class or just spitted into different methods.
So I would like to make
HealthCheckServiceInstanceListSupplieranInitializingBean, and move start of pings out of constructorand
make this
and
two distinct methods which are invoked from
afterPropertiesSet()does this make sense @spencergibb @OlgaMaciaszek