本系列代码地址:https://github.com/HashZhang/spring-cloud-scaffold/tree/master/spring-cloud-iiford

经过上一节的详细分析,我们知道可以通过 LoadBalancerClientFactory 知道默认配置类LoadBalancerClientConfiguration. 并且获取微服务名称可以通过 environment.getProperty(LoadBalancerClientFactory.PROPERTY_NAME);

LoadBalancerClientFactory

public static final String NAMESPACE = "loadbalancer";
public static final String PROPERTY_NAME = NAMESPACE + ".client.name";
public LoadBalancerClientFactory() {
super(LoadBalancerClientConfiguration.class, NAMESPACE, PROPERTY_NAME);
}

查看配置类 LoadBalancerClientConfiguration,我们可以发现这个类主要定义两种 Bean,分别是 ReactorLoadBalancer<ServiceInstance>ServiceInstanceListSupplier

ReactorLoadBalancer 是负载均衡器,主要提供根据服务名称获取服务实例列表并从从中选择的功能。

ReactorLoadBalancer

Mono<Response<T>> choose(Request request);

在默认配置中的实现是:

LoadBalancerClientConfiguration

@Bean
@ConditionalOnMissingBean
public ReactorLoadBalancer<ServiceInstance> reactorServiceInstanceLoadBalancer(
Environment environment,
LoadBalancerClientFactory loadBalancerClientFactory) {
//获取微服务名称
String name = environment.getProperty(LoadBalancerClientFactory.PROPERTY_NAME);
//创建 RoundRobinLoadBalancer
//注意这里注入的是 LazyProvider,这主要因为在注册这个 Bean 的时候相关的 Bean 可能还没有被加载注册,利用 LazyProvider 而不是直接注入所需的 Bean 防止报找不到 Bean 注入的错误。
return new RoundRobinLoadBalancer(loadBalancerClientFactory.getLazyProvider(name,
ServiceInstanceListSupplier.class), name);
}

可以看出,默认配置的 ReactorLoadBalancer 实现是 RoundRobinLoadBalancer。这个负载均衡器实现很简单,有一个原子类型的 AtomicInteger position,从 ServiceInstanceListSupplier 中读取所有的服务实例列表,然后对于 position 原子加1,对列表大小取模,返回列表中这个位置的服务实例 ServiceInstance

RoundRobinLoadBalancer

public Mono<Response<ServiceInstance>> choose(Request request) {
//注入的时候注入的是 Lazy Provider,这里取出真正的 Bean,也就是 ServiceInstanceListSupplier
ServiceInstanceListSupplier supplier = serviceInstanceListSupplierProvider
.getIfAvailable(NoopServiceInstanceListSupplier::new);
//获取实例列表
return supplier.get(request)
.next()
//从列表中选择一个实例
.map(serviceInstances -> processInstanceResponse(supplier, serviceInstances));
} private Response<ServiceInstance> processInstanceResponse(ServiceInstanceListSupplier supplier,
List<ServiceInstance> serviceInstances) {
Response<ServiceInstance> serviceInstanceResponse = getInstanceResponse(serviceInstances);
// 如果 ServiceInstanceListSupplier 也实现了 SelectedInstanceCallback,则执行下面的逻辑进行回调。SelectedInstanceCallback 就是每次负载均衡器选择实例之后进行的回调
if (supplier instanceof SelectedInstanceCallback && serviceInstanceResponse.hasServer()) {
((SelectedInstanceCallback) supplier).selectedServiceInstance(serviceInstanceResponse.getServer());
}
return serviceInstanceResponse;
} private Response<ServiceInstance> getInstanceResponse(List<ServiceInstance> instances) {
if (instances.isEmpty()) {
return new EmptyResponse();
}
//postion 原子 +1 并取绝对值
int pos = Math.abs(this.position.incrementAndGet());
//返回对应下标的实例
ServiceInstance instance = instances.get(pos % instances.size());
return new DefaultResponse(instance);
}

ServiceInstanceListSupplier 是服务列表提供者接口:

ServiceInstanceListSupplier

