LoadBalancer工作原理

前言: 当前章节不会像SpringBoot源码分析篇章一样大篇幅的列举大量源码, 仅仅列举部分源码

负载均衡

依赖

首先需要导入LoadBalance依赖

1
2
3
4
5
<!--LoadBalanced-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-loadbalancer</artifactId>
</dependency>

逐步追踪依赖

通过该starter追踪找到依赖jar包中只有一个pom文件, 在pom文件中找到LoadBalance的真正依赖

1
2
3
4
5
6
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-loadbalancer</artifactId>
<version>3.1.3</version>
<scope>compile</scope>
</dependency>

找到LoadBalancerAutoConfiguration

继续在依赖里面找到spring-cloud-loadbalancer的jar包, 根据SPI原则找到spring.factories, 在该文件中找到自动配置LoadBalancerAutoConfiguration 追踪到了该类中

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
@Configuration(proxyBeanMethods = false)
@LoadBalancerClients
@EnableConfigurationProperties(LoadBalancerClientsProperties.class)
@AutoConfigureBefore({ ReactorLoadBalancerClientAutoConfiguration.class,
LoadBalancerBeanPostProcessorAutoConfiguration.class })
@ConditionalOnProperty(value = "spring.cloud.loadbalancer.enabled",havingValue = "true",
matchIfMissing = true)
public class LoadBalancerAutoConfiguration {
// 略(详情见org.springframework.cloud.loadbalancer.config.LoadBalancerAutoConfiguration)
@ConditionalOnMissingBean // 当前注解为:假如没有该bean则创建,有则不执行
@Bean
public LoadBalancerClientFactory loadBalancerClientFactory(LoadBalancerClientsProperties properties) {
LoadBalancerClientFactory clientFactory = new LoadBalancerClientFactory(properties);
clientFactory.setConfigurations(this.configurations.getIfAvailable(Collections::emptyList));
return clientFactory;
}
}

解析点:

  • 对于6-7行代码处的注解, 可以清晰的知道, 假如使用SpringCloud的负载均衡在没有配置开启的情况下, 默认为开启, 因为 matchIfMissing = true
  • 对于12行的方法为负载均衡客户端工厂方法, 用于构造负载均衡客户端的

切入点与自定义设想

切入点: 为什么在SpringCloud LoadBalance中默认为轮询 ? 我们能不能自定义一个算法进行负载均衡呢 ?

继续对于12行方法中的LoadBalancerClientFactory负载均衡客户端工厂进行追踪, 对于工厂而言, 最重要的就是实例化bean了, 在当前源码处, 可以容易找到一个名为getInstance的方法, 显然为从工厂获取实例bean的方法: (如下)

1
2
3
4
@Override
public ReactiveLoadBalancer<ServiceInstance> getInstance(String serviceId) {
return getInstance(serviceId, ReactorServiceInstanceLoadBalancer.class);
}

通过解读, 获取实例是通过

  1. 一个服务的编号
  2. 一个实现接口的字节码类

对于1当然不陌生, 显然当前要以2切入

追踪进去是一个接口

1
2
3
4
5
6
7
8
9
/**
* A marker interface for {@link ReactorLoadBalancer} that allows selecting
* {@link ServiceInstance} objects.
*
* @author Olga Maciaszek-Sharma
* @since 2.2.0
*/
public interface ReactorServiceInstanceLoadBalancer extends ReactorLoadBalancer<ServiceInstance> {
}

注释: 一个标记为负载均衡的接口, 它被允许选择服务实例对象

选择负载均衡实例

也就是说我们需要实现自己的负载均衡就是通过当前的接口实现的, 通过点击当前接口名, 可以发现有三个实现类

  1. RandomLoadBalancer
  2. RoundRobinLoadBalancer
  3. LoadBalancerClientFactory

