Adds API and implementation of ReactiveLoadBalancer
API lives in spring-cloud-commons module and implementation in spring-cloud-loadbalancer.
This commit is contained in:
1
pom.xml
1
pom.xml
@@ -126,6 +126,7 @@
|
||||
<module>spring-cloud-context</module>
|
||||
<module>spring-cloud-context-integration-tests</module>
|
||||
<module>spring-cloud-commons</module>
|
||||
<module>spring-cloud-loadbalancer</module>
|
||||
<module>spring-cloud-starter</module>
|
||||
<module>docs</module>
|
||||
</modules>
|
||||
|
||||
@@ -43,6 +43,11 @@
|
||||
<artifactId>spring-cloud-context</artifactId>
|
||||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.cloud</groupId>
|
||||
<artifactId>spring-cloud-loadbalancer</artifactId>
|
||||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.cloud</groupId>
|
||||
<artifactId>spring-cloud-starter</artifactId>
|
||||
|
||||
@@ -50,7 +50,7 @@ public class DefaultServiceInstance implements ServiceInstance {
|
||||
|
||||
public DefaultServiceInstance(String serviceId, String host, int port,
|
||||
boolean secure) {
|
||||
this(serviceId, host, port, secure, new LinkedHashMap<String, String>());
|
||||
this(serviceId, host, port, secure, new LinkedHashMap<>());
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -0,0 +1,64 @@
|
||||
/*
|
||||
* Copyright 2013-2017 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.cloud.client.loadbalancer.reactive;
|
||||
|
||||
import org.springframework.core.style.ToStringCreator;
|
||||
|
||||
/**
|
||||
* @author Spencer Gibb
|
||||
*/
|
||||
//TODO: add metrics
|
||||
public class CompletionContext {
|
||||
|
||||
public enum Status {
|
||||
/** Request was handled successfully */
|
||||
SUCCESSS,
|
||||
/** Request reached the server but failed due to timeout or internal error */
|
||||
FAILED,
|
||||
/** Request did not go off box and should not be counted for statistics */
|
||||
DISCARD,
|
||||
}
|
||||
|
||||
private final Status status;
|
||||
private final Throwable throwable;
|
||||
|
||||
public CompletionContext(Status status) {
|
||||
this(status, null);
|
||||
}
|
||||
|
||||
public CompletionContext(Status status, Throwable throwable) {
|
||||
this.status = status;
|
||||
this.throwable = throwable;
|
||||
}
|
||||
|
||||
public Status getStatus() {
|
||||
return status;
|
||||
}
|
||||
|
||||
public Throwable getThrowable() {
|
||||
return throwable;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
ToStringCreator to = new ToStringCreator(this);
|
||||
to.append("status", status);
|
||||
to.append("throwable", throwable);
|
||||
return to.toString();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
/*
|
||||
* Copyright 2013-2017 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.cloud.client.loadbalancer.reactive;
|
||||
|
||||
/**
|
||||
* @author Spencer Gibb
|
||||
*/
|
||||
public class DefaultRequest implements Request {
|
||||
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
/*
|
||||
* Copyright 2013-2017 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.cloud.client.loadbalancer.reactive;
|
||||
|
||||
import org.springframework.cloud.client.ServiceInstance;
|
||||
|
||||
/**
|
||||
* @author Spencer Gibb
|
||||
*/
|
||||
public class DefaultResponse implements Response<ServiceInstance> {
|
||||
|
||||
private final ServiceInstance serviceInstance;
|
||||
|
||||
public DefaultResponse(ServiceInstance serviceInstance) {
|
||||
this.serviceInstance = serviceInstance;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasServer() {
|
||||
return this.serviceInstance != null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ServiceInstance getServer() {
|
||||
return this.serviceInstance;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onComplete(CompletionContext completionContext) {
|
||||
//TODO: implement
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
/*
|
||||
* Copyright 2013-2017 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.cloud.client.loadbalancer.reactive;
|
||||
|
||||
import org.springframework.cloud.client.ServiceInstance;
|
||||
|
||||
/**
|
||||
* @author Spencer Gibb
|
||||
*/
|
||||
public class EmptyResponse implements Response<ServiceInstance> {
|
||||
|
||||
@Override
|
||||
public boolean hasServer() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ServiceInstance getServer() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onComplete(CompletionContext completionContext) {
|
||||
//TODO: implement
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
/*
|
||||
* Copyright 2013-2017 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.cloud.client.loadbalancer.reactive;
|
||||
|
||||
import org.reactivestreams.Publisher;
|
||||
|
||||
/**
|
||||
* @author Spencer Gibb
|
||||
*/
|
||||
public interface ReactiveLoadBalancer<T> {
|
||||
|
||||
Request REQUEST = new DefaultRequest();
|
||||
|
||||
/**
|
||||
* Choose the next server based on the load balancing algorithm
|
||||
* @param request
|
||||
* @return
|
||||
*/
|
||||
Publisher<Response<T>> choose(Request request);
|
||||
|
||||
default Publisher<Response<T>> choose() { //conflicting name
|
||||
return choose(REQUEST);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
package org.springframework.cloud.client.loadbalancer.reactive;
|
||||
|
||||
public interface Request {
|
||||
//TODO: define contents
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
package org.springframework.cloud.client.loadbalancer.reactive;
|
||||
|
||||
/**
|
||||
* Response created for each request.
|
||||
*/
|
||||
public interface Response<T> {
|
||||
boolean hasServer();
|
||||
|
||||
T getServer();
|
||||
|
||||
/**
|
||||
* Notification that the request completed
|
||||
* @param completionContext
|
||||
*/
|
||||
void onComplete(CompletionContext completionContext);
|
||||
}
|
||||
@@ -0,0 +1,116 @@
|
||||
/*
|
||||
* Copyright 2013-2018 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.cloud.context.named;
|
||||
|
||||
import org.springframework.beans.BeansException;
|
||||
import org.springframework.beans.factory.ObjectProvider;
|
||||
import org.springframework.lang.Nullable;
|
||||
|
||||
import java.util.Iterator;
|
||||
import java.util.Spliterator;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.function.Supplier;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
/**
|
||||
* Special ObjectProvider that allows the actual ObjectProvider to be resolved
|
||||
* later because of the creation of the named child context.
|
||||
* @param <T>
|
||||
*/
|
||||
class ClientFactoryObjectProvider<T> implements ObjectProvider<T> {
|
||||
|
||||
private final NamedContextFactory clientFactory;
|
||||
private final String name;
|
||||
private final Class<T> type;
|
||||
private ObjectProvider<T> provider;
|
||||
|
||||
public ClientFactoryObjectProvider(NamedContextFactory clientFactory, String name, Class<T> type) {
|
||||
this.clientFactory = clientFactory;
|
||||
this.name = name;
|
||||
this.type = type;
|
||||
}
|
||||
|
||||
@Override
|
||||
public T getObject(Object... args) throws BeansException {
|
||||
return delegate().getObject(args);
|
||||
}
|
||||
|
||||
@Override
|
||||
@Nullable
|
||||
public T getIfAvailable() throws BeansException {
|
||||
return delegate().getIfAvailable();
|
||||
}
|
||||
|
||||
@Override
|
||||
public T getIfAvailable(Supplier<T> defaultSupplier) throws BeansException {
|
||||
return delegate().getIfAvailable(defaultSupplier);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void ifAvailable(Consumer<T> dependencyConsumer) throws BeansException {
|
||||
delegate().ifAvailable(dependencyConsumer);
|
||||
}
|
||||
|
||||
@Override
|
||||
@Nullable
|
||||
public T getIfUnique() throws BeansException {
|
||||
return delegate().getIfUnique();
|
||||
}
|
||||
|
||||
@Override
|
||||
public T getIfUnique(Supplier<T> defaultSupplier) throws BeansException {
|
||||
return delegate().getIfUnique(defaultSupplier);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void ifUnique(Consumer<T> dependencyConsumer) throws BeansException {
|
||||
delegate().ifUnique(dependencyConsumer);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Iterator<T> iterator() {
|
||||
return delegate().iterator();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Stream<T> stream() {
|
||||
return delegate().stream();
|
||||
}
|
||||
|
||||
@Override
|
||||
public T getObject() throws BeansException {
|
||||
return delegate().getObject();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void forEach(Consumer<? super T> action) {
|
||||
delegate().forEach(action);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Spliterator<T> spliterator() {
|
||||
return delegate().spliterator();
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private ObjectProvider<T> delegate() {
|
||||
if (this.provider == null) {
|
||||
provider = this.clientFactory.getProvider(name, type);
|
||||
}
|
||||
return provider;
|
||||
}
|
||||
}
|
||||
@@ -11,10 +11,12 @@ import java.util.concurrent.ConcurrentHashMap;
|
||||
import org.springframework.beans.BeansException;
|
||||
import org.springframework.beans.factory.BeanFactoryUtils;
|
||||
import org.springframework.beans.factory.DisposableBean;
|
||||
import org.springframework.beans.factory.ObjectProvider;
|
||||
import org.springframework.boot.autoconfigure.context.PropertyPlaceholderAutoConfiguration;
|
||||
import org.springframework.context.ApplicationContext;
|
||||
import org.springframework.context.ApplicationContextAware;
|
||||
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
|
||||
import org.springframework.core.ResolvableType;
|
||||
import org.springframework.core.env.MapPropertySource;
|
||||
|
||||
/**
|
||||
@@ -26,6 +28,7 @@ import org.springframework.core.env.MapPropertySource;
|
||||
* @author Spencer Gibb
|
||||
* @author Dave Syer
|
||||
*/
|
||||
//TODO: add javadoc
|
||||
public abstract class NamedContextFactory<C extends NamedContextFactory.Specification>
|
||||
implements DisposableBean, ApplicationContextAware {
|
||||
|
||||
@@ -131,6 +134,35 @@ public abstract class NamedContextFactory<C extends NamedContextFactory.Specific
|
||||
return null;
|
||||
}
|
||||
|
||||
public <T> ObjectProvider<T> getLazyProvider(String name, Class<T> type) {
|
||||
return new ClientFactoryObjectProvider<>(this, name, type);
|
||||
}
|
||||
|
||||
public <T> ObjectProvider<T> getProvider(String name, Class<T> type) {
|
||||
AnnotationConfigApplicationContext context = getContext(name);
|
||||
return context.getBeanProvider(type);
|
||||
}
|
||||
|
||||
public <T> T getInstance(String name, Class<?> clazz, Class<?>... generics) {
|
||||
ResolvableType type = ResolvableType.forClassWithGenerics(clazz, generics);
|
||||
return getInstance(name, type);
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public <T> T getInstance(String name, ResolvableType type) {
|
||||
AnnotationConfigApplicationContext context = getContext(name);
|
||||
String[] beanNames = BeanFactoryUtils.beanNamesForTypeIncludingAncestors(context,
|
||||
type);
|
||||
if (beanNames.length > 0) {
|
||||
for (String beanName : beanNames) {
|
||||
if (context.isTypeMatch(beanName, type)) {
|
||||
return (T) context.getBean(beanName);
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public <T> Map<String, T> getInstances(String name, Class<T> type) {
|
||||
AnnotationConfigApplicationContext context = getContext(name);
|
||||
if (BeanFactoryUtils.beanNamesForTypeIncludingAncestors(context,
|
||||
|
||||
88
spring-cloud-loadbalancer/pom.xml
Normal file
88
spring-cloud-loadbalancer/pom.xml
Normal file
@@ -0,0 +1,88 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<parent>
|
||||
<groupId>org.springframework.cloud</groupId>
|
||||
<artifactId>spring-cloud-commons-parent</artifactId>
|
||||
<version>2.1.0.BUILD-SNAPSHOT</version>
|
||||
<relativePath>..</relativePath>
|
||||
</parent>
|
||||
<artifactId>spring-cloud-loadbalancer</artifactId>
|
||||
<packaging>jar</packaging>
|
||||
<name>Spring Cloud Load Balancer</name>
|
||||
<description>Spring Cloud Balancer</description>
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.springframework.cloud</groupId>
|
||||
<artifactId>spring-cloud-commons</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.cloud</groupId>
|
||||
<artifactId>spring-cloud-context</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-actuator</artifactId>
|
||||
<optional>true</optional>
|
||||
<!-- TODO: remove this when it's fixed in ModifiedClassPathRunner -->
|
||||
<exclusions>
|
||||
<exclusion>
|
||||
<groupId>org.apache.logging.log4j</groupId>
|
||||
<artifactId>log4j-to-slf4j</artifactId>
|
||||
</exclusion>
|
||||
</exclusions>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>io.projectreactor</groupId>
|
||||
<artifactId>reactor-core</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>io.projectreactor.addons</groupId>
|
||||
<artifactId>reactor-extra</artifactId>
|
||||
<!-- TODO: remove after next reactor milestone -->
|
||||
<version>3.2.0.M2</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-autoconfigure-processor</artifactId>
|
||||
<optional>true</optional>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-configuration-processor</artifactId>
|
||||
<optional>true</optional>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-webflux</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-cache</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-test</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.cloud</groupId>
|
||||
<artifactId>spring-cloud-test-support</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>io.projectreactor</groupId>
|
||||
<artifactId>reactor-test</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.github.ben-manes.caffeine</groupId>
|
||||
<artifactId>caffeine</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
</project>
|
||||
@@ -0,0 +1,67 @@
|
||||
/*
|
||||
* Copyright 2013-2018 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.cloud.loadbalancer.annotation;
|
||||
|
||||
import java.lang.annotation.Documented;
|
||||
import java.lang.annotation.ElementType;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
import org.springframework.cloud.loadbalancer.support.LoadBalancerClientFactory;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.context.annotation.Import;
|
||||
import org.springframework.core.annotation.AliasFor;
|
||||
|
||||
/**
|
||||
* Declarative configuration for a load balancer client. Add this annotation to any
|
||||
* <code>@Configuration</code> and then inject a {@link LoadBalancerClientFactory} to access the
|
||||
* client that is created.
|
||||
*
|
||||
* @author Dave Syer
|
||||
*/
|
||||
@Configuration
|
||||
@Import(LoadBalancerClientConfigurationRegistrar.class)
|
||||
@Target(ElementType.TYPE)
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Documented
|
||||
public @interface LoadBalancerClient {
|
||||
|
||||
/**
|
||||
* Synonym for name (the name of the client)
|
||||
*
|
||||
* @see #name()
|
||||
*/
|
||||
@AliasFor("name")
|
||||
String value() default "";
|
||||
|
||||
/**
|
||||
* The name of the load balancer client, uniquely identifying a set of client resources,
|
||||
* including a load balancer.
|
||||
*/
|
||||
@AliasFor("value")
|
||||
String name() default "";
|
||||
|
||||
/**
|
||||
* A custom <code>@Configuration</code> for the load balancer client. Can contain override
|
||||
* <code>@Bean</code> definition for the pieces that make up the client.
|
||||
*
|
||||
* @see LoadBalancerClientConfiguration for the defaults
|
||||
*/
|
||||
Class<?>[] configuration() default {};
|
||||
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
/*
|
||||
* Copyright 2013-2018 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.cloud.loadbalancer.annotation;
|
||||
|
||||
import org.springframework.beans.factory.ObjectProvider;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
|
||||
import org.springframework.boot.context.properties.EnableConfigurationProperties;
|
||||
import org.springframework.cache.CacheManager;
|
||||
import org.springframework.cloud.client.discovery.DiscoveryClient;
|
||||
import org.springframework.cloud.loadbalancer.core.CachingServiceInstanceSupplier;
|
||||
import org.springframework.cloud.loadbalancer.core.DiscoveryClientServiceInstanceSupplier;
|
||||
import org.springframework.cloud.loadbalancer.core.ServiceInstanceSupplier;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.core.env.Environment;
|
||||
|
||||
/**
|
||||
* @author Spencer Gibb
|
||||
*/
|
||||
@Configuration
|
||||
@EnableConfigurationProperties
|
||||
public class LoadBalancerClientConfiguration {
|
||||
|
||||
@Bean
|
||||
@ConditionalOnMissingBean
|
||||
public ServiceInstanceSupplier discoveryClientServiceInstanceSupplier(
|
||||
DiscoveryClient discoveryClient, Environment env, ObjectProvider<CacheManager> cacheManager) {
|
||||
//TODO: bean post processor to enable caching?
|
||||
DiscoveryClientServiceInstanceSupplier delegate = new DiscoveryClientServiceInstanceSupplier(discoveryClient, env);
|
||||
if (cacheManager.getIfAvailable() != null) {
|
||||
return new CachingServiceInstanceSupplier(delegate, cacheManager.getIfAvailable());
|
||||
}
|
||||
return delegate;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,88 @@
|
||||
/*
|
||||
* Copyright 2013-2018 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.cloud.loadbalancer.annotation;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
|
||||
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
|
||||
import org.springframework.context.annotation.ImportBeanDefinitionRegistrar;
|
||||
import org.springframework.core.annotation.AnnotationAttributes;
|
||||
import org.springframework.core.type.AnnotationMetadata;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
/**
|
||||
* @author Dave Syer
|
||||
*/
|
||||
public class LoadBalancerClientConfigurationRegistrar implements ImportBeanDefinitionRegistrar {
|
||||
|
||||
@Override
|
||||
public void registerBeanDefinitions(AnnotationMetadata metadata,
|
||||
BeanDefinitionRegistry registry) {
|
||||
Map<String, Object> attrs = metadata.getAnnotationAttributes(
|
||||
LoadBalancerClients.class.getName(), true);
|
||||
if (attrs != null && attrs.containsKey("value")) {
|
||||
AnnotationAttributes[] clients = (AnnotationAttributes[]) attrs.get("value");
|
||||
for (AnnotationAttributes client : clients) {
|
||||
registerClientConfiguration(registry, getClientName(client),
|
||||
client.get("configuration"));
|
||||
}
|
||||
}
|
||||
if (attrs != null && attrs.containsKey("defaultConfiguration")) {
|
||||
String name;
|
||||
if (metadata.hasEnclosingClass()) {
|
||||
name = "default." + metadata.getEnclosingClassName();
|
||||
} else {
|
||||
name = "default." + metadata.getClassName();
|
||||
}
|
||||
registerClientConfiguration(registry, name,
|
||||
attrs.get("defaultConfiguration"));
|
||||
}
|
||||
Map<String, Object> client = metadata.getAnnotationAttributes(
|
||||
LoadBalancerClient.class.getName(), true);
|
||||
String name = getClientName(client);
|
||||
if (name != null) {
|
||||
registerClientConfiguration(registry, name, client.get("configuration"));
|
||||
}
|
||||
}
|
||||
|
||||
private static String getClientName(Map<String, Object> client) {
|
||||
if (client == null) {
|
||||
return null;
|
||||
}
|
||||
String value = (String) client.get("value");
|
||||
if (!StringUtils.hasText(value)) {
|
||||
value = (String) client.get("name");
|
||||
}
|
||||
if (StringUtils.hasText(value)) {
|
||||
return value;
|
||||
}
|
||||
throw new IllegalStateException(
|
||||
"Either 'name' or 'value' must be provided in @LoadBalancerClient");
|
||||
}
|
||||
|
||||
private static void registerClientConfiguration(BeanDefinitionRegistry registry,
|
||||
Object name, Object configuration) {
|
||||
BeanDefinitionBuilder builder = BeanDefinitionBuilder
|
||||
.genericBeanDefinition(LoadBalancerClientSpecification.class);
|
||||
builder.addConstructorArgValue(name);
|
||||
builder.addConstructorArgValue(configuration);
|
||||
registry.registerBeanDefinition(name + ".LoadBalancerClientSpecification",
|
||||
builder.getBeanDefinition());
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,84 @@
|
||||
/*
|
||||
* Copyright 2013-2018 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.cloud.loadbalancer.annotation;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Objects;
|
||||
|
||||
import org.springframework.cloud.context.named.NamedContextFactory;
|
||||
import org.springframework.core.style.ToStringCreator;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
* @author Dave Syer
|
||||
*/
|
||||
public class LoadBalancerClientSpecification implements NamedContextFactory.Specification {
|
||||
|
||||
private String name;
|
||||
|
||||
private Class<?>[] configuration;
|
||||
|
||||
public LoadBalancerClientSpecification() {
|
||||
}
|
||||
|
||||
public LoadBalancerClientSpecification(String name, Class<?>[] configuration) {
|
||||
Assert.hasText(name, "name must not be empty");
|
||||
this.name = name;
|
||||
Assert.notNull(configuration, "configuration must not be null");
|
||||
this.configuration = configuration;
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
public void setName(String name) {
|
||||
Assert.hasText(name, "name must not be empty");
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
public Class<?>[] getConfiguration() {
|
||||
return configuration;
|
||||
}
|
||||
|
||||
public void setConfiguration(Class<?>[] configuration) {
|
||||
Assert.notNull(configuration, "configuration must not be null");
|
||||
this.configuration = configuration;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
ToStringCreator to = new ToStringCreator(this);
|
||||
to.append("name", name);
|
||||
to.append("configuration", configuration);
|
||||
return to.toString();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) return true;
|
||||
if (o == null || getClass() != o.getClass()) return false;
|
||||
LoadBalancerClientSpecification that = (LoadBalancerClientSpecification) o;
|
||||
return Objects.equals(name, that.name) &&
|
||||
Arrays.equals(configuration, that.configuration);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hash(name, configuration);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
/*
|
||||
* Copyright 2013-2018 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.cloud.loadbalancer.annotation;
|
||||
|
||||
import java.lang.annotation.Documented;
|
||||
import java.lang.annotation.ElementType;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
import org.springframework.cloud.loadbalancer.support.LoadBalancerClientFactory;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.context.annotation.Import;
|
||||
|
||||
/**
|
||||
* Convenience annotation that allows user to combine multiple <code>@LoadBalancerClient</code>
|
||||
* annotations on a single class (including in Java 7).
|
||||
*
|
||||
* @author Dave Syer
|
||||
*/
|
||||
@Configuration
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Target({ ElementType.TYPE })
|
||||
@Documented
|
||||
@Import(LoadBalancerClientConfigurationRegistrar.class)
|
||||
public @interface LoadBalancerClients {
|
||||
|
||||
LoadBalancerClient[] value() default {};
|
||||
|
||||
/**
|
||||
* {@link LoadBalancerClientConfigurationRegistrar} creates a {@link LoadBalancerClientSpecification}
|
||||
* with this as an argument. These in turn are added as default contexts in {@link LoadBalancerClientFactory}.
|
||||
* Configuration defined in these classes are used as defaults if values aren't defined via
|
||||
* {@link LoadBalancerClient#configuration()}
|
||||
*/
|
||||
Class<?>[] defaultConfiguration() default {};
|
||||
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
/*
|
||||
* Copyright 2013-2018 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.cloud.loadbalancer.config;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
import org.springframework.beans.factory.ObjectProvider;
|
||||
import org.springframework.cloud.loadbalancer.annotation.LoadBalancerClientSpecification;
|
||||
import org.springframework.cloud.loadbalancer.annotation.LoadBalancerClients;
|
||||
import org.springframework.cloud.loadbalancer.support.LoadBalancerClientFactory;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
|
||||
/**
|
||||
* @author Spencer Gibb
|
||||
*/
|
||||
@Configuration
|
||||
@LoadBalancerClients
|
||||
// @EnableCaching //TODO: how to enforce, or check conditions?
|
||||
// @AutoConfigureBefore(CacheAutoConfiguration.class)
|
||||
public class LoadBalancerAutoConfiguration {
|
||||
private final ObjectProvider<List<LoadBalancerClientSpecification>> configurations;
|
||||
|
||||
public LoadBalancerAutoConfiguration(ObjectProvider<List<LoadBalancerClientSpecification>> configurations) {
|
||||
this.configurations = configurations;
|
||||
}
|
||||
|
||||
@Bean
|
||||
public LoadBalancerClientFactory loadBalancerClientFactory() {
|
||||
LoadBalancerClientFactory clientFactory = new LoadBalancerClientFactory();
|
||||
clientFactory.setConfigurations(configurations.getIfAvailable(Collections::emptyList));
|
||||
return clientFactory;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,73 @@
|
||||
/*
|
||||
* Copyright 2013-2018 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.cloud.loadbalancer.core;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import reactor.cache.CacheFlux;
|
||||
import reactor.core.publisher.Flux;
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
import org.springframework.cache.Cache;
|
||||
import org.springframework.cache.CacheManager;
|
||||
import org.springframework.cloud.client.ServiceInstance;
|
||||
|
||||
/**
|
||||
* @author Spencer Gibb
|
||||
*/
|
||||
public class CachingServiceInstanceSupplier implements ServiceInstanceSupplier {
|
||||
|
||||
public static final String SERVICE_INSTANCE_CACHE_NAME = CachingServiceInstanceSupplier.class.getSimpleName()+"Cache";
|
||||
|
||||
private final ServiceInstanceSupplier delegate;
|
||||
private final Flux<ServiceInstance> serviceInstances;
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public CachingServiceInstanceSupplier(ServiceInstanceSupplier delegate, CacheManager cacheManager) {
|
||||
this.delegate = delegate;
|
||||
this.serviceInstances = CacheFlux.lookup(key -> {
|
||||
Cache cache = cacheManager.getCache(SERVICE_INSTANCE_CACHE_NAME); //TODO: configurable cache name
|
||||
List<ServiceInstance> list = cache.get(key, List.class);
|
||||
if (list == null || list.isEmpty()) {
|
||||
return Mono.empty();
|
||||
}
|
||||
return Flux.fromIterable(list)
|
||||
.materialize()
|
||||
.collectList();
|
||||
}, delegate.getServiceId())
|
||||
.onCacheMissResume(this.delegate::get)
|
||||
.andWriteWith((key, signals) -> Flux.fromIterable(signals)
|
||||
.dematerialize()
|
||||
.cast(ServiceInstance.class)
|
||||
.collectList()
|
||||
.doOnNext(instances -> {
|
||||
Cache cache = cacheManager.getCache(SERVICE_INSTANCE_CACHE_NAME);
|
||||
cache.put(key, instances);
|
||||
})
|
||||
.then());
|
||||
}
|
||||
|
||||
@Override
|
||||
public Flux<ServiceInstance> get() {
|
||||
return this.serviceInstances;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getServiceId() {
|
||||
return this.delegate.getServiceId();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
/*
|
||||
* Copyright 2013-2018 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.cloud.loadbalancer.core;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import reactor.core.publisher.Flux;
|
||||
|
||||
import org.springframework.cloud.client.ServiceInstance;
|
||||
import org.springframework.cloud.client.discovery.DiscoveryClient;
|
||||
import org.springframework.core.env.Environment;
|
||||
|
||||
import static org.springframework.cloud.loadbalancer.support.LoadBalancerClientFactory.PROPERTY_NAME;
|
||||
|
||||
/**
|
||||
* @author Spencer Gibb
|
||||
*/
|
||||
public class DiscoveryClientServiceInstanceSupplier implements ServiceInstanceSupplier {
|
||||
|
||||
private final DiscoveryClient delegate;
|
||||
private final String serviceId;
|
||||
|
||||
public DiscoveryClientServiceInstanceSupplier(DiscoveryClient delegate, Environment environment) {
|
||||
this.delegate = delegate;
|
||||
serviceId = environment.getProperty(PROPERTY_NAME);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Flux<ServiceInstance> get() {
|
||||
List<ServiceInstance> instances = delegate.getInstances(serviceId);
|
||||
return Flux.fromIterable(instances);
|
||||
}
|
||||
|
||||
public String getServiceId() {
|
||||
return this.serviceId;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
/*
|
||||
* Copyright 2013-2018 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.cloud.loadbalancer.core;
|
||||
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
import org.springframework.cloud.client.loadbalancer.reactive.ReactiveLoadBalancer;
|
||||
import org.springframework.cloud.client.loadbalancer.reactive.Request;
|
||||
import org.springframework.cloud.client.loadbalancer.reactive.Response;
|
||||
|
||||
public interface ReactorLoadBalancer<T> extends ReactiveLoadBalancer<T> {
|
||||
/**
|
||||
* Choose the next server based on the load balancing algorithm
|
||||
* @param request
|
||||
* @return
|
||||
*/
|
||||
Mono<Response<T>> choose(Request request);
|
||||
|
||||
default Mono<Response<T>> choose() {
|
||||
return choose(REQUEST);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,75 @@
|
||||
/*
|
||||
* Copyright 2013-2018 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.cloud.loadbalancer.core;
|
||||
|
||||
import java.util.Random;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
|
||||
import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
import org.springframework.beans.factory.ObjectProvider;
|
||||
import org.springframework.cloud.client.ServiceInstance;
|
||||
import org.springframework.cloud.client.loadbalancer.reactive.DefaultResponse;
|
||||
import org.springframework.cloud.client.loadbalancer.reactive.EmptyResponse;
|
||||
import org.springframework.cloud.client.loadbalancer.reactive.Request;
|
||||
import org.springframework.cloud.client.loadbalancer.reactive.Response;
|
||||
|
||||
/**
|
||||
* @author Spencer Gibb
|
||||
*/
|
||||
public class RoundRobinLoadBalancer implements ReactorLoadBalancer<ServiceInstance> {
|
||||
|
||||
private static final Log log = LogFactory.getLog(RoundRobinLoadBalancer.class);
|
||||
|
||||
private final AtomicInteger position;
|
||||
private final ObjectProvider<ServiceInstanceSupplier> serviceInstanceSupplier;
|
||||
private final String serviceId;
|
||||
|
||||
public RoundRobinLoadBalancer(String serviceId, ObjectProvider<ServiceInstanceSupplier> serviceInstanceSupplier) {
|
||||
this(serviceId, serviceInstanceSupplier, new Random().nextInt(1000));
|
||||
}
|
||||
|
||||
public RoundRobinLoadBalancer(String serviceId, ObjectProvider<ServiceInstanceSupplier> serviceInstanceSupplier,
|
||||
int seedPosition) {
|
||||
this.serviceId = serviceId;
|
||||
this.serviceInstanceSupplier = serviceInstanceSupplier;
|
||||
this.position = new AtomicInteger(seedPosition);
|
||||
}
|
||||
|
||||
@Override
|
||||
/**
|
||||
* see original https://github.com/Netflix/ocelli/blob/master/ocelli-core/src/main/java/netflix/ocelli/loadbalancer/RoundRobinLoadBalancer.java
|
||||
*/
|
||||
public Mono<Response<ServiceInstance>> choose(Request request) {
|
||||
// TODO: move supplier to Request?
|
||||
ServiceInstanceSupplier supplier = serviceInstanceSupplier.getIfAvailable();
|
||||
return supplier.get().collectList().map(instances -> {
|
||||
if (instances.isEmpty()) {
|
||||
log.warn("No servers available for service: " + this.serviceId);
|
||||
return new EmptyResponse();
|
||||
}
|
||||
// TODO: enforce order?
|
||||
int pos = Math.abs(position.incrementAndGet());
|
||||
|
||||
ServiceInstance instance = instances.get(pos % instances.size());
|
||||
|
||||
return new DefaultResponse(instance);
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
/*
|
||||
* Copyright 2013-2018 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.cloud.loadbalancer.core;
|
||||
|
||||
import java.util.function.Supplier;
|
||||
|
||||
import reactor.core.publisher.Flux;
|
||||
|
||||
import org.springframework.cloud.client.ServiceInstance;
|
||||
|
||||
/**
|
||||
* @author Spencer Gibb
|
||||
*/
|
||||
public interface ServiceInstanceSupplier extends Supplier<Flux<ServiceInstance>> {
|
||||
|
||||
String getServiceId();
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
/*
|
||||
* Copyright 2013-2018 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.cloud.loadbalancer.support;
|
||||
|
||||
import org.springframework.cloud.context.named.NamedContextFactory;
|
||||
import org.springframework.cloud.loadbalancer.annotation.LoadBalancerClientConfiguration;
|
||||
import org.springframework.cloud.loadbalancer.annotation.LoadBalancerClientSpecification;
|
||||
import org.springframework.core.env.Environment;
|
||||
|
||||
/**
|
||||
* A factory that creates client, load balancer and client configuration instances. It
|
||||
* creates a Spring ApplicationContext per client name, and extracts the beans that it
|
||||
* needs from there.
|
||||
*
|
||||
* @author Spencer Gibb
|
||||
* @author Dave Syer
|
||||
*/
|
||||
public class LoadBalancerClientFactory extends NamedContextFactory<LoadBalancerClientSpecification> {
|
||||
|
||||
public static final String NAMESPACE = "loadbalancer";
|
||||
public static final String PROPERTY_NAME = NAMESPACE + ".client.name";
|
||||
|
||||
public LoadBalancerClientFactory() {
|
||||
super(LoadBalancerClientConfiguration.class, NAMESPACE, PROPERTY_NAME);
|
||||
}
|
||||
|
||||
public String getName(Environment environment) {
|
||||
return environment.getProperty(PROPERTY_NAME);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,43 @@
|
||||
/*
|
||||
* Copyright 2013-2018 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.cloud.loadbalancer.support;
|
||||
|
||||
import org.springframework.beans.factory.ObjectProvider;
|
||||
import org.springframework.cloud.client.ServiceInstance;
|
||||
import org.springframework.cloud.loadbalancer.core.ServiceInstanceSupplier;
|
||||
import reactor.core.publisher.Flux;
|
||||
|
||||
public class ServiceInstanceSuppliers {
|
||||
|
||||
public static ServiceInstanceSupplier from(String serviceId, ServiceInstance... instances) {
|
||||
return new ServiceInstanceSupplier() {
|
||||
@Override
|
||||
public Flux<ServiceInstance> get() {
|
||||
return Flux.just(instances);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getServiceId() {
|
||||
return serviceId;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
public static ObjectProvider<ServiceInstanceSupplier> toProvider(String serviceId, ServiceInstance... instances) {
|
||||
return new SimpleObjectProvider<>(from(serviceId, instances));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
/*
|
||||
* Copyright 2013-2018 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.cloud.loadbalancer.support;
|
||||
|
||||
import org.springframework.beans.BeansException;
|
||||
import org.springframework.beans.factory.ObjectProvider;
|
||||
|
||||
public class SimpleObjectProvider<T> implements ObjectProvider<T> {
|
||||
|
||||
private final T object;
|
||||
|
||||
public SimpleObjectProvider(T object) {
|
||||
this.object = object;
|
||||
}
|
||||
|
||||
@Override
|
||||
public T getObject(Object... args) throws BeansException {
|
||||
return this.object;
|
||||
}
|
||||
|
||||
@Override
|
||||
public T getIfAvailable() throws BeansException {
|
||||
return this.object;
|
||||
}
|
||||
|
||||
@Override
|
||||
public T getIfUnique() throws BeansException {
|
||||
return this.object;
|
||||
}
|
||||
|
||||
@Override
|
||||
public T getObject() throws BeansException {
|
||||
return this.object;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
# AutoConfiguration
|
||||
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
|
||||
org.springframework.cloud.loadbalancer.config.LoadBalancerAutoConfiguration
|
||||
|
||||
@@ -0,0 +1,144 @@
|
||||
/*
|
||||
* Copyright 2013-2018 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.cloud.loadbalancer.core;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import reactor.core.publisher.Mono;
|
||||
import reactor.test.StepVerifier;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.boot.SpringBootConfiguration;
|
||||
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
|
||||
import org.springframework.boot.test.context.SpringBootTest;
|
||||
import org.springframework.cache.annotation.EnableCaching;
|
||||
import org.springframework.cloud.client.DefaultServiceInstance;
|
||||
import org.springframework.cloud.client.ServiceInstance;
|
||||
import org.springframework.cloud.client.loadbalancer.reactive.CompletionContext;
|
||||
import org.springframework.cloud.client.loadbalancer.reactive.CompletionContext.Status;
|
||||
import org.springframework.cloud.client.loadbalancer.reactive.ReactiveLoadBalancer;
|
||||
import org.springframework.cloud.client.loadbalancer.reactive.Response;
|
||||
import org.springframework.cloud.loadbalancer.annotation.LoadBalancerClient;
|
||||
import org.springframework.cloud.loadbalancer.annotation.LoadBalancerClients;
|
||||
import org.springframework.cloud.loadbalancer.support.LoadBalancerClientFactory;
|
||||
import org.springframework.cloud.loadbalancer.support.ServiceInstanceSuppliers;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.core.ResolvableType;
|
||||
import org.springframework.core.env.Environment;
|
||||
import org.springframework.test.context.junit4.SpringRunner;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
/**
|
||||
* @author Spencer Gibb
|
||||
*/
|
||||
@RunWith(SpringRunner.class)
|
||||
@SpringBootTest
|
||||
public class LoadBalancerTest {
|
||||
|
||||
@Autowired
|
||||
private LoadBalancerClientFactory clientFactory;
|
||||
|
||||
@Test
|
||||
public void roundRobbinLoadbalancerWorks() {
|
||||
ReactiveLoadBalancer<ServiceInstance> reactiveLoadBalancer = this.clientFactory
|
||||
.getInstance("myservice", ReactiveLoadBalancer.class, ServiceInstance.class);
|
||||
|
||||
assertThat(reactiveLoadBalancer).isInstanceOf(RoundRobinLoadBalancer.class);
|
||||
assertThat(reactiveLoadBalancer).isInstanceOf(ReactorLoadBalancer.class);
|
||||
ReactorLoadBalancer<ServiceInstance> loadBalancer = (ReactorLoadBalancer<ServiceInstance>) reactiveLoadBalancer;
|
||||
|
||||
//order dependent on seedPosition -1 of RoundRobinLoadBalancer
|
||||
List<String> hosts = Arrays.asList("a.host", "c.host", "b.host-secure", "a.host");
|
||||
|
||||
assertLoadBalancer(loadBalancer, hosts);
|
||||
}
|
||||
|
||||
private void assertLoadBalancer(ReactorLoadBalancer<ServiceInstance> loadBalancer, List<String> hosts) {
|
||||
for (String host : hosts) {
|
||||
Mono<Response<ServiceInstance>> source = loadBalancer.choose();
|
||||
StepVerifier.create(source).consumeNextWith(response -> {
|
||||
assertThat(response).isNotNull();
|
||||
assertThat(response.hasServer()).isTrue();
|
||||
|
||||
ServiceInstance instance = response.getServer();
|
||||
assertThat(instance).isNotNull();
|
||||
assertThat(instance.getHost())
|
||||
.as("instance host is incorrent %s", host)
|
||||
.isEqualTo(host);
|
||||
|
||||
if (host.contains("secure")) {
|
||||
assertThat(instance.isSecure()).isTrue();
|
||||
} else {
|
||||
assertThat(instance.isSecure()).isFalse();
|
||||
}
|
||||
|
||||
response.onComplete(new CompletionContext(Status.SUCCESSS));
|
||||
}).verifyComplete();
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void emptyHosts() {
|
||||
ResolvableType type = ResolvableType.forClassWithGenerics(ReactorLoadBalancer.class, ServiceInstance.class);
|
||||
ReactorLoadBalancer<ServiceInstance> loadBalancer = this.clientFactory.getInstance("unknownservice", type);
|
||||
|
||||
assertThat(loadBalancer).isInstanceOf(RoundRobinLoadBalancer.class);
|
||||
|
||||
Mono<Response<ServiceInstance>> source = loadBalancer.choose();
|
||||
StepVerifier.create(source).consumeNextWith(response -> {
|
||||
assertThat(response).isNotNull();
|
||||
assertThat(response.hasServer()).isFalse();
|
||||
}).verifyComplete();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void staticConfigurationWorks() {
|
||||
String serviceId = "test1";
|
||||
RoundRobinLoadBalancer loadBalancer = new RoundRobinLoadBalancer(serviceId,
|
||||
ServiceInstanceSuppliers.toProvider(serviceId, instance(serviceId, "1.host", false),
|
||||
instance(serviceId, "2.host-secure", true)),
|
||||
-1);
|
||||
assertLoadBalancer(loadBalancer, Arrays.asList("1.host", "2.host-secure"));
|
||||
}
|
||||
|
||||
private DefaultServiceInstance instance(String serviceId, String host, boolean secure) {
|
||||
return new DefaultServiceInstance(serviceId, host, 80, secure);
|
||||
}
|
||||
|
||||
@EnableAutoConfiguration
|
||||
@SpringBootConfiguration
|
||||
@LoadBalancerClients({
|
||||
@LoadBalancerClient(name = "myservice", configuration = MyServiceConfig.class),
|
||||
@LoadBalancerClient(name = "unknownservice", configuration = MyServiceConfig.class),
|
||||
})
|
||||
@EnableCaching
|
||||
protected static class Config { }
|
||||
|
||||
protected static class MyServiceConfig {
|
||||
@Bean
|
||||
public RoundRobinLoadBalancer roundRobinContextLoadBalancer(LoadBalancerClientFactory clientFactory, Environment env) {
|
||||
String serviceId = clientFactory.getName(env);
|
||||
return new RoundRobinLoadBalancer(serviceId,
|
||||
clientFactory.getLazyProvider(serviceId, ServiceInstanceSupplier.class),
|
||||
-1);
|
||||
}
|
||||
}
|
||||
}
|
||||
36
spring-cloud-loadbalancer/src/test/resources/application.yml
Normal file
36
spring-cloud-loadbalancer/src/test/resources/application.yml
Normal file
@@ -0,0 +1,36 @@
|
||||
spring:
|
||||
cloud:
|
||||
discovery:
|
||||
client:
|
||||
simple:
|
||||
instances:
|
||||
myservice:
|
||||
-
|
||||
service-id: myservice
|
||||
uri: http://a.host
|
||||
-
|
||||
service-id: myservice
|
||||
uri: http://c.host
|
||||
-
|
||||
service-id: myservice
|
||||
uri: https://b.host-secure
|
||||
anotherservice:
|
||||
-
|
||||
service-id: myservice
|
||||
uri: http://d.host
|
||||
-
|
||||
service-id: myservice
|
||||
uri: http://f.host
|
||||
-
|
||||
service-id: myservice
|
||||
uri: https://e.host
|
||||
thirdservice:
|
||||
-
|
||||
service-id: myservice
|
||||
uri: http://g.host
|
||||
-
|
||||
service-id: myservice
|
||||
uri: http://h.host
|
||||
-
|
||||
service-id: myservice
|
||||
uri: https://i.host
|
||||
Reference in New Issue
Block a user