open feign3.0开始就不支持Ribbon了
所以只能用loadbalancer
spring-cloud-loadbalancer 官网文档
引入
org.springframework.cloud
spring-cloud-starter-loadbalancer
3.0.2
首先说明一下
spring cloud loadbalancer 是可以单独使用的
但通常我们会和consul等注册中心一起使用
这样我们就不用写死配置了(配置集群里面有哪些服务)
还有这个版本与OpenFeign 和 consul配合使用是不需要做任何配置的
并且spring-cloud-loadbalancer 是 spring-cloud-commons的一个子项目
spring-cloud-loadbalancer
spring.factories
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
org.springframework.cloud.loadbalancer.config.LoadBalancerAutoConfiguration,\
org.springframework.cloud.loadbalancer.config.BlockingLoadBalancerClientAutoConfiguration,\
org.springframework.cloud.loadbalancer.config.LoadBalancerCacheAutoConfiguration,\
org.springframework.cloud.loadbalancer.security.OAuth2LoadBalancerClientAutoConfiguration,\
org.springframework.cloud.loadbalancer.config.LoadBalancerStatsAutoConfiguration
LoadBalancerAutoConfiguration
功能:
- 1 接受一个客户端配置列表,用consul这里为空
- 2 设置区域,目前只有Eureka 可以自动检测,consul需要手动设置,负载均衡器会优先在同一个zone下找其他服务器实例,如果只有一个,则去其他zone里找
- 3 生成LoadBalancerClientFactory 对象,看名字就知道 用来生产LoadBalancerClient的
@Configuration(proxyBeanMethods = false)
@LoadBalancerClients
@EnableConfigurationProperties(LoadBalancerProperties.class)
@AutoConfigureBefore({ ReactorLoadBalancerClientAutoConfiguration.class,
LoadBalancerBeanPostProcessorAutoConfiguration.class })
public class LoadBalancerAutoConfiguration {
private final ObjectProvider> configurations;
public LoadBalancerAutoConfiguration(ObjectProvider> configurations) {
this.configurations = configurations;
}
@Bean
@ConditionalOnMissingBean
public LoadBalancerZoneConfig zoneConfig(Environment environment) {
return new LoadBalancerZoneConfig(environment.getProperty("spring.cloud.loadbalancer.zone"));
}
@ConditionalOnMissingBean
@Bean
public LoadBalancerClientFactory loadBalancerClientFactory() {
LoadBalancerClientFactory clientFactory = new LoadBalancerClientFactory();
clientFactory.setConfigurations(this.configurations.getIfAvailable(Collections::emptyList));
return clientFactory;
}
}
BlockingLoadBalancerClientAutoConfiguration
首先在spring-cloud-commons包下也有个叫LoadBalancerAutoConfiguration的配置类,这个配置类会在它前面执行
功能:
- 1 生产BlockingLoadBalancerClient对象
- 2 生产LoadBalancerServiceInstanceCookieTransformer对象:用来在发送请求时,通过cookie传递实例serviceId
- 3 生产BlockingLoadBalancedRetryFactory对象
@Configuration(proxyBeanMethods = false)
@LoadBalancerClients
@AutoConfigureAfter(LoadBalancerAutoConfiguration.class)
@AutoConfigureBefore({ org.springframework.cloud.client.loadbalancer.LoadBalancerAutoConfiguration.class,
AsyncLoadBalancerAutoConfiguration.class })
@ConditionalOnClass(RestTemplate.class)
public class BlockingLoadBalancerClientAutoConfiguration {
@Bean
@ConditionalOnBean(LoadBalancerClientFactory.class)
@ConditionalOnMissingBean
public LoadBalancerClient blockingLoadBalancerClient(LoadBalancerClientFactory loadBalancerClientFactory,
LoadBalancerProperties properties) {
return new BlockingLoadBalancerClient(loadBalancerClientFactory, properties);
}
@Bean
@ConditionalOnProperty(value = "spring.cloud.loadbalancer.sticky-session.add-service-instance-cookie",
havingValue = "true")
@ConditionalOnMissingBean(LoadBalancerServiceInstanceCookieTransformer.class)
public LoadBalancerServiceInstanceCookieTransformer loadBalancerServiceInstanceCookieTransformer(
LoadBalancerProperties properties) {
return new LoadBalancerServiceInstanceCookieTransformer(properties.getStickySession());
}
@Configuration
@ConditionalOnClass(RetryTemplate.class)
@EnableConfigurationProperties(LoadBalancerProperties.class)
protected static class BlockingLoadBalancerRetryConfig {
@Bean
@ConditionalOnMissingBean
LoadBalancedRetryFactory loadBalancedRetryFactory(LoadBalancerProperties properties) {
return new BlockingLoadBalancedRetryFactory(properties);
}
}
}
BlockingLoadBalancerClient
请先读一下这篇文章:spring boot open feign 客户端调用过程
会知道OpenFeign发起请求前会调用BlockingLoadBalancerClient.choose选择一个服务端
上面也提到了BlockingLoadBalancerClient 它的加载过程。
接下来看看它的 实现
public class BlockingLoadBalancerClient implements LoadBalancerClient {
private final LoadBalancerClientFactory loadBalancerClientFactory;
private final LoadBalancerProperties properties;
public BlockingLoadBalancerClient(LoadBalancerClientFactory loadBalancerClientFactory,
LoadBalancerProperties properties) {
this.loadBalancerClientFactory = loadBalancerClientFactory;
this.properties = properties;
}
。。。
@Override
public ServiceInstance choose(String serviceId) {
return choose(serviceId, REQUEST);
}
@Override
public ServiceInstance choose(String serviceId, Request request) {
ReactiveLoadBalancer loadBalancer = loadBalancerClientFactory.getInstance(serviceId);
if (loadBalancer == null) {
return null;
}
Response loadBalancerResponse = Mono.from(loadBalancer.choose(request)).block();
if (loadBalancerResponse == null) {
return null;
}
return loadBalancerResponse.getServer();
}
private String getHint(String serviceId) {
String defaultHint = properties.getHint().getOrDefault("default", "default");
String hintPropertyValue = properties.getHint().get(serviceId);
return hintPropertyValue != null ? hintPropertyValue : defaultHint;
}
}
从 loadBalancerClientFactory 里取得服务列表
但我们提到了,我们并没有主动的创建服务器列表。
而是通过consul取得的,那么是什么时候取得的呢?
读一下:spring boot consul 客户端加载过程
我们能知道 consul是在web服务器启动完成后,才向注册中心发起注册的
也就是在这之前LoadBalancerClientFactory 一直是空的
所以是调用choose方法的时候才去拉取consul 上的服务器列表
所以我们看下:ReactiveLoadBalancer.choose
它是一个接口,看一下它有哪些实现
共两个实现:
1,RoundRobinLoadBalancer
2,RandomLoadBalancer
这里的ReactiveLoadBalancer实际上是RoundRobinLoadBalancer(默认的)
但我们在
spring-cloud-loadbalancer的spring.factories
并没有发现RoundRobinLoadBalancer的初始化
不过我们发现它是从
loadBalancerClientFactory里取出来的
ReactiveLoadBalancer loadBalancer = loadBalancerClientFactory.getInstance(serviceId);
LoadBalancerClientFactory
继承自:NamedContextFactory:真正创建ReactiveLoadBalancer对象
实现了:ReactiveLoadBalancer.Factory。看名字就知道,它就是用来创建ReactiveLoadBalancer 的,但它是委托给了NamedContextFactory 来做的
public class LoadBalancerClientFactory extends NamedContextFactory
implements ReactiveLoadBalancer.Factory {
/**
* Property source name for load balancer.
*/
public static final String NAMESPACE = "loadbalancer";
/**
* Property for client name within the load balancer namespace.
*/
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);
}
@Override
public ReactiveLoadBalancer getInstance(String serviceId) {
return getInstance(serviceId, ReactorServiceInstanceLoadBalancer.class);
}
}
NamedContextFactory
看一下NamedContextFactory是怎么做的
public abstract class NamedContextFactory
implements DisposableBean, ApplicationContextAware {
。。。
public NamedContextFactory(Class> defaultConfigType, String propertySourceName, String propertyName) {
this.defaultConfigType = defaultConfigType;
this.propertySourceName = propertySourceName;
this.propertyName = propertyName;
}
protected AnnotationConfigApplicationContext getContext(String name) {
if (!this.contexts.containsKey(name)) {
synchronized (this.contexts) {
if (!this.contexts.containsKey(name)) {
this.contexts.put(name, createContext(name));
}
}
}
return this.contexts.get(name);
}
protected AnnotationConfigApplicationContext createContext(String name) {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
if (this.configurations.containsKey(name)) {
for (Class> configuration : this.configurations.get(name).getConfiguration()) {
context.register(configuration);
}
}
for (Map.Entry entry : this.configurations.entrySet()) {
if (entry.getKey().startsWith("default.")) {
for (Class> configuration : entry.getValue().getConfiguration()) {
context.register(configuration);
}
}
}
context.register(PropertyPlaceholderAutoConfiguration.class, this.defaultConfigType);
context.getEnvironment().getPropertySources().addFirst(new MapPropertySource(this.propertySourceName,
Collections.singletonMap(this.propertyName, name)));
if (this.parent != null) {
// Uses Environment from parent as well as beans
context.setParent(this.parent);
// jdk11 issue
// https://github.com/spring-cloud/spring-cloud-netflix/issues/3101
context.setClassLoader(this.parent.getClassLoader());
}
context.setDisplayName(generateDisplayName(name));
context.refresh();
return context;
}
public T getInstance(String name, Class type) {
AnnotationConfigApplicationContext context = getContext(name);
try {
return context.getBean(type);
}
catch (NoSuchBeanDefinitionException e) {
// ignore
}
return null;
}
。。。
}
可以看到
先getContext(name)
没有的化就createContext
然后就是这句
context.register(PropertyPlaceholderAutoConfiguration.class, this.defaultConfigType);
defaultConfigType是LoadBalancerClientConfiguration
context将LoadBalancerClientConfiguration注册到上下文环境中
然后context.refresh();
LoadBalancerClientConfiguration的对象就创建出来了(spring.fatories里是没有它的)
@Configuration(proxyBeanMethods = false)
@ConditionalOnDiscoveryEnabled
public class LoadBalancerClientConfiguration {
private static final int REACTIVE_SERVICE_INSTANCE_SUPPLIER_ORDER = 193827465;
@Bean
@ConditionalOnMissingBean
public ReactorLoadBalancer reactorServiceInstanceLoadBalancer(Environment environment,
LoadBalancerClientFactory loadBalancerClientFactory) {
String name = environment.getProperty(LoadBalancerClientFactory.PROPERTY_NAME);
return new RoundRobinLoadBalancer(
loadBalancerClientFactory.getLazyProvider(name, ServiceInstanceListSupplier.class), name);
}
@Configuration(proxyBeanMethods = false)
@ConditionalOnBlockingDiscoveryEnabled
@Order(REACTIVE_SERVICE_INSTANCE_SUPPLIER_ORDER + 1)
public static class BlockingSupportConfiguration {
@Bean
@ConditionalOnBean(DiscoveryClient.class)
@ConditionalOnMissingBean
@ConditionalOnProperty(value = "spring.cloud.loadbalancer.configurations", havingValue = "default",
matchIfMissing = true)
public ServiceInstanceListSupplier discoveryClientServiceInstanceListSupplier(
ConfigurableApplicationContext context) {
return ServiceInstanceListSupplier.builder().withBlockingDiscoveryClient().withCaching().build(context);
}
}
。。。
}
可以看到 这里创建了 RoundRobinLoadBalancer
为什么要用NamedContextFactory这样创建
因为,道理很简单,spring-cloud-balancer要为每一个rpc客户端创建一个自己的上下文环境包含自己的配置
用服务名去取,这个serviceId在OpenFeign, Loadbalancer,Consul里是一致的
然后这里还创建了ServiceInstanceListSupplier
看这句,创建这个类的时候用到了DiscoveryClient,还把它们缓存了起来
这里context:ConfigurableApplicationContext包含ConsuDiscoveryClient
ServiceInstanceListSupplier.builder().withBlockingDiscoveryClient().withCaching().build(context)
ServiceInstanceListSupplierBuilder
public final class ServiceInstanceListSupplierBuilder {
private static final Log LOG = LogFactory.getLog(ServiceInstanceListSupplierBuilder.class);
private Creator baseCreator;
private DelegateCreator cachingCreator;
private final List creators = new ArrayList<>();
ServiceInstanceListSupplierBuilder() {
}
/**
* Sets a blocking {@link DiscoveryClient}-based
* {@link DiscoveryClientServiceInstanceListSupplier} as a base
* {@link ServiceInstanceListSupplier} in the hierarchy.
* @return the {@link ServiceInstanceListSupplierBuilder} object
*/
public ServiceInstanceListSupplierBuilder withBlockingDiscoveryClient() {
if (baseCreator != null && LOG.isWarnEnabled()) {
LOG.warn("Overriding a previously set baseCreator with a blocking DiscoveryClient baseCreator.");
}
this.baseCreator = context -> {
DiscoveryClient discoveryClient = context.getBean(DiscoveryClient.class);
return new DiscoveryClientServiceInstanceListSupplier(discoveryClient, context.getEnvironment());
};
return this;
}
/**
* If {@link LoadBalancerCacheManager} is available in the context, wraps created
* {@link ServiceInstanceListSupplier} hierarchy with a
* {@link CachingServiceInstanceListSupplier} instance to provide a caching mechanism
* for service instances. Uses {@link ObjectProvider} to lazily resolve
* {@link LoadBalancerCacheManager}.
* @return the {@link ServiceInstanceListSupplierBuilder} object
*/
public ServiceInstanceListSupplierBuilder withCaching() {
if (cachingCreator != null && LOG.isWarnEnabled()) {
LOG.warn(
"Overriding a previously set cachingCreator with a CachingServiceInstanceListSupplier-based cachingCreator.");
}
this.cachingCreator = (context, delegate) -> {
ObjectProvider cacheManagerProvider = context
.getBeanProvider(LoadBalancerCacheManager.class);
if (cacheManagerProvider.getIfAvailable() != null) {
return new CachingServiceInstanceListSupplier(delegate, cacheManagerProvider.getIfAvailable());
}
if (LOG.isWarnEnabled()) {
LOG.warn("LoadBalancerCacheManager not available, returning delegate without caching.");
}
return delegate;
};
return this;
}
/**
* Builds the {@link ServiceInstanceListSupplier} hierarchy.
* @param context application context
* @return a {@link ServiceInstanceListSupplier} instance on top of the delegate
* hierarchy
*/
public ServiceInstanceListSupplier build(ConfigurableApplicationContext context) {
Assert.notNull(baseCreator, "A baseCreator must not be null");
ServiceInstanceListSupplier supplier = baseCreator.apply(context);
for (DelegateCreator creator : creators) {
supplier = creator.apply(context, supplier);
}
if (this.cachingCreator != null) {
supplier = this.cachingCreator.apply(context, supplier);
}
return supplier;
}
。。。
}
这里用到了BiFunciton,大家可以自己去查一下怎么用
DiscoveryClientServiceInstanceListSupplier
这里还用到了Flux
大家可以自己去看
public class DiscoveryClientServiceInstanceListSupplier implements ServiceInstanceListSupplier {
/**
* Property that establishes the timeout for calls to service discovery.
*/
public static final String SERVICE_DISCOVERY_TIMEOUT = "spring.cloud.loadbalancer.service-discovery.timeout";
private static final Log LOG = LogFactory.getLog(DiscoveryClientServiceInstanceListSupplier.class);
private Duration timeout = Duration.ofSeconds(30);
private final String serviceId;
private final Flux> serviceInstances;
public DiscoveryClientServiceInstanceListSupplier(DiscoveryClient delegate, Environment environment) {
this.serviceId = environment.getProperty(PROPERTY_NAME);
resolveTimeout(environment);
this.serviceInstances = Flux.defer(() -> Flux.just(delegate.getInstances(serviceId)))
.subscribeOn(Schedulers.boundedElastic()).timeout(timeout, Flux.defer(() -> {
logTimeout();
return Flux.just(new ArrayList<>());
})).onErrorResume(error -> {
logException(error);
return Flux.just(new ArrayList<>());
});
}
。。。
@Override
public Flux> get() {
return serviceInstances;
}
。。。
}
主要是这句
delegate.getInstances(serviceId)
这里的delegate就是ConsulDiscoveryClient
然后就回到RoundRobinLoadBalancer
这个类其实很简单,就是取到服务器列表,然后一个简单的轮询
public class RoundRobinLoadBalancer implements ReactorServiceInstanceLoadBalancer {
private static final Log log = LogFactory.getLog(RoundRobinLoadBalancer.class);
final AtomicInteger position;
final String serviceId;
ObjectProvider serviceInstanceListSupplierProvider;
/**
* @param serviceInstanceListSupplierProvider a provider of
* {@link ServiceInstanceListSupplier} that will be used to get available instances
* @param serviceId id of the service for which to choose an instance
*/
public RoundRobinLoadBalancer(ObjectProvider serviceInstanceListSupplierProvider,
String serviceId) {
this(serviceInstanceListSupplierProvider, serviceId, new Random().nextInt(1000));
}
/**
* @param serviceInstanceListSupplierProvider a provider of
* {@link ServiceInstanceListSupplier} that will be used to get available instances
* @param serviceId id of the service for which to choose an instance
* @param seedPosition Round Robin element position marker
*/
public RoundRobinLoadBalancer(ObjectProvider serviceInstanceListSupplierProvider,
String serviceId, int seedPosition) {
this.serviceId = serviceId;
this.serviceInstanceListSupplierProvider = serviceInstanceListSupplierProvider;
this.position = new AtomicInteger(seedPosition);
}
@SuppressWarnings("rawtypes")
@Override
// see original
// https://github.com/Netflix/ocelli/blob/master/ocelli-core/
// src/main/java/netflix/ocelli/loadbalancer/RoundRobinLoadBalancer.java
public Mono> choose(Request request) {
ServiceInstanceListSupplier supplier = serviceInstanceListSupplierProvider
.getIfAvailable(NoopServiceInstanceListSupplier::new);
return supplier.get(request).next()
.map(serviceInstances -> processInstanceResponse(supplier, serviceInstances));
}
private Response processInstanceResponse(ServiceInstanceListSupplier supplier,
List serviceInstances) {
Response serviceInstanceResponse = getInstanceResponse(serviceInstances);
if (supplier instanceof SelectedInstanceCallback && serviceInstanceResponse.hasServer()) {
((SelectedInstanceCallback) supplier).selectedServiceInstance(serviceInstanceResponse.getServer());
}
return serviceInstanceResponse;
}
private Response getInstanceResponse(List instances) {
if (instances.isEmpty()) {
if (log.isWarnEnabled()) {
log.warn("No servers available for service: " + serviceId);
}
return new EmptyResponse();
}
// TODO: enforce order?
int pos = Math.abs(this.position.incrementAndGet());
ServiceInstance instance = instances.get(pos % instances.size());
return new DefaultResponse(instance);
}
}