public interface ServiceInstanceListSupplier extends Supplier<Flux<List<ServiceInstance>>> {
String getServiceId();
default Flux<List<ServiceInstance>> get(Request request) {
return get();
}
static ServiceInstanceListSupplierBuilder builder() {
return new ServiceInstanceListSupplierBuilder();
}
}

spring-cloud-loadbalancer 中有很多 ServiceInstanceListSupplier 的实现,在默认配置中是通过属性配置指定实现的,这个配置项是spring.cloud.loadbalancer.configurations。例如:

LoadBalancerClientConfiguration

@Bean
@ConditionalOnBean(ReactiveDiscoveryClient.class)
@ConditionalOnMissingBean
//spring.cloud.loadbalancer.configurations 未指定或者为 default
@ConditionalOnProperty(value = "spring.cloud.loadbalancer.configurations", havingValue = "default",
matchIfMissing = true)
public ServiceInstanceListSupplier discoveryClientServiceInstanceListSupplier(
ConfigurableApplicationContext context) {
return ServiceInstanceListSupplier.builder()
//通过 DiscoveryClient 提供实例
.withDiscoveryClient()
//开启缓存
.withCaching()
.build(context);
} @Bean
@ConditionalOnBean(ReactiveDiscoveryClient.class)
@ConditionalOnMissingBean
//如果 spring.cloud.loadbalancer.configurations 指定为 zone-preference
@ConditionalOnProperty(value = "spring.cloud.loadbalancer.configurations", havingValue = "zone-preference")
public ServiceInstanceListSupplier zonePreferenceDiscoveryClientServiceInstanceListSupplier(
ConfigurableApplicationContext context) {
return ServiceInstanceListSupplier.builder()
//通过 DiscoveryClient 提供实例
.withDiscoveryClient()
//启用更倾向于同一个 zone 下实例的特性
.withZonePreference()
//开启缓存
.withCaching()
.build(context);
}

可以看到,可以通过 ServiceInstanceListSupplier.builder() 生成官方封装好各种特性的 ServiceInstanceListSupplier。其实从底层实现可以看出,所有的 ServiceInstanceListSupplier 实现都是代理模式,例如对于默认配置,底层代码近似于:

return  //开启服务实例缓存
new CachingServiceInstanceListSupplier(
//启用通过 discoveryClient 的服务发现
new DiscoveryClientServiceInstanceListSupplier(
discoveryClient, env
)
, cacheManagerProvider.getIfAvailable()
);

除了默认配置 LoadBalancerClientConfiguration,用户配置自定义配置则是通过 @LoadBalancerClients@LoadBalancerClient.这个原理是通过 LoadBalancerClientConfigurationRegistrar 实现的。首先,我们来看一下 LoadBalancerClientFactory 这个 NamedContextFactory 是如何创建的:

[LoadBalancerAutoConfiguration]

private final ObjectProvider<List<LoadBalancerClientSpecification>> configurations;

public LoadBalancerAutoConfiguration(ObjectProvider<List<LoadBalancerClientSpecification>> configurations) {
//注入 LoadBalancerClientSpecification List 的 provider
//在 Bean 创建的时候,进行载入,而不是注册的时候
this.configurations = configurations;
} @ConditionalOnMissingBean
@Bean
public LoadBalancerClientFactory loadBalancerClientFactory() {
//创建 LoadBalancerClientFactory
LoadBalancerClientFactory clientFactory = new LoadBalancerClientFactory();
//读取所有的 LoadBalancerClientSpecification,设置为 LoadBalancerClientFactory 的配置
clientFactory.setConfigurations(this.configurations.getIfAvailable(Collections::emptyList));
return clientFactory;
}

那么,LoadBalancerClientSpecification 这些 Bean 是怎么创建的呢?在 @LoadBalancerClients@LoadBalancerClient 注解中,都包含 @Import(LoadBalancerClientConfigurationRegistrar.class)。这个 @Import 加载一个 ImportBeanDefinitionRegistrar,这里是 LoadBalancerClientConfigurationRegistrar. ImportBeanDefinitionRegistrar里面的方法参数包含注解元数据,以及注册 Bean 的 BeanDefinitionRegistry。一般通过注解元数据,动态通过 BeanDefinitionRegistry 注册 Bean,在这里的实现是:

