关于负载均衡
通过软 / 硬件来分:一种是硬件负载均衡,硬件比如:F5、Array 等;另一种是软件负载均衡,软件比如:LVS、Nginx 等
通过服务 / 消费 来分:服务器端负载均衡 和 客户端负载均衡
所谓服务器端负载均衡,⽐如 Nginx、F5 这些,请求到达服务器之后由这些负载均衡器根据⼀定的算法将请求路由到⽬标服务器处理。
所谓客户端负载均衡,⽐如我们要说的 Ribbon,服务消费者客户端会有⼀个服务器地址列表,调⽤⽅在请求前通过⼀定的负载均衡算法选择⼀个服务器进⾏访问,负载均衡算法的执⾏是在请求客户端进⾏。
Ribbon 是 Netflix 发布的负载均衡器。Eureka ⼀般配合 Ribbon 进⾏使⽤,Ribbon 利⽤从 Eureka 中读取到服务信息,在调⽤服务提供者提供的服务时,会根据⼀定的算法进⾏负载。
Ribbon 高级应用
Greenwich.RELEASE 版本的 Ribbon 相关Jar在 eureka-client jar中
不需要引⼊额外的Jar坐标,因为在服务消费者中我们引⼊过 eureka-client,它会引⼊Ribbon 相关Jar
2020.0.1 版本的 Ribbon 相关Jar在 eurekaServer jar中
代码中使⽤如下,在 RestTemplate 上添加对应注解即可
// Ribbon 负载均衡只需要通过 @LoadBalanced 修饰 RestTemplate 即可
@Bean
@LoadBalanced
public RestTemplate getRestTemplate() {
return new RestTemplate();
}
Ribbon负载均衡策略
Ribbon内置了多种负载均衡策略,内部负责复杂均衡的顶级接⼝为com.netflix.loadbalancer.IRule,类树如下
负载均衡策略配置与描述
负载均衡策略 | 描述 |
RoundRobinRule 轮询策略 | 默认超过10次获取到的server都不可⽤,会返回⼀个空的server |
RandomRule 随机策略 | 如果随机到的server为null或者不可⽤的话,会while不停的循环选取 |
RetryRule 重试策略 | ⼀定时限内循环重试。默认继承RoundRobinRule,也⽀持⾃定义注⼊,RetryRule会在每次选取之后,对选举的server进⾏判断, 是否为null,是否alive,并且在500ms内会不停的选取判断。⽽RoundRobinRule失效的策略是超过10次,RandomRule是没有失效时间的概念,只要serverList没都挂。 |
BestAvailableRule 最⼩连接数策略 | 遍历serverList,选取出可⽤的且连接数最⼩的⼀个server。该算法⾥⾯有⼀个LoadBalancerStats的成员变量,会存储所有server 的运⾏状况和连接数。如果选取到的server为null,那么会调⽤RoundRobinRule重新选取。1(1) 2(1) 3(1) |
AvailabilityFilteringRule 可⽤过滤策略 | 扩展了轮询策略,会先通过默认的轮询选取⼀个server,再去判断 该server是否超时可⽤,当前连接数是否超限,都成功再返回。 |
ZoneAvoidanceRule 区域权衡策略 (默认策略) | 扩展了轮询策略,继承了2个过滤器:ZoneAvoidancePredicate和AvailabilityPredicate,除了过滤超时和链接数过多的server,还会过滤掉不符合要求的zone区域⾥⾯的所有节点,AWS --ZONE 在⼀个区域/机房内的服务实例中轮询 |
修改负载均衡策略
# 针对的被调⽤⽅微服务名称,不加就是全局⽣效
lagou-service-resume:
ribbon:
NFLoadBalancerRuleClassName: com.netflix.loadbalancer.RandomRule #负载策略调整
Ribbon 核心源码剖析
Ribbon⼯作原理
重点:Ribbon 给 restTemplate 添加了⼀个拦截器
当服务消费者通过被 @loadBanlancer 修饰过的 restTemplate 调用服务端时,ribbon 会根据服务提供者的服务名获取到该服务对应的实例列表,并按照⼀定的负载均衡策略从实例列表中获取⼀个实例 Server,并最终通过 RestTemplate 进⾏请求访问
Ribbon细节结构图(涉及到底层的⼀些组件/类的描述)
1)获取服务实例列表
2)从列表中选择⼀个server
图中核⼼是负载均衡管理器 LoadBalancer(总的协调者,相当于⼤脑,为了做事情,协调四肢),围绕它周围的多有 IRule、IPing 等
IRule:是在选择实例的时候的负载均衡策略对象
IPing:是用来向服务发起心跳检测的,通过心跳检测来判断该服务是否可用
ServerListFilter:根据一些规则过滤传入的服务实例列表
ServerListUpdater:定义了一系列的对服务列表的更新操作
@LoadBalanced 源码剖析
我们在 RestTemplate 实例上添加了⼀个 @LoadBalanced 注解,就可以实现负载均衡,很神奇,我们接下来分析这个注解背后的操作(负载均衡过程)
查看 @LoadBalanced 注解,那这个注解是在哪里被识别到的呢?
下面源码的主体部分基本没有可参考内容,但是注释中对我们非诚有用,翻译一下:使用 @LoadBalanced 注解可以将 RestTemplate 对象使用 LoadBalancerClient 去处理
/**
* Annotation to mark a RestTemplate or WebClient bean to be configured to use a LoadBalancerClient.
* 使用 @LoadBalanced 注解可以将 RestTemplate 对象使用 LoadBalancerClient 去处理
* @author Spencer Gibb
*/
@Target({ ElementType.FIELD, ElementType.PARAMETER, ElementType.METHOD })
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@Qualifier
public @interface LoadBalanced {
}
LoadBalancerClient 接口(实现类 RibbonLoadBalancerClient,后面会细读)
public interface LoadBalancerClient extends ServiceInstanceChooser {
/**
* 根据 serviceId 查找 LoadBalancer 来执行制定的请求
* @param serviceId 用于查找 LoadBalancer 的服务Id.
*/
<T> T execute(String serviceId, LoadBalancerRequest<T> request) throws IOException;
/**
* 根据 serviceId 查找 LoadBalancer 来执行制定的请求
* @param serviceId 用于查找 LoadBalancer 的服务Id.
* @param serviceInstance 执行请求的服务。
* @param request 允许实现执行pre和post操作,例如增加指标。
* @param <T> type of the response
*/
<T> T execute(String serviceId, ServiceInstance serviceInstance, LoadBalancerRequest<T> request) throws IOException;
/**
* 拼接请求方式 传统:ip:port;现在:服务名称:port 形式
*/
URI reconstructURI(ServiceInstance instance, URI original);
}
上面的源码读到这里线索断了,此时我们要想起 springboot的自动装配功能【读 springboot 源码的套路】
SpringCloud 充分利⽤了 SpringBoot 的自动装配特点,找 spring.factories 配置⽂件
1. 细读 RibbonAutoConfiguration
@Configuration
@Conditional(RibbonAutoConfiguration.RibbonClassesConditions.class)
@RibbonClients
@AutoConfigureAfter(name = "org.springframework.cloud.netflix.eureka.EurekaClientAutoConfiguration")
// 在初始化当前类之前需要先初始化 LoadBalancerAutoConfiguration【需要重点研究】
@AutoConfigureBefore({LoadBalancerAutoConfiguration.class, AsyncLoadBalancerAutoConfiguration.class})
@EnableConfigurationProperties({RibbonEagerLoadProperties.class, ServerIntrospectorProperties.class})
public class RibbonAutoConfiguration {
}
2. 细读 LoadBalancerAutoConfiguration
- 注入 restTemplate 对象到集合待用
- 注入 restTemplate 定制器
- 使用定制器给集合中的每一个 restTemplate 对象添加一个拦截器
@Configuration
// 条件注解,只有容器中存在 RestTemplate 实例,才会初始化当前类
@ConditionalOnClass(RestTemplate.class)
@ConditionalOnBean(LoadBalancerClient.class)
@EnableConfigurationProperties(LoadBalancerRetryProperties.class)
public class LoadBalancerAutoConfiguration {
// 注⼊resttemplate对象到集合待⽤
@LoadBalanced
@Autowired(required = false)
// 上面两个注解合并使用会自动注入那些被 @loadBalanced 修饰 restTemplate 对象(统一在这里集中缓存)
private List<RestTemplate> restTemplates = Collections.emptyList();
// 使⽤定制器给集合中的每⼀个 restTemplate 对象添加⼀个拦截器
@Bean
public SmartInitializingSingleton loadBalancedRestTemplateInitializerDeprecated(
final ObjectProvider<List<RestTemplateCustomizer>> restTemplateCustomizers) {
return () -> restTemplateCustomizers.ifAvailable(customizers -> {
for (RestTemplate restTemplate : LoadBalancerAutoConfiguration.this.restTemplates) {
for (RestTemplateCustomizer customizer : customizers) {
// 定制化处理
customizer.customize(restTemplate);
}
}
});
}
@Configuration
@ConditionalOnMissingClass("org.springframework.retry.support.RetryTemplate")
static class LoadBalancerInterceptorConfig {
@Bean
public LoadBalancerInterceptor ribbonInterceptor(LoadBalancerClient loadBalancerClient, LoadBalancerRequestFactory requestFactory) {
return new LoadBalancerInterceptor(loadBalancerClient, requestFactory);
}
// 注⼊resttemplate定制器
@Bean
@ConditionalOnMissingBean
public RestTemplateCustomizer restTemplateCustomizer (
final LoadBalancerInterceptor loadBalancerInterceptor) {
return restTemplate -> {
List<ClientHttpRequestInterceptor> list = new ArrayList<>(
restTemplate.getInterceptors());
// 向容器注入 RestTemplate 定制器(给restTemplate对象添加一个拦截器 LoadBalancerIntercepter)
list.add(loadBalancerInterceptor);
restTemplate.setInterceptors(list);
};
}
}
}
小结:源码分析到此可以知道,添加了注解的 RestTemplate 对象会被添加⼀个拦截器 LoadBalancerInterceptor,该拦截器就是后续拦截请求进⾏负载处理的。所以,下⼀步重点我们该分析拦截器 LoadBalancerInterceptor.intercept() 方法
3. 细读 LoadBalancerInterceptor.intercept() 方法
public class LoadBalancerInterceptor implements ClientHttpRequestInterceptor {
private LoadBalancerClient loadBalancer;
private LoadBalancerRequestFactory requestFactory;
public LoadBalancerInterceptor(LoadBalancerClient loadBalancer, LoadBalancerRequestFactory requestFactory) {
this.loadBalancer = loadBalancer;
this.requestFactory = requestFactory;
}
public LoadBalancerInterceptor(LoadBalancerClient loadBalancer) {
// for backwards compatibility
this(loadBalancer, new LoadBalancerRequestFactory(loadBalancer));
}
@Override
public ClientHttpResponse intercept(final HttpRequest request, final byte[] body, final ClientHttpRequestExecution execution) throws IOException {
// 获取拦截到的请求 URI
final URI originalUri = request.getURI();
// 获取 URI 中的服务名,如:spring.application.name = user
String serviceName = originalUri.getHost();
Assert.state(serviceName != null, "Request URI does not contain a valid hostname: " + originalUri);
// 具体负载均衡的事情交给 LoadBalancerClient 对象完成,它的具体实现是 RibbonLoadBalancerClient 对象
return this.loadBalancer.execute(serviceName, requestFactory.createRequest(request, body, execution));
}
}
上面的源码有以下两个核心点:
1. intercept() 方法的最后将负载均衡的事情交给 LoadBalancerClient 对象完成,它的具体实现是 RibbonLoadBalancerClient 对象,那么 RibbonLoadBalancerClient 对象是在哪⾥注⼊的 ===》回到最初的⾃动配置类 RibbonAutoConfiguration 中
2. LoadBalancerInterceptor 构造器通过 getter、setter 注入了 RibbonLoadBalancerClient 对象、SpringClientFactory 对象【后面获取负载均衡器时需要用到】
@Configuration
@Conditional(RibbonAutoConfiguration.RibbonClassesConditions.class)
@RibbonClients
// 在此之后装配
@AutoConfigureAfter(name = "org.springframework.cloud.netflix.eureka.EurekaClientAutoConfiguration")
// 在此之前装配
@AutoConfigureBefore({LoadBalancerAutoConfiguration.class, AsyncLoadBalancerAutoConfiguration.class})
@EnableConfigurationProperties({RibbonEagerLoadProperties.class, ServerIntrospectorProperties.class})
public class RibbonAutoConfiguration {
@Bean
public SpringClientFactory springClientFactory() {
SpringClientFactory factory = new SpringClientFactory();
factory.setConfigurations(this.configurations);
return factory;
}
@Bean
@ConditionalOnMissingBean(LoadBalancerClient.class)
public LoadBalancerClient loadBalancerClient() {
return new RibbonLoadBalancerClient(springClientFactory());
}
}
4. 【核心方法】RibbonLoadBalancerClient.execute() ,有下面四个核心流程
- 【关注1】获取一个负载均衡器对象【大脑】
- 【关注2】通过负载均衡器选择一个最终要使用的 server 实例对象
- 将选择后的 server 对象包装成 RibbonServer 对象
- 【关注3】继续执行重载方法
public class RibbonLoadBalancerClient implements LoadBalancerClient {
private SpringClientFactory clientFactory;
public RibbonLoadBalancerClient(SpringClientFactory clientFactory) {
this.clientFactory = clientFactory;
}
/**
* New: Execute a request by selecting server using a 'key'.
* The hint will have to be the last parameter to not mess with the `execute(serviceId, ServiceInstance, request)`
* method. This somewhat breaks the fluent coding style when using a lambda to define the LoadBalancerRequest.
*/
public <T> T execute(String serviceId, LoadBalancerRequest<T> request, Object hint) throws IOException {
// 【关注1】获取一个负载均衡器对象【大脑】
ILoadBalancer loadBalancer = getLoadBalancer(serviceId);
// 【关注2】通过负载均衡器选择一个最终要使用的 server 实例对象
Server server = getServer(loadBalancer, hint);
if (server == null) {
throw new IllegalStateException("No instances available for " + serviceId);
}
// 将选择后的 server 对象包装成 RibbonServer 对象
RibbonServer ribbonServer = new RibbonServer(serviceId, server, isSecure(server,
serviceId), serverIntrospector(serviceId).getMetadata(server));
// 【关注3】继续执行重载方法
return execute(serviceId, ribbonServer, request);
}
@Override
public <T> T execute(String serviceId, ServiceInstance serviceInstance, LoadBalancerRequest<T> request) throws IOException {
Server server = null;
if(serviceInstance instanceof RibbonServer) {
server = ((RibbonServer)serviceInstance).getServer();
}
if (server == null) {
throw new IllegalStateException("No instances available for " + serviceId);
}
RibbonLoadBalancerContext context = this.clientFactory
.getLoadBalancerContext(serviceId);
RibbonStatsRecorder statsRecorder = new RibbonStatsRecorder(context, server);
try {
T returnVal = request.apply(serviceInstance);
statsRecorder.recordStats(returnVal);
return returnVal;
}
// catch IOException and rethrow so RestTemplate behaves correctly
catch (IOException ex) {
statsRecorder.recordStats(ex);
throw ex;
}
catch (Exception ex) {
statsRecorder.recordStats(ex);
ReflectionUtils.rethrowRuntimeException(ex);
}
return null;
}
// 获取一个负载均衡器对象
protected ILoadBalancer getLoadBalancer(String serviceId) {
// 继续深入 clientFactory 对象是什么时候注入的???
return this.clientFactory.getLoadBalancer(serviceId);
}
}
4.1 SpringClientFactory 对象的注入在主配置类 RibbonAutoConfiguration(见上面主配置的源码),继续深入到 SpringClientFactory 的构造器,内部涉及到 RibbonClientConfiguration
public class SpringClientFactory extends NamedContextFactory<RibbonClientSpecification> {
static final String NAMESPACE = "ribbon";
// 这里又涉及到了 RibbonClientConfiguration
public SpringClientFactory() {
super(RibbonClientConfiguration.class, NAMESPACE, "ribbon.client.name");
}
}
4.1.1 RibbonClientConfiguration 中装配了⼤脑和肢⼲
装配了一下核心对象;
IClientConfig:其实现 ribbonClientConfig 是 ribbonClient 的所有配置信息
IRule:是在选择实例的时候的负载均衡策略对象
IPing:是用来向服务发起心跳检测的,通过心跳检测来判断该服务是否可用
ILoadBalancer:其中装配的 serverList 是在哪里初始化、数据在哪里、什么时候装载的【重点分析】
ServerList<Server>:下面源码中向容器注入 ServerList bean 对象,但此方法实际上并未进行数据初始化,暂且猜测:可能只是预先注入了一个空对象,对象中的数据应该是后续操作中获取并添加的【后面继续深入分析】
@Configuration
@EnableConfigurationProperties
//Order is important here, last should be the default, first should be optional
// see https://github.com/spring-cloud/spring-cloud-netflix/issues/2086#issuecomment-316281653
@Import({HttpClientConfiguration.class, OkHttpRibbonConfiguration.class, RestClientRibbonConfiguration.class, HttpClientRibbonConfiguration.class})
public class RibbonClientConfiguration {
public static final int DEFAULT_CONNECT_TIMEOUT = 1000;
public static final int DEFAULT_READ_TIMEOUT = 1000;
public static final boolean DEFAULT_GZIP_PAYLOAD = true;
@RibbonClientName
private String name = "client";
// TODO: maybe re-instate autowired load balancers: identified by name they could be associated with ribbon clients
@Autowired
private PropertiesFactory propertiesFactory;
@Bean
@ConditionalOnMissingBean
public IClientConfig ribbonClientConfig() {
DefaultClientConfigImpl config = new DefaultClientConfigImpl();
config.loadProperties(this.name);
config.set(CommonClientConfigKey.ConnectTimeout, DEFAULT_CONNECT_TIMEOUT);
config.set(CommonClientConfigKey.ReadTimeout, DEFAULT_READ_TIMEOUT);
config.set(CommonClientConfigKey.GZipPayload, DEFAULT_GZIP_PAYLOAD);
return config;
}
@Bean
@ConditionalOnMissingBean
public IRule ribbonRule(IClientConfig config) {
// 如果配置文件配置负载均衡策略,以配置文件为准
if (this.propertiesFactory.isSet(IRule.class, name)) {
return this.propertiesFactory.get(IRule.class, config, name);
}
// 默认使用 ZoneAvoidanceRule 策略
ZoneAvoidanceRule rule = new ZoneAvoidanceRule();
rule.initWithNiwsConfig(config);
return rule;
}
@Bean
@ConditionalOnMissingBean
public IPing ribbonPing(IClientConfig config) {
if (this.propertiesFactory.isSet(IPing.class, name)) {
return this.propertiesFactory.get(IPing.class, config, name);
}
return new DummyPing();
}
// 下面的装配中 serverList 是在哪里初始化、装配的???【后面会单独分析】,源码见下一个方法
@Bean
@ConditionalOnMissingBean
public ILoadBalancer ribbonLoadBalancer(IClientConfig config,
ServerList<Server> serverList, ServerListFilter<Server> serverListFilter,
IRule rule, IPing ping, ServerListUpdater serverListUpdater) {
if (this.propertiesFactory.isSet(ILoadBalancer.class, name)) {
return this.propertiesFactory.get(ILoadBalancer.class, config, name);
}
// 默认注入的负载均衡器实现为 ZoneAwareLoadBalancer
// serverList 也一同注入了,需要关注其 初始化、注入流程
return new ZoneAwareLoadBalancer<>(config, rule, ping, serverList,
serverListFilter, serverListUpdater);
}
// 向容器注入 ServerList bean 对象,此方法实际上并未进行数据初始化
// 猜测:可能只是预先注入了一个空对象,对象中的数据应该是后续操作中获取并添加的
@Bean
@ConditionalOnMissingBean
@SuppressWarnings("unchecked")
public ServerList<Server> ribbonServerList(IClientConfig config) {
if (this.propertiesFactory.isSet(ServerList.class, name)) {
return this.propertiesFactory.get(ServerList.class, config, name);
}
ConfigurationBasedServerList serverList = new ConfigurationBasedServerList();
serverList.initWithNiwsConfig(config);
return serverList;
}
}
向容器注入 ServerList bean 对象的时候 实际上并未进行数据初始化,那么数据从哪里来的,我接下来看看具体使用数据的位置:org.springframework.cloud.netflix.ribbon.RibbonClientConfiguration#ribbonLoadBalancer,在其源码中 ZoneAwareLoadBalancer 的构造器中使用到了,内部调用了父类的构造器 DynamicServerListLoadBalancer,内部调用了 com.netflix.loadbalancer.DynamicServerListLoadBalancer#restOfInit 方法,源码如下:
public class DynamicServerListLoadBalancer<T extends Server> extends BaseLoadBalancer {
protected final ServerListUpdater.UpdateAction updateAction = new ServerListUpdater.UpdateAction() {
@Override
public void doUpdate() {
updateListOfServers();
}
};
public DynamicServerListLoadBalancer(IClientConfig clientConfig, IRule rule, IPing ping,
ServerList<T> serverList, ServerListFilter<T> filter,
ServerListUpdater serverListUpdater) {
super(clientConfig, rule, ping);
this.serverListImpl = serverList;
this.filter = filter;
this.serverListUpdater = serverListUpdater;
if (filter instanceof AbstractServerListFilter) {
((AbstractServerListFilter) filter).setLoadBalancerStats(getLoadBalancerStats());
}
// 关键调用
restOfInit(clientConfig);
}
void restOfInit(IClientConfig clientConfig) {
boolean primeConnection = this.isEnablePrimingConnections();
// turn this off to avoid duplicated asynchronous priming done in BaseLoadBalancer.setServerList()
this.setEnablePrimingConnections(false);
// 这里会开启一个延时定时任务,若干时间后每隔一段时间就去 eurekaClient 的缓存中获取最新的服务实例信息
// eurekaClient 也会定时从 eurekaServer 中更新服务信息,最后更新到 ribbon 的本地缓存中
enableAndInitLearnNewServersFeature();
// 因为上面定义的是延时定时任务,并未立即执行,此处主动触发执行一次
updateListOfServers();
if (primeConnection && this.getPrimeConnections() != null) {
this.getPrimeConnections()
.primeConnections(getReachableServers());
}
this.setEnablePrimingConnections(primeConnection);
LOGGER.info("DynamicServerListLoadBalancer for client {} initialized: {}", clientConfig.getClientName(), this.toString());
}
public void enableAndInitLearnNewServersFeature() {
LOGGER.info("Using serverListUpdater {}", serverListUpdater.getClass().getSimpleName());
// 下面方法的参数 updateAction 是一个对象,此对象有个 update() 方法,核心逻辑就在其中的 updateListOfServers();
serverListUpdater.start(updateAction);
}
}
com.netflix.loadbalancer.PollingServerListUpdater#start
public class PollingServerListUpdater implements ServerListUpdater {
@Override
public synchronized void start(final UpdateAction updateAction) {
if (isActive.compareAndSet(false, true)) {
// 定义线程、调用 updateAction 对象的 doUpdate() 方法
final Runnable wrapperRunnable = new Runnable() {
@Override
public void run() {
if (!isActive.get()) {
if (scheduledFuture != null) {
scheduledFuture.cancel(true);
}
return;
}
try {
updateAction.doUpdate();
lastUpdated = System.currentTimeMillis();
} catch (Exception e) {
logger.warn("Failed one update cycle", e);
}
}
};
// 定义延迟定时任务、启动上面的线程任务
scheduledFuture = getRefreshExecutor().scheduleWithFixedDelay(
wrapperRunnable,
initialDelayMs,
refreshIntervalMs,
TimeUnit.MILLISECONDS
);
} else {
logger.info("Already active, no-op");
}
}
}
4.2 通过负载均衡器选择最终的 server 对象具体实现
com.netflix.loadbalancer.ZoneAwareLoadBalancer#chooseServer,在过年都是只有一个 Zone,只是走了最前面一段逻辑
public class ZoneAwareLoadBalancer<T extends Server> extends DynamicServerListLoadBalancer<T> {
@Override
public Server chooseServer(Object key) {
// 一个 Zone 走下面的分支
if (!ENABLED.get() || getLoadBalancerStats().getAvailableZones().size() <= 1) {
logger.debug("Zone aware logic disabled or there is only one zone");
return super.chooseServer(key);
}
// 多个 Zone 的逻辑,国内不会涉及,这里是存在这部分逻辑是因为 Netflix 公司将业务上到了亚马逊 AWS 云
}
}
4.2.1 ⽗类:com.netflix.loadbalancer.BaseLoadBalancer#chooseServer
public class BaseLoadBalancer extends AbstractLoadBalancer implements PrimeConnections.PrimeConnectionListener, IClientConfigAware {
public Server chooseServer(Object key) {
if (counter == null) {
counter = createCounter();
}
counter.increment();
if (rule == null) {
return null;
} else {
try {
// 使用负载均衡策略选择一个实例,默认区域隔离策略
return rule.choose(key);
} catch (Exception e) {
logger.warn("LoadBalancer [{}]: Error choosing server for key {}", name, key, e);
return null;
}
}
}
}
来到区域隔离策略的⽗类choose⽅法中com.netflix.loadbalancer.PredicateBasedRule#choose
public abstract class PredicateBasedRule extends ClientConfigEnabledRoundRobinRule {
/**
* 抽象方法,子类根据具体负载均衡策略去实现
*/
public abstract AbstractServerPredicate getPredicate();
@Override
public Server choose(Object key) {
ILoadBalancer lb = getLoadBalancer();
// 从过滤之后的服务实例集合中根据轮训策略选择一个 server
Optional<Server> server = getPredicate().chooseRoundRobinAfterFiltering(lb.getAllServers(), key);
if (server.isPresent()) {
return server.get();
} else {
return null;
}
}
}
继续深入 com.netflix.loadbalancer.AbstractServerPredicate#chooseRoundRobinAfterFiltering(List<Server> servers, Object loadBalancerKey)
核心逻辑:通过 CAS 设置下一个索引值(解决并发场景可能造成的数据问题),当然也可以使用锁进行控制
public abstract class AbstractServerPredicate implements Predicate<PredicateKey> {
/**
* Choose a server in a round robin fashion after the predicate filters a given list of servers and load balancer key.
*/
public Optional<Server> chooseRoundRobinAfterFiltering(List<Server> servers, Object loadBalancerKey) {
// 过滤服务
List<Server> eligible = getEligibleServers(servers, loadBalancerKey);
if (eligible.size() == 0) {
return Optional.absent();
}
// incrementAndGetModulo(eligible.size()) 为轮训到实例索引值计算方法
return Optional.of(eligible.get(incrementAndGetModulo(eligible.size())));
}
// 轮训实例索引值计算方法
private int incrementAndGetModulo(int modulo) {
for (;;) {
int current = nextIndex.get(); // 获取当前服务实例的索引值
int next = (current + 1) % modulo; // 通过求余的方式记录下一个索引值
// 通过 CAS 设置下一个索引值(解决并发场景可能造成的数据问题),当然也可以使用锁进行控制
if (nextIndex.compareAndSet(current, next) && current < modulo)
return current;
}
}
}
4.3 重载方法中的核心方法,也是正式向 server 实例发起请求的关键 T returnVal = request.apply(serviceInstance);
public class AsyncLoadBalancerInterceptor implements AsyncClientHttpRequestInterceptor {
private LoadBalancerClient loadBalancer;
public AsyncLoadBalancerInterceptor(LoadBalancerClient loadBalancer) {
this.loadBalancer = loadBalancer;
}
@Override
public ListenableFuture<ClientHttpResponse> intercept(final HttpRequest request, final byte[] body,
final AsyncClientHttpRequestExecution execution) throws IOException {
final URI originalUri = request.getURI();
String serviceName = originalUri.getHost();
return this.loadBalancer.execute(serviceName,
new LoadBalancerRequest<ListenableFuture<ClientHttpResponse>>() {
@Override
public ListenableFuture<ClientHttpResponse> apply(final ServiceInstance instance) throws Exception {
HttpRequest serviceRequest = new ServiceRequestWrapper(request, instance, loadBalancer);
// 核心调用
return execution.executeAsync(serviceRequest, body);
}
});
}
}
org.springframework.http.client.InterceptingClientHttpRequest.InterceptingRequestExecution#execute
@Override
public ClientHttpResponse execute(HttpRequest request, byte[] body) throws IOException {
if (this.iterator.hasNext()) {
ClientHttpRequestInterceptor nextInterceptor = this.iterator.next();
return nextInterceptor.intercept(request, body, this);
}
else {
HttpMethod method = request.getMethod();
Assert.state(method != null, "No standard HTTP method");
ClientHttpRequest delegate = requestFactory.createRequest(request.getURI(), method);
request.getHeaders().forEach((key, value) -> delegate.getHeaders().addAll(key, value));
if (body.length > 0) {
if (delegate instanceof StreamingHttpOutputMessage) {
StreamingHttpOutputMessage streamingOutputMessage = (StreamingHttpOutputMessage) delegate;
streamingOutputMessage.setBody(outputStream -> StreamUtils.copy(body, outputStream));
}
else {
StreamUtils.copy(body, delegate.getBody());
}
}
// 核心代码
return delegate.execute();
}
}
请求最后会调用 org.springframework.http.client.AbstractClientHttpRequest#execute 方法
此处,就已经到了 RestTemplate 底层执⾏的代码了(可以通过 restTemplate.getForObject(url, Integer.class) 方法直接进入到下面的方法),由此也将验证最终请求的调⽤还是靠的 RestTemplate
public abstract class AbstractClientHttpRequest implements ClientHttpRequest {
private final HttpHeaders headers = new HttpHeaders();
private boolean executed = false;
public AbstractClientHttpRequest() {
}
public final HttpHeaders getHeaders() {
return this.executed ? HttpHeaders.readOnlyHttpHeaders(this.headers) : this.headers;
}
public final OutputStream getBody() throws IOException {
this.assertNotExecuted();
return this.getBodyInternal(this.headers);
}
// 此方法就是 restTemplate 执行请求是会调用的逻辑
public final ClientHttpResponse execute() throws IOException {
this.assertNotExecuted();
ClientHttpResponse result = this.executeInternal(this.headers);
this.executed = true;
return result;
}
protected void assertNotExecuted() {
Assert.state(!this.executed, "ClientHttpRequest already executed");
}
protected abstract OutputStream getBodyInternal(HttpHeaders var1) throws IOException;
protected abstract ClientHttpResponse executeInternal(HttpHeaders var1) throws IOException;
}
RoundRobinRule 轮询策略源码剖析
package com.netflix.loadbalancer;
import com.netflix.client.config.IClientConfig;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.List;
import java.util.concurrent.atomic.AtomicInteger;
/**
* The most well known and basic load balancing strategy, i.e. Round Robin Rule.
* 最著名、最基本的负载均衡策略,即轮询规则
* @author stonse
* @author Nikos Michalakis <nikos@netflix.com>
*
*/
public class RoundRobinRule extends AbstractLoadBalancerRule {
private AtomicInteger nextServerCyclicCounter;
private static final boolean AVAILABLE_ONLY_SERVERS = true;
private static final boolean ALL_SERVERS = false;
private static Logger log = LoggerFactory.getLogger(RoundRobinRule.class);
public RoundRobinRule() {
nextServerCyclicCounter = new AtomicInteger(0);
}
public RoundRobinRule(ILoadBalancer lb) {
this();
setLoadBalancer(lb);
}
// 负载均衡策略类核心方法
public Server choose(ILoadBalancer lb, Object key) {
if (lb == null) {
log.warn("no load balancer");
return null;
}
Server server = null;
int count = 0;
while (server == null && count++ < 10) {
// 所有可用服务实例列表
List<Server> reachableServers = lb.getReachableServers();
// 所有服务实例列表
List<Server> allServers = lb.getAllServers();
int upCount = reachableServers.size();
int serverCount = allServers.size();
if ((upCount == 0) || (serverCount == 0)) {
log.warn("No up servers available from load balancer: " + lb);
return null;
}
// 获得一个轮询索引
int nextServerIndex = incrementAndGetModulo(serverCount);
// 根据索引取出服务实例对象
server = allServers.get(nextServerIndex);
if (server == null) {
/* Transient. */
Thread.yield();
continue;
}
// 判断服务可用后返回
if (server.isAlive() && (server.isReadyToServe())) {
return (server);
}
// Next.
server = null;
}
if (count >= 10) {
log.warn("No available alive servers after 10 tries from load balancer: "
+ lb);
}
return server;
}
/**
* Inspired by the implementation of {@link AtomicInteger#incrementAndGet()}.
*
* @param modulo The modulo to bound the value of the counter.
* @return The next value.
*/
private int incrementAndGetModulo(int modulo) {
for (;;) {
// 取出上次的计数
int current = nextServerCyclicCounter.get();
// 因为是轮询,计数+1之后对总数取模
int next = (current + 1) % modulo;
if (nextServerCyclicCounter.compareAndSet(current, next))
return next;
}
}
@Override
public Server choose(Object key) {
return choose(getLoadBalancer(), key);
}
@Override
public void initWithNiwsConfig(IClientConfig clientConfig) {
}
}
RandomRule随机策略源码剖析
package com.netflix.loadbalancer;
import com.netflix.client.config.IClientConfig;
import java.util.List;
import java.util.concurrent.ThreadLocalRandom;
/**
* A loadbalacing strategy that randomly distributes traffic amongst existing
* servers.
*
* @author stonse
*
*/
public class RandomRule extends AbstractLoadBalancerRule {
/**
* Randomly choose from all living servers
* 负载均衡的核心方法
*/
@edu.umd.cs.findbugs.annotations.SuppressWarnings(value = "RCN_REDUNDANT_NULLCHECK_OF_NULL_VALUE")
public Server choose(ILoadBalancer lb, Object key) {
if (lb == null) {
return null;
}
Server server = null;
while (server == null) {
if (Thread.interrupted()) {
return null;
}
// 可用的服务实例列表
List<Server> upList = lb.getReachableServers();
// 所有的服务实例
List<Server> allList = lb.getAllServers();
int serverCount = allList.size();
if (serverCount == 0) {
/*
* No servers. End regardless of pass, because subsequent passes
* only get more restrictive.
*/
return null;
}
// 根据服务实例数量获取一个随机数,根据随机数获取服务实例
int index = chooseRandomInt(serverCount);
server = upList.get(index);
if (server == null) {
/*
* The only time this should happen is if the server list were
* somehow trimmed. This is a transient condition. Retry after
* yielding.
*/
Thread.yield();
continue;
}
// 判断服务实例是否可用,可用及返回
if (server.isAlive()) {
return (server);
}
// Shouldn't actually happen.. but must be transient or a bug.
server = null;
Thread.yield();
}
return server;
}
protected int chooseRandomInt(int serverCount) {
return ThreadLocalRandom.current().nextInt(serverCount);
}
@Override
public Server choose(Object key) {
return choose(getLoadBalancer(), key);
}
@Override
public void initWithNiwsConfig(IClientConfig clientConfig) {
// TODO Auto-generated method stub
}
}