以2(RoundRobin: 轮询)为例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
public class RoundRobinLoadBalancer implements ReactorServiceInstanceLoadBalancer {  // 实现ReactorServiceInstanceLoadBalancer 接口

private static final Log log = LogFactory.getLog(RoundRobinLoadBalancer.class);
final AtomicInteger position; // 一个原子整数(用于线程同步),表示循环轮询中的当前位置
final String serviceId; // 表示需要选择实例的服务的ID

ObjectProvider<ServiceInstanceListSupplier> 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
*/
// 构造函数,使用给定的 serviceInstanceListSupplierProvider、serviceId 和一个随机的种子位置初始化 RoundRobinLoadBalancer。
public RoundRobinLoadBalancer(ObjectProvider<ServiceInstanceListSupplier> 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<ServiceInstanceListSupplier> serviceInstanceListSupplierProvider,
String serviceId, int seedPosition) {
this.serviceId = serviceId;
this.serviceInstanceListSupplierProvider = serviceInstanceListSupplierProvider;
this.position = new AtomicInteger(seedPosition);
}

@SuppressWarnings("rawtypes")
@Override
// 负责根据循环轮询算法选择一个服务实例
public Mono<Response<ServiceInstance>> choose(Request request) {
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);
if (supplier instanceof SelectedInstanceCallback && serviceInstanceResponse.hasServer()) {
((SelectedInstanceCallback) supplier).selectedServiceInstance(serviceInstanceResponse.getServer());
}
return serviceInstanceResponse;
}
// 负责根据循环轮询算法从提供的列表中生成包含所选服务实例的响应
private Response<ServiceInstance> getInstanceResponse(List<ServiceInstance> instances) {
if (instances.isEmpty()) {
if (log.isWarnEnabled()) {
log.warn("No servers available for service: " + serviceId);
}
return new EmptyResponse();
}
// Ignore the sign bit, this allows pos to loop sequentially from 0 to
// Integer.MAX_VALUE
int pos = this.position.incrementAndGet() & Integer.MAX_VALUE;
ServiceInstance instance = instances.get(pos % instances.size());
return new DefaultResponse(instance);
}
}

对于上面类的解释:

  • position属性: 使用了 AtomicInteger 进行原子操作, 使多线程操作也能同步

  • 构造函数: 两个的区别在于是否有seedPosition 循环轮询的起始位置, 除去该项, 构造器的作用在于初始化服务实例列表的提供者(即初始化服务器选择的种子)

  • choose: 根据算法选择合适的服务实例

  • processInstanceResponse: 处理选择服务实例后收到的响应,并在有回调时通知

  • getInstanceResponse: 负责轮询的算法

    上面三个方法为层级调用 choose -> processInstanceResponse -> getInstanceResponse

  • 对于62行的解释: & Integer.MAX_VALUE这部分是一个位运算,它确保了如果 position 的值增加到超过 Integer.MAX_VALUE 时,不会产生负数。其一,在轮询算法中,如果计数器变成负数,那么取余操作可能会产生负的索引值,这是无效的; 其二,也可也保证在相同规则底下的公平性

自定义负载均衡

前置分析

对于自定义负载均衡策略, 追踪上面的案例显然是不够的, 当然还有配置文件需要解读

通过注解@LoadBalancerClient追踪 (该注解为负载均衡客户端配置 —- service-id、Configuration)

然后找到configuration的注释有默认配置在: LoadBalancerClientConfiguration, 其中这个里面就标明默认为RoundRobinLoadBalancer 轮询

1
2
3
4
5
6
7
8
@Bean
@ConditionalOnMissingBean
public ReactorLoadBalancer<ServiceInstance> reactorServiceInstanceLoadBalancer(Environment environment,
LoadBalancerClientFactory loadBalancerClientFactory) {
String name = environment.getProperty(LoadBalancerClientFactory.PROPERTY_NAME);
return new RoundRobinLoadBalancer(
loadBalancerClientFactory.getLazyProvider(name, ServiceInstanceListSupplier.class), name);
}