[LoadBalancerClients]

@Configuration(proxyBeanMethods = false)
@Retention(RetentionPolicy.RUNTIME)
@Target({ ElementType.TYPE })
@Documented
@Import(LoadBalancerClientConfigurationRegistrar.class)
public @interface LoadBalancerClients {
//可以指定多个 LoadBalancerClient
LoadBalancerClient[] value() default {};
//指定所有的负载均衡配置的默认配置
Class<?>[] defaultConfiguration() default {};
}

[LoadBalancerClient]

@Configuration(proxyBeanMethods = false)
@Import(LoadBalancerClientConfigurationRegistrar.class)
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface LoadBalancerClient {
//name 和 value 都是微服务名称
@AliasFor("name")
String value() default "";
@AliasFor("value")
String name() default "";
//这个微服务的配置
Class<?>[] configuration() default {};
}

[LoadBalancerClientConfigurationRegistrar]

@Override
public void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {
//获取 LoadBalancerClients 注解的元数据
Map<String, Object> attrs = metadata.getAnnotationAttributes(LoadBalancerClients.class.getName(), true);
if (attrs != null && attrs.containsKey("value")) {
AnnotationAttributes[] clients = (AnnotationAttributes[]) attrs.get("value");
//对于 value 属性,其实就是一个 LoadBalancerClient 列表,对于每个生成一个特定微服务名字的 LoadBalancerClientSpecification
for (AnnotationAttributes client : clients) {
registerClientConfiguration(registry, getClientName(client), client.get("configuration"));
}
}
//如果指定了 defaultConfiguration,则注册为 default 的配置
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"));
}
//获取 LoadBalancerClient 注解的元数据
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 void registerClientConfiguration(BeanDefinitionRegistry registry, Object name, Object configuration) {
//初始化 LoadBalancerClientSpecification 的 BeanDefinition,用于注册一个 LoadBalancerClientSpecification Bean
BeanDefinitionBuilder builder = BeanDefinitionBuilder
.genericBeanDefinition(LoadBalancerClientSpecification.class);
//构造器参数
builder.addConstructorArgValue(name);
builder.addConstructorArgValue(configuration);
//注册 Bean
registry.registerBeanDefinition(name + ".LoadBalancerClientSpecification", builder.getBeanDefinition());
}

从代码中我们可以看出,通过使用 @LoadBalancerClients@LoadBalancerClient 注解可以自动生成对应的 LoadBalancerClientSpecification 进而实现公共负载均衡配置或者特定某个微服务的负载均衡配置。

我们这一节详细分析 Spring Cloud LoadBalancer 的源代码来理解其中的原理,下一节我们将介绍在我们项目中使用 Spring Cloud LoadBalancer 要实现的功能。

微信搜索“我的编程喵”关注公众号,每日一刷,轻松提升技术,斩获各种offer

SpringCloud升级之路2020.0.x版-22.Spring Cloud LoadBalancer核心源码的更多相关文章

  1. SpringCloud升级之路2020.0.x版-21.Spring Cloud LoadBalancer简介

    本系列代码地址:https://github.com/HashZhang/spring-cloud-scaffold/tree/master/spring-cloud-iiford 我们使用 Spri ...

  2. SpringCloud升级之路2020.0.x版-29.Spring Cloud OpenFeign 的解析(1)

    本系列代码地址:https://github.com/JoJoTec/spring-cloud-parent 在使用云原生的很多微服务中,比较小规模的可能直接依靠云服务中的负载均衡器进行内部域名与服务 ...

  3. Spring Cloud 升级之路 - 2020.0.x - 6. 使用 Spring Cloud LoadBalancer (1)

    本项目代码地址:https://github.com/HashZhang/spring-cloud-scaffold/tree/master/spring-cloud-iiford 我们使用 Spri ...

  4. Spring Cloud 升级之路 - 2020.0.x - 7. 使用 Spring Cloud LoadBalancer (2)

    本项目代码地址:https://github.com/HashZhang/spring-cloud-scaffold/tree/master/spring-cloud-iiford 我们使用 Spri ...

  5. SpringCloud升级之路2020.0.x版-6.微服务特性相关的依赖说明

    本系列代码地址:https://github.com/HashZhang/spring-cloud-scaffold/tree/master/spring-cloud-iiford spring-cl ...

  6. SpringCloud升级之路2020.0.x版-43.为何 SpringCloudGateway 中会有链路信息丢失

    本系列代码地址:https://github.com/JoJoTec/spring-cloud-parent 在开始编写我们自己的日志 Filter 之前,还有一个问题我想在这里和大家分享,即在 Sp ...

  7. SpringCloud升级之路2020.0.x版-20. 启动一个 Eureka Server 集群

    本系列代码地址:https://github.com/HashZhang/spring-cloud-scaffold/tree/master/spring-cloud-iiford 我们的业务集群结构 ...

  8. SpringCloud升级之路2020.0.x版-23.订制Spring Cloud LoadBalancer

    本系列代码地址:https://github.com/HashZhang/spring-cloud-scaffold/tree/master/spring-cloud-iiford 我们使用 Spri ...

  9. SpringCloud升级之路2020.0.x版-33. 实现重试、断路器以及线程隔离源码

    本系列代码地址:https://github.com/JoJoTec/spring-cloud-parent 在前面两节,我们梳理了实现 Feign 断路器以及线程隔离的思路,并说明了如何优化目前的负 ...

