SpringCloud中使用Netflix方案做分布式时,只需要在RestTemplate的bean定义上加一个注解@LoadBalanced,无需做其它任何操作就可以开启负载均衡,怎么做到的呢?

不从@LoadBalanced开始倒推,我觉得简单描述正向实现流程,更容易理解

从RestTemplate入手

Ribbon的负载均衡实现,其实就是利用了RestTemplate上的可自定义拦截器功能,给RestTemplate的bean定义上添加@LoadBalanced就是为了给RestTemplate对象添加上指定的拦截器

RestTemplate上具有的拦截器功能来自于RestTemplate的父类InterceptingHttpAccessor

public class RestTemplate extends InterceptingHttpAccessor implements RestOperations
public abstract class InterceptingHttpAccessor extends HttpAccessor {

  private final List<ClientHttpRequestInterceptor> interceptors = new ArrayList<>();

  public void setInterceptors(List<ClientHttpRequestInterceptor> interceptors) {
// Take getInterceptors() List as-is when passed in here
if (this.interceptors != interceptors) {
this.interceptors.clear();
this.interceptors.addAll(interceptors);
AnnotationAwareOrderComparator.sort(this.interceptors);
}
//省略其它代码
}
}

接下来再看下是怎么加上的拦截器,自动加上的,就先看自动化配置LoadBalancerAutoConfiguration,相关配置如下

public class LoadBalancerAutoConfiguration {

      @Bean
public SmartInitializingSingleton loadBalancedRestTemplateInitializerDeprecated(
final ObjectProvider<List<RestTemplateCustomizer>> restTemplateCustomizers) {
return () -> restTemplateCustomizers.ifAvailable(customizers -> {
for (RestTemplate restTemplate : LoadBalancerAutoConfiguration.this.restTemplates) {
for (RestTemplateCustomizer customizer : customizers) {
//执行RestTemplateCustomizer.customize()方法,对restTemplate进行增强
customizer.customize(restTemplate);
}
}
});
} @Bean
@ConditionalOnMissingBean
public LoadBalancerRequestFactory loadBalancerRequestFactory(
LoadBalancerClient loadBalancerClient) {
return new LoadBalancerRequestFactory(loadBalancerClient, this.transformers);
} @Configuration
@ConditionalOnMissingClass("org.springframework.retry.support.RetryTemplate")
static class LoadBalancerInterceptorConfig { //定义LoadBalancerInterceptor
@Bean
public LoadBalancerInterceptor ribbonInterceptor(
LoadBalancerClient loadBalancerClient,
LoadBalancerRequestFactory requestFactory) {
return new LoadBalancerInterceptor(loadBalancerClient, requestFactory);
} @Bean
@ConditionalOnMissingBean
public RestTemplateCustomizer restTemplateCustomizer(
final LoadBalancerInterceptor loadBalancerInterceptor) {
return restTemplate -> {
List<ClientHttpRequestInterceptor> list = new ArrayList<>(
restTemplate.getInterceptors());
list.add(loadBalancerInterceptor);
//将定义LoadBalancerInterceptor拦截器添加到restTemplate的拦截器列表中
restTemplate.setInterceptors(list);
};
} }
}

LoadBalancerInterceptor里怎么做的呢?看下

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 {
final URI originalUri = request.getURI();
String serviceName = originalUri.getHost();
Assert.state(serviceName != null,
"Request URI does not contain a valid hostname: " + originalUri);
return this.loadBalancer.execute(serviceName,
this.requestFactory.createRequest(request, body, execution));
} }

发现了,从服务调用URI中获取到serviceName,然后再传给LoadBalancerClient

如果去看注解@LoadBalanced的定义,也能发现里面提到了LoadBalancerClient

/**
* Annotation to mark a RestTemplate bean to be configured to use a LoadBalancerClient.
* @author Spencer Gibb
*/
@Target({ ElementType.FIELD, ElementType.PARAMETER, ElementType.METHOD })
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@Qualifier
public @interface LoadBalanced { }

看下LoadBalancerClient,就是个接口

public interface LoadBalancerClient extends ServiceInstanceChooser {

      // 根据传入的serviceId,从负载均衡器中挑选服务实例执行请求内容
<T> T execute(String serviceId, LoadBalancerRequest<T> request) throws IOException; // 根据传入的serviceId,用指定的服务实例执行请求内容
<T> T execute(String serviceId, ServiceInstance serviceInstance,LoadBalancerRequest<T> request) throws IOException; // 将服务实例转换成实际的URI信息
URI reconstructURI(ServiceInstance instance, URI original); }

再看下父类ServiceInstanceChooser

public interface ServiceInstanceChooser {

      // 从负载均衡器中挑选服务实例
ServiceInstance choose(String serviceId); }