那么到此处, 配置文件 还有轮询方案都了解了, 需要自定义实现也就清楚了:

  1. 编写一个配置类, 返回自定义的LoadBalance类
  2. 自定义的LoadBalance类 需要实现接口 ReactorServiceInstanceLoadBalancer
  3. 利用@LoadBalancerClient 指定配置类生效范围(指定的服务)

理论存在 实践开始

自定义负载均衡算法-

首先参考RoundRobinLoadBalancer创建一个自己的LoadBalancer

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
public class MyLoadBalancer implements ReactorServiceInstanceLoadBalancer {

private static final Log log = LogFactory.getLog(MyLoadBalancer.class);
final String serviceId; // 服务id
ObjectProvider<ServiceInstanceListSupplier> serviceInstanceListSupplierProvider; // 服务提供者
// 当前仅提供两个参数的构造器, 有需追加属性
public MyLoadBalancer(ObjectProvider<ServiceInstanceListSupplier> serviceInstanceListSupplierProvider,String serviceId) {
this.serviceId = serviceId;
this.serviceInstanceListSupplierProvider = serviceInstanceListSupplierProvider;
}

@SuppressWarnings("rawtypes")
@Override
public Mono<Response<ServiceInstance>> choose(Request request) {
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);
if (supplier instanceof SelectedInstanceCallback && serviceInstanceResponse.hasServer()) {
((SelectedInstanceCallback) supplier).selectedServiceInstance(serviceInstanceResponse.getServer());
}
return serviceInstanceResponse;
}

private Response<ServiceInstance> getInstanceResponse(List<ServiceInstance> instances) {
if (instances.isEmpty()) {
if (log.isWarnEnabled()) {
log.warn("No servers available for service: " + serviceId);
}
return new EmptyResponse();
}
// 负载均衡算法处 (当前以仅访问一个服务为例)
int pos = 0;
ServiceInstance instance = instances.get(pos);

return new DefaultResponse(instance);
}
}

当前负载均衡示例仅设置了访问一个固定服务, 如需修改则修改此处的算法即可 (37行处)

配置完刚刚的负载均衡算法部分, 接下来需要读取自定义的负载均衡

例自定义一个对于南北服务器的负载均衡

思路引导

使用hutool 工具获取本机IP地址

1
this.localIps = NetUtil.localIpv4s();

然后对于IP区域进行负载均衡, 在上述代码中的负载均衡算法处书写自己的具体逻辑即可

配置类

1
2
3
4
5
6
7
8
9
10
11
12
@Configuration(proxyBeanMethods = false)
@ConditionalOnDiscoveryEnabled
public class MyLoadBalancerConfiguration {
@Bean
// @ConditionalOnMissingBean // 此注解如果需要自定义必须得去掉
public ReactorLoadBalancer<ServiceInstance> reactorServiceInstanceLoadBalancer(Environment environment,
LoadBalancerClientFactory loadBalancerClientFactory) {
String name = environment.getProperty(LoadBalancerClientFactory.PROPERTY_NAME);
return new MyLoadBalancer(
loadBalancerClientFactory.getLazyProvider(name, ServiceInstanceListSupplier.class), name);
}
}

在最下面(即第9行处), 返回我们刚刚自定义的负载均衡实例

100%

90%

至此, 自定义的负载均衡已经完成90%了

配置配置类生效范围

1
2
3
4
5
6
7
8
9
10
11
12
@Configuration
@LoadBalancerClients(value = {
@LoadBalancerClient(value = "service-id1",configuration = MyLoadBalancerConfiguration.class),
@LoadBalancerClient(value = "service-id2",configuration = MyLoadBalancerConfiguration.class)
})
public class LoadApplication {
@LoadBalanced
@Bean
public RestTemplate restTemplate(){
return new RestTemplate();
}
}

LoadBalancerClients中可以配置多个LoadBalancerClient, 在LoadBalancerClient中指定服务使用指定的负载均衡策略

当前配置项一般用于启动类中

结束标识


点击跳转到首页