随机推荐

  1. DHCP与配置命令

    1. DHCP简介 2. DHCP主要用途 3. 使用DHCP的好处 4.DHCP经典应用模式 5.DHCP交互过程 DHCP的IP地址自动获取工作原理 6.DHCP中继    应用场景   工作原理 ...

  2. 剖析:如何用 SwitchUI 5天写一个微信 —— 聊天界面篇

    前置资源 GitHub: SwiftUI-WeChatDemo 第零章:用 SwiftUI 五天组装一个微信 - wavky - 博客园 整体结构 UI 部分代码分布如上图所示,App 的主入口类为 ...

  3. 学习 CLR 源码:连续内存块数据操作的性能优化

    目录 C# 原语类型 1,利用 Buffer 优化数组性能 2,BinaryPrimitives 细粒度操作字节数组 提高代码安全性 3,BitConverter.MemoryMarshal 4,Ma ...

  4. VS Code 与 ESP32 官方SDK配置

    开发基于 ESP XXX 微控制器应用,最简单的环境搭建方案是像 MicroPython.CircuitPython.NanoFramework 等,下载固件,直接开刷:或者基于 Arduino 的开 ...

  5. 打造一个window桌面应用:在线聊天对话机器人

    大家好,我是辰哥~~~ 本文目标:打造一个window桌面应用:在线聊天对话机器人. 今天辰哥教大家做一个在线聊天对话机器人桌面应用,已经打包成exe可执行文件,读者可以直接拿来使用, 先上演示图 聊 ...

  6. ODOO14 ---系统启动方式

    一.通过pycharm启动 1.配置启动面板: 点击启动即可: 第二种.通过CMD窗口启动:进入到odoo-bin的目录下,执行:python E:\odoo14\odoo14\odoo-bin  这 ...

  7. javascript学习笔记-(三)

    ES6标准新增了一种新的函数:Arrow Function(箭头函数) 案例: 为什么叫Arrow Function?因为它的定义用的就是一个箭头: x => x * x 上面的箭头函数相当于: ...

  8. odoo学习笔记create函数

    @api.multi def create_order_sale(self): """""" stage_list = [] for ord ...

  9. GooseFS助力大数据业务数倍提升计算能力

    前言 GooseFS是由腾讯云推出的一款分布式缓存方案,主要针对包括需要缓存加速的数据湖业务场景,提供基于对象存储COS服务的近计算端数据加速层. GooseFS 基于开源大数据缓存方案 Alluxi ...

  10. 95后新同事年薪35W+,老员工却“自愿申请”降薪10%,中年职场人正在崩溃

    蔡依林在演唱会上说过的一句话:"在乐坛摸爬滚打这么多年,遭遇了那么多质疑和嘲讽还能挺立到今天,然后想说40岁是个很棒的年纪......",让很多在职场打拼多年的老员工感慨颇深. 真 ...