而LoadBalancerClient的实现呢,只有一个,就是RibbonLoadBalancerClient,那么主要逻辑肯定就在这里面了

public class RibbonLoadBalancerClient implements LoadBalancerClient {

	private SpringClientFactory clientFactory;

	public RibbonLoadBalancerClient(SpringClientFactory clientFactory) {
this.clientFactory = clientFactory;
} @Override
public URI reconstructURI(ServiceInstance instance, URI original) {
Assert.notNull(instance, "instance can not be null");
String serviceId = instance.getServiceId();
//从springClientFactory中根据serviceId获取到RibbonLoadBalancerContext
RibbonLoadBalancerContext context = this.clientFactory.getLoadBalancerContext(serviceId); URI uri;
Server server;
if (instance instanceof RibbonServer) {
RibbonServer ribbonServer = (RibbonServer) instance;
server = ribbonServer.getServer();
uri = updateToSecureConnectionIfNeeded(original, ribbonServer);
}
else {
server = new Server(instance.getScheme(), instance.getHost(),
instance.getPort());
IClientConfig clientConfig = clientFactory.getClientConfig(serviceId);
ServerIntrospector serverIntrospector = serverIntrospector(serviceId);
uri = updateToSecureConnectionIfNeeded(original, clientConfig,
serverIntrospector, server);
}
//RibbonLoadBalancerContext.reconstructURIWithServer(),将服务名的调用URL转换成真实IP地址的实际操作逻辑
return context.reconstructURIWithServer(server, uri);
}
//代码太多,省略其它代码
}

里面还有些其它代码,就不一一贴出来了,其它的基本都是对LoadBalancerClient的实现,看这个重构URI的方法就能看到RibbonLoadBalancerContext和IClientConfig都是从SpringClientFactory clientFactory里取出来的,而且也能看到RibbonLoadBalancerClient的构造函数也只需要这一个属性,有SpringClientFactory就能创建出来RibbonLoadBalancerClient对象,那这个肯定很重要,看下是什么

public class SpringClientFactory extends NamedContextFactory<RibbonClientSpecification> {

      static final String NAMESPACE = "ribbon";

      public SpringClientFactory() {
super(RibbonClientConfiguration.class, NAMESPACE, "ribbon.client.name");
} public ILoadBalancer getLoadBalancer(String name) {
return getInstance(name, ILoadBalancer.class);
} public IClientConfig getClientConfig(String name) {
return getInstance(name, IClientConfig.class);
} public RibbonLoadBalancerContext getLoadBalancerContext(String serviceId) {
return getInstance(serviceId, RibbonLoadBalancerContext.class);
} @Override
public <C> C getInstance(String name, Class<C> type) {
C instance = super.getInstance(name, type);
if (instance != null) {
return instance;
}
IClientConfig config = getInstance(name, IClientConfig.class);
return instantiateWithConfig(getContext(name), type, config);
}
//获取负载均衡器
protected ILoadBalancer getLoadBalancer(String serviceId) {
return this.clientFactory.getLoadBalancer(serviceId);
}
//省略其它代码
}

SpringClientFactory是NamedContextFactory的子类,这些获取ILoadBalancer、IClientConfig、RibbonLoadBalancerContext的方法,都是调用父类中的getInstance(name, type)而已

public abstract class NamedContextFactory<C extends NamedContextFactory.Specification> implements DisposableBean, ApplicationContextAware {

      private final String propertySourceName;

      private final String propertyName;

      //存放AnnotationConfigApplicationContext的容器,每一个服务都对应一个AnnotationConfigApplicationContext,没有就创建
private Map<String, AnnotationConfigApplicationContext> contexts = new ConcurrentHashMap<>(); private Map<String, C> configurations = new ConcurrentHashMap<>(); private ApplicationContext parent; private Class<?> defaultConfigType; 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);
} //通过name(也就是serviceId)获取到AnnotationConfigApplicationContext,然后再从AnnotationConfigApplicationContext里取到type类型的bean
public <T> T getInstance(String name, Class<T> type) {
AnnotationConfigApplicationContext context = getContext(name);
if (BeanFactoryUtils.beanNamesForTypeIncludingAncestors(context,type).length > 0) {
return context.getBean(type);
}
return null;
}
}

到这也就基本到头了,NamedContextFactory是专门定义出来的,每一个需要调用的微服务都创建一个AnnotationConfigApplicationContext,并存放各自的bean,也就是说IOC容器分离开了,没有混在一起,这样可以做到针对不同的微服务情况做不同的配置

再接下来,就是找到这些配置,或者自定义这些bean的配置就可以了,也就是负载均衡器、负载均衡策略等的配置了

自动配置

在RibbonAutoConfiguration中有如下相关配置

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());
}
//省略其它配置
}

创建SpringClientFactory时需要设置一个属性List configurations,这个集合里的值是这么来的:

  1. 来自于RibbonClientConfigurationRegistrar中registerBeanDefinitions()方法的执行
  2. RibbonClientConfigurationRegistrar会执行是因为@RibbonClient上有个@Import(RibbonClientConfigurationRegistrar.class)
  3. @RibbonClient是@RibbonClients注解里的属性
  4. @RibbonClients又在RibbonAutoConfiguration和RibbonEurekaAutoConfiguration上有了使用,并且在EurekaRibbonClientConfiguration上标注时,做了配置@RibbonClients(defaultConfiguration =
  5. EurekaRibbonClientConfiguration中呢又包含EurekaClientConfig(eureka.client相关属性配置类),EurekaInstanceConfig(eureka.instance相关属性配置类)

就这样一长串的关联,就能把相关bean的配置给引入到了SpringClientFactory中,并可以按服务给分别设定了IOC容器并做了bean注入(PS:我也只是看出来了相关性,没看那么具体)

通过这两个Bean的配置,IOC容器中就有了RibbonLoadBalancerClient对象

在RibbonClientConfiguration中,有如下相关配置

public class RibbonClientConfiguration {

      @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 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();
} @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);
}
return new ZoneAwareLoadBalancer<>(config, rule, ping, serverList,
serverListFilter, serverListUpdater);
} @Bean
@ConditionalOnMissingBean
public RibbonLoadBalancerContext ribbonLoadBalancerContext(ILoadBalancer loadBalancer,
IClientConfig config, RetryHandler retryHandler) {
return new RibbonLoadBalancerContext(loadBalancer, config, retryHandler);
}
}

可以看到,默认的负载均衡器ILoadBalancer配置是ZoneAwareLoadBalancer,默认的负载策略IRule配置是ZoneAvoidanceRule,默认的实例状态检查IPing配置是DummyPing

这几个也是我们常用的自定义的配置项,可以针对不同需要进行配置,@ConditionalOnMissingBean,如果有手动配置了,那么自动配置便不会生效了

自定义配置

负载均衡器ZoneAwareLoadBalancer是实现好的区域感知均衡器,对同Zone区域的服务友好会优先调用,继承自DynamicServerListLoadBalancer动态服务列表均衡器,DynamicServerListLoadBalancer又继承自BaseLoadBalancer基础均衡器,各项都有了基本实现,如果有需要进行负载均衡器调整的话,得需要自己去实现了,可以通过继承DynamicServerListLoadBalancer或BaseLoadBalancer来做

默认的负载策略IRule配置是ZoneAvoidanceRule,也是对区域友好的,要调整的话,这项有其它的选择,在IRule下有很多实现,如:

  1. RandomRule随机策略
  2. RoundRobinRule线性轮询策略
  3. RetryRule重试机制策略(基于其它策略的具备重试机制选择策略[默认RoundRobinRule]和超时时间[默认500毫秒]的配置)
  4. WeightedResponseTimeRule响应时间权重策略(继承自RoundRobinRule并扩展了按服务响应时间计算权重)
  5. BestAvailableRule最佳可用策略(过滤掉故障实例,选出并发请求数最小的实例)
  6. AvailabilityFilteringRule可用性过滤器策略(选取没有故障且没有超过指定的可配置并发阀值的实例)

默认的实例状态检查IPing配置是DummyPing,Dummy就是“假的”的意思,看代码里也是,检查时什么操作都没做,直接返回true,其它可选配置有:

  1. NIWSDiscoveryPing(不做ping检查,只是判断下服务状态是否是UP)
  2. NoOpPing无操作检查(也是啥都不做)
  3. PingConstant(通过一个可设置的boolean变量来控制检查结果)
  4. PingUrl连通性检查(可指定要ping的url路径)

总结

大概流程在这了,写的不好,请见谅,有错误的地方欢迎指正,感谢~

Ribbon负载均衡的实现流程简要分析的更多相关文章

  1. 小D课堂 - 新版本微服务springcloud+Docker教程_4-03 高级篇幅之Ribbon负载均衡源码分析实战

    笔记 3.高级篇幅之Ribbon负载均衡源码分析实战     简介: 讲解ribbon服务间调用负载均衡源码分析         1.完善下单接口         2.分析@LoadBalanced ...

  2. 四. Ribbon负载均衡服务调用

    1. 概述 1.1 Ribbon是什么 SpringCloud Ribbon是基于Netflix Ribbon实现的一套客户端,是负载均衡的工具. Ribbon是Netflix发布的开源项目,主要功能 ...

  3. 微服务(三) Eureka注册中心和Ribbon负载均衡

    1. Eureka注册中心 1.1 Eureka的结构和作用 在上一篇文章中 微服务(二)服务拆分及远程调用 order-service在发起远程调用的时候,该如何得知user-service实例的i ...

  4. (4)什么是Ribbon负载均衡

    4.Ribbon负载均衡 上一节中,我们添加了@LoadBalanced注解,即可实现负载均衡功能,这是什么原理呢? 4.1.负载均衡原理 SpringCloud底层其实是利用了一个名为Ribbon的 ...

  5. SpringCloud系列——Ribbon 负载均衡

    前言 Ribbon是一个客户端负载均衡器,它提供了对HTTP和TCP客户端的行为的大量控制.我们在上篇(猛戳:SpringCloud系列——Feign 服务调用)已经实现了多个服务之间的Feign调用 ...

  6. Spring Cloud微服务Ribbon负载均衡/Zuul网关使用

    客户端负载均衡,当服务节点出现问题时进行调节或是在正常情况下进行 服务调度.所谓的负载均衡,就是当服务提供的数量和调用方对服务进行 取舍的调节问题,在spring cloud中是通过Ribbon来解决 ...

  7. SpringCloud系列五:Ribbon 负载均衡(Ribbon 基本使用、Ribbon 负载均衡、自定义 Ribbon 配置、禁用 Eureka 实现 Ribbon 调用)

    1.概念:Ribbon 负载均衡 2.具体内容 现在所有的服务已经通过了 Eureka 进行了注册,那么使用 Eureka 注册的目的是希望所有的服务都统一归属到 Eureka 之中进 行处理,但是现 ...

  8. SpringCloud无废话入门02:Ribbon负载均衡

    1.白话负载均衡 在上一篇的介绍中,我们创建了两个一模一样的服务提供者:Provider1和Provider2,然后它们提供的服务也一模一样,都叫Hello-Service.为什么一样的服务我们要部署 ...

  9. spring cloud: 关闭ribbon负载均衡

    spring cloud: 关闭ribbon负载均衡 1.eureka服务 2.2个user服务:7900/7901 3,movie服务 movie服务去请求 user的用户信息,而此时只想请求790 ...

  10. spring-cloud: eureka之:ribbon负载均衡自定义配置(二)

    spring-cloud: eureka之:ribbon负载均衡自定义配置(二) 有默认配置的话基本上就是轮询接口,现在我们改用自定义配置,同时支持:轮询,随机接口读取 准备工作: 1.eureka服 ...

随机推荐

  1. gird 布局控制元素都显示在一行

    gird 布局控制元素都显示在一行 <ul class="list"> <li v-for="(li, index) in list" :ke ...

  2. Virtualbox网络设置

    记录一下https://ladybug.top/%E8%BD%AF%E4%BB%B6%E5%AE%89%E8%A3%85&%E9%85%8D%E7%BD%AE/complete-the-net ...

  3. python3.10.0字符串基础

    字符串支持 索引 (下标访问),第一个字符的索引是 0.单字符没有专用的类型,就是长度为一的字符串: >>> word = 'Python' >>> word[0] ...

  4. WPF materialDesign 锁屏后界面卡死问题解决

    materialDesign的一个缓存机制问题,在xaml文件中Window属性添加一行: materialDesign:ShadowAssist.CacheMode="{x:Null}&q ...

  5. laravel request lifecycle

    1,  index.php2, 生成service container3,  service provider register/booted4, dispatch routing5, middlew ...

  6. 线性斜压模式LBM学习&安装实录

    本文基本参照了LBM的用户手册进行. 环境:Ubuntu 18.04LTS (Windows Subsystem Linux) 编译器:gfortran 7.5.0 安装包: lapack-3.9.0 ...

  7. 解题报告:Codeforces 279C Ladder

    Codeforces 279C Ladder 本题与tbw这篇博客上的题有相似思路.tbw的本来我还不会,抄了题解才过,这道题好歹自己磕半天磕出来了.到tbw做那道题我突然想明白了,再一想诶跟这里不是 ...

  8. MySQL 5.7升级8.0过程(详解)

    记一次MySQL 5.7升级8.0的详细过程,聊聊我的思路,希望可以帮助大家. 以一个例子为切入点 一.升级背景 为什么要升级到MySQL8.0?大概多久进行一次? 大家可以参考下图记录的各个版本的发 ...

  9. Go实现KMP和Sunday算法

    KMP 1 func KMP(str, substr string) int { 2 if substr == "" { 3 return 0 4 } 5 strLen := le ...

  10. Retrofit简要分析

    Retrofit是对网络请求实现了一套请求架构封装,屏蔽底层网络实现,使网络请求像调用本地接口一样 基本使用例子 public interface GitHubService {//定义request ...