文章目录

  • 1. 什么是负载均衡?
  • 2. Ribbon的使用
    • ①:自定义负载均衡策略
    • ②:Ribbon的饥饿加载
  • 3. Ribbon的负载均衡原理
    • ①:收集带有@LoadBalanced注解的RestTemplate,并为其添加一个负载均衡拦截器
    • ②:选择负载均衡器,执行负载均衡算法(默认轮询)
    • Ribbon负载均衡器的默认实现:ZoneAwareLoadBalancer
    • Ribbon负载均衡算法的默认实现:ZoneAvoidanceRule
  • 4. Feign的原理
  • 5. OpenFeign是如何整合Ribbon的?
    • ①:扫描所有@FeignClient注解,以FactoryBean的形式注册到容器中
    • ②:RPC调用时,通过LoadBalancerFeignClient整合Ribbon,实现负载均衡调用
  • 6. 总结图

1. 什么是负载均衡?

负载均衡是从多个服务中根据某个策略选择一个进行访问,常见的负载均衡分为两种

  1. 客户端负载均衡:即在客户端就进行负载均衡算法分配。例如spring cloud中的ribbon,客户端会有一个服务器地址列表,在发送请求前通过负载均衡算法选择 一个服务器,然后进行访问
  2. 服务端负载均衡:在消费者和服务提供方中间使用独立的代理方式进行负载。例如Nginx,先发送请求,然后通过Nginx的负载均衡算法,在多个服务器之间选择一 个进行访问!

常见的负载均衡算法:

  • 随机:通过随机选择服务进行执行,一般这种方式使用较少;
  • 轮询:请求来之后排队处理,轮着来
  • 加权轮询:通过对服务器性能的分型,给高配置,低负载的服务器分配更高的权重,均衡各个 服务器的压力;
  • 一致性hash:通过客户端请求的地址的HASH值取模映射进行服务器调度。
  • 最少并发:将请求分配到当前压力最小的服务器上

2. Ribbon的使用

Ribbon属于netflix的产品,依赖如下

<!--添加ribbon的依赖-->
<dependency><groupId>org.springframework.cloud</groupId><artifactId>spring-cloud-starter-netflix-ribbon</artifactId>
</dependency>

但 spring-cloud 体系下的大多数产品都整合和ribbon,如服务发现nacos-discovery,RPC调用feign组件等等,所以,使用时可以不用再引入ribbon依赖

使用Ribbon时只需添加@LoadBalanced注解即可,代表当前请求拥有了负载均衡的能力

①:为RestTemplate添加@LoadBalanced注解

@Configuration
public class RestConfig {@Bean@LoadBalancedpublic RestTemplate restTemplate() {return new RestTemplate();}

②:使用RestTemplate进行远程调用,此次调用有负载均衡效果!

@Autowired
private RestTemplate restTemplate;@RequestMapping(value = "/findOrderByUserId/{id}")
public R  findOrderByUserId(@PathVariable("id") Integer id) {String url = "http://order/findOrderByUserId/"+id;R result = restTemplate.getForObject(url,R.class);return result;
}

①:自定义负载均衡策略

自定义负载均衡策略方式有多种

  • 实现 IRule 接口
  • 或者继承AbstractLoadBalancerRule

实现基于Nacos权重的负载均衡策略:nacos中权重越大的实例请求频次越高!


//继承 AbstractLoadBalancerRule 类
@Slf4j
public class NacosRandomWithWeightRule extends AbstractLoadBalancerRule {@Autowiredprivate NacosDiscoveryProperties nacosDiscoveryProperties;@Overridepublic Server choose(Object key) {//获取负载均衡器DynamicServerListLoadBalancer loadBalancer = (DynamicServerListLoadBalancer) getLoadBalancer();String serviceName = loadBalancer.getName();NamingService namingService = nacosDiscoveryProperties.namingServiceInstance();try {//nacos基于权重的算法Instance instance = namingService.selectOneHealthyInstance(serviceName);return new NacosServer(instance);} catch (NacosException e) {log.error("获取服务实例异常:{}", e.getMessage());e.printStackTrace();}return null;}@Overridepublic void initWithNiwsConfig(IClientConfig clientConfig) {}

自定义负载均衡策略的配置也有两种

  • 全局配置:当前服务调用其他微服务时,一律使用指定的负载均衡算法

    @Configuration
    public class RibbonConfig {/*** 全局配置* 指定负载均衡策略* @return*/@Beanpublic IRule ribbonRule() {// 指定使用基于`Nacos`权重的负载均衡策略:`nacos`中权重越大的实例请求频次越高!return new NacosRandomWithWeightRule();}
    
  • 局部配置:当前服务调用指定的微服务时,使用对应的负载均衡算法,比如调用order服务使用该算法,调用其他的不使用!

    # 被调用的微服务名
    mall-order:ribbon:# 自定义的负载均衡策略(基于随机&权重)NFLoadBalancerRuleClassName: com.china.test.ribbondemo.rule.NacosRandomWithWeightRule
    

②:Ribbon的饥饿加载

Ribbon默认懒加载,意味着只有在发起调用的时候才会创建客户端。在第一次进行服务调用时会做一些初始化工作,比如:创建负载均衡器 ,如果网络情况不好,这次调用可能会超时。

可以开启饥饿加载,在项目启动时就完成初始化工作,解决第一次调用慢的问题

ribbon:eager-load:# 开启ribbon饥饿加载,源码对应属性配置类:RibbonEagerLoadPropertiesenabled: true# 配置mall-user使用ribbon饥饿加载,多个使用逗号分隔clients: mall-order

开启之后,可以看到,第一次调用日志已经没有初始化工作了

3. Ribbon的负载均衡原理

①:收集带有@LoadBalanced注解的RestTemplate,并为其添加一个负载均衡拦截器

上面的使用案例中,如果不加@LoadBalanced注解的话,RestTemplate没有负载均衡功能的,为什么一个@LoadBalanced注解就使RestTemplate具有负载均衡功能了呢?下面来看一下Ribbon的负载均衡原理

Ribbon既然在springboot中使用,自然会想到springboot对Ribbon的自动配置类RibbonAutoConfiguration!这个自动配置类被加载的前置条件是:需要加载LoadBalancerAutoConfiguration类,如下所示

@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 {

LoadBalancerAutoConfiguration类是属于spring-cloud-common包下的,该包是spring cloud的基础包,肯定会被加载的

进入LoadBalancerAutoConfiguration类中,该类中注册了几个bean,主要做了以下几件事

  1. 收集到所有带有@LoadBalanced注解的RestTemplate,并放入restTemplates 集合
  2. 创建一个带有负载均衡功能的拦截器LoadBalancerInterceptor
  3. 在容器类初始化完毕后,把所有的RestTemplate内部都添加上拦截器LoadBalancerInterceptor,当有请求经过ribbon,通过 restTemplate 发起调用时,会先走此拦截器,实现负载均衡
@Configuration
@ConditionalOnClass(RestTemplate.class)
@ConditionalOnBean(LoadBalancerClient.class)
@EnableConfigurationProperties(LoadBalancerRetryProperties.class)
public class LoadBalancerAutoConfiguration {//Springboot会将所有带有@LoadBalanced注解的RestTemplate,都放进restTemplates这个集合中去@LoadBalanced@Autowired(required = false)private List<RestTemplate> restTemplates = Collections.emptyList();@Autowired(required = false)private List<LoadBalancerRequestTransformer> transformers = Collections.emptyList();//SmartInitializingSingleton :该方法会等待所有类都初始化完毕后执行// 拿到上面收集到的所有带有@LoadBalanced注解的RestTemplate// 执行下面的函数式接口方法customize,把拦截器 放入每一个restTemplate中,//当有请求经过ribbon,通过 restTemplate 发起调用时,会先走此拦截器,实现负载均衡@Beanpublic SmartInitializingSingleton loadBalancedRestTemplateInitializerDeprecated(final ObjectProvider<List<RestTemplateCustomizer>> restTemplateCustomizers) {return () -> restTemplateCustomizers.ifAvailable(customizers -> {for (RestTemplate restTemplate : LoadBalancerAutoConfiguration.this.restTemplates) {for (RestTemplateCustomizer customizer : customizers) {customizer.customize(restTemplate);}}});}@Bean@ConditionalOnMissingBeanpublic LoadBalancerRequestFactory loadBalancerRequestFactory(LoadBalancerClient loadBalancerClient) {return new LoadBalancerRequestFactory(loadBalancerClient, this.transformers);}@Configuration@ConditionalOnMissingClass("org.springframework.retry.support.RetryTemplate")static class LoadBalancerInterceptorConfig {//向容器中放入一个带有负载均衡功能的拦截器@Beanpublic LoadBalancerInterceptor ribbonInterceptor(LoadBalancerClient loadBalancerClient,LoadBalancerRequestFactory requestFactory) {return new LoadBalancerInterceptor(loadBalancerClient, requestFactory);}//这是一个函数式接口,此处实现的是接口的customize方法,定义了一个操作,操作内容如下:// 传入一个restTemplate,并把上面的拦截器 放入restTemplate中//当有请求经过ribbon,通过 restTemplate 发起调用时//会先走此拦截器,实现负载均衡@Bean@ConditionalOnMissingBeanpublic RestTemplateCustomizer restTemplateCustomizer(final LoadBalancerInterceptor loadBalancerInterceptor) {return restTemplate -> {List<ClientHttpRequestInterceptor> list = new ArrayList<>(restTemplate.getInterceptors());list.add(loadBalancerInterceptor);restTemplate.setInterceptors(list);};}}

LoadBalancerAutoConfiguration是如何精准的收集到所有的带有@LoadBalanced注解的RestTemplate呢?点开@LoadBalanced注解,发现他是带有@Qualifier限定符的!

@Target({ ElementType.FIELD, ElementType.PARAMETER, ElementType.METHOD })
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@Qualifier  //spring的限定符
public @interface LoadBalanced {}

@Qualifier限定符的作用:

  • 一个接口有两个实现类,当我们通过 @AutoWired 注解进行注入时,spring不知道应该绑定哪个实现类,从而导致报错。
  • 这时就可以通过 @Qualifier注解来解决。通过它可以标识我们需要的实现类。而@LoadBalanced的元注解是 @Qualifier,所以 源码中就可以通过@LoadBalanced注解来限定收集所有带有@LoadBalanced注解的RestTemplate实现

②:选择负载均衡器,执行负载均衡算法(默认轮询)

上面说到请求经过ribbon的RestTemplate调用时,会先走其内部的拦截器LoadBalancerInterceptor的负载均衡逻辑。既然是走拦截器,那么就可以去看LoadBalancerInterceptorintercept()方法,一般该方法就有负载均衡逻辑!

 @Overridepublic 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);//负载均衡器的 execute 方法return this.loadBalancer.execute(serviceName,this.requestFactory.createRequest(request, body, execution));}
 public <T> T execute(String serviceId, LoadBalancerRequest<T> request, Object hint)throws IOException {//获取负载均衡器 ILoadBalancer ILoadBalancer loadBalancer = getLoadBalancer(serviceId);// 通过负载均衡选择一个服务器Server server = getServer(loadBalancer, hint);if (server == null) {throw new IllegalStateException("No instances available for " + serviceId);}RibbonServer ribbonServer = new RibbonServer(serviceId, server,isSecure(server, serviceId),serverIntrospector(serviceId).getMetadata(server));//向某一台服务器 发起HTTP请求return execute(serviceId, ribbonServer, request);}

如上,Ribbon通过负载均衡 选择一台机器 发起http请求已经执行完毕!

Ribbon负载均衡器的默认实现:ZoneAwareLoadBalancer

上面说到getLoadBalancer(serviceId)方法可以获取一个负载均衡器,用于执行负载均衡算法,这个负载均衡器已经在RibbonClientConfiguration配置类中初始化好了,获取时直接从容器中取即可

 @Bean@ConditionalOnMissingBeanpublic ILoadBalancer ribbonLoadBalancer(IClientConfig config,ServerList<Server> serverList, ServerListFilter<Server> serverListFilter,IRule rule, IPing ping, ServerListUpdater serverListUpdater) {// 从配置类中找一个负载均衡器 ILoadBalancerif (this.propertiesFactory.isSet(ILoadBalancer.class, name)) {return this.propertiesFactory.get(ILoadBalancer.class, config, name);}// 如果没有就创建一个负载均衡器的实现 ZoneAwareLoadBalancerreturn new ZoneAwareLoadBalancer<>(config, rule, ping, serverList,serverListFilter, serverListUpdater);}

可以看到RibbonClientConfiguration配置类中,默认初始化的是ZoneAwareLoadBalancer,它具备区域感知的能力。

在创建默认负载均衡器时(new ZoneAwareLoadBalancer)做了什么呢?

  1. nacos注册中心上立即获取最新的服务信息,保存在ribbon的本地服务列表中
  2. 使用延时定时线程池,定时从nacos上拉取最新服务地址,更新ribbon的本地服务列表中

进入new ZoneAwareLoadBalancer中:

    void restOfInit(IClientConfig clientConfig) {boolean primeConnection = this.isEnablePrimingConnections();// turn this off to avoid duplicated asynchronous priming done in BaseLoadBalancer.setServerList()this.setEnablePrimingConnections(false);//开启定时延时任务(会延后执行),定时从nacos上拉取最新服务地址,更新`ribbon`的本地服务列表中enableAndInitLearnNewServersFeature();//进入后立即执行,从`nacos`注册中心上立即获取最新的服务信息,保存在`ribbon`的本地服务列表中updateListOfServers();if (primeConnection && this.getPrimeConnections() != null) {this.getPrimeConnections().primeConnections(getReachableServers());}this.setEnablePrimingConnections(primeConnection);LOGGER.info("DynamicServerListLoadBalancer for client {} initialized: {}", clientConfig.getClientName(), this.toString());}

开启定时任务方法enableAndInitLearnNewServersFeature();如下:

    @Overridepublic synchronized void start(final UpdateAction updateAction) {if (isActive.compareAndSet(false, true)) {//开启一个线程,执行更新任务final Runnable wrapperRunnable = new Runnable() {@Overridepublic void run() {if (!isActive.get()) {if (scheduledFuture != null) {scheduledFuture.cancel(true);}return;}try {//UpdateAction 又是一个函数式接口,//doUpdate方法需要看一下传进来的方法内容,下文展示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");}}================== 函数式接口的 updateAction.doUpdate()方法内容如下=============protected final ServerListUpdater.UpdateAction updateAction = new ServerListUpdater.UpdateAction() {@Overridepublic void doUpdate() {updateListOfServers();}};// updateListOfServers方法如下:@VisibleForTestingpublic void updateListOfServers() {List<T> servers = new ArrayList<T>();if (serverListImpl != null) {// 该方法会从对应的配置中心中取最新数据servers = serverListImpl.getUpdatedListOfServers();LOGGER.debug("List of Servers for {} obtained from Discovery client: {}",getIdentifier(), servers);if (filter != null) {servers = filter.getFilteredListOfServers(servers);LOGGER.debug("Filtered List of Servers for {} obtained from Discovery client: {}",getIdentifier(), servers);}}// 更新本地服务列表!updateAllServerList(servers);}

负载均衡器初始化时,立即从注册中心获取最新服务的方法updateListOfServers(),如下:

    @VisibleForTestingpublic void updateListOfServers() {List<T> servers = new ArrayList<T>();if (serverListImpl != null) {//从注册中心上获取服务地址servers = serverListImpl.getUpdatedListOfServers();LOGGER.debug("List of Servers for {} obtained from Discovery client: {}",getIdentifier(), servers);if (filter != null) {servers = filter.getFilteredListOfServers(servers);LOGGER.debug("Filtered List of Servers for {} obtained from Discovery client: {}",getIdentifier(), servers);}}//更新本地服务列表!updateAllServerList(servers);}

Ribbon负载均衡算法的默认实现:ZoneAvoidanceRule

有了负载均衡器ZoneAwareLoadBalancer,接下来执行负载均衡算法即可getServer(loadBalancer, hint);,回顾上边第②条的代码:

 public <T> T execute(String serviceId, LoadBalancerRequest<T> request, Object hint)throws IOException {//获取负载均衡器 ILoadBalancer ILoadBalancer loadBalancer = getLoadBalancer(serviceId);// 通过负载均衡算法 选择一个服务器地址Server server = getServer(loadBalancer, hint);if (server == null) {throw new IllegalStateException("No instances available for " + serviceId);}RibbonServer ribbonServer = new RibbonServer(serviceId, server,isSecure(server, serviceId),serverIntrospector(serviceId).getMetadata(server));//向某一台服务器 发起HTTP请求return execute(serviceId, ribbonServer, request);}

进入负载均衡算法选择服务器的方法getServer(loadBalancer, hint)

    public Server chooseServer(Object key) {if (counter == null) {counter = createCounter();}counter.increment();if (rule == null) {return null;} else {try {//这个rule就是ribbon的负载均衡算法!//默认是 ZoneAvoidanceRule ,在没有区域的环境下,类似于轮询(RandomRule)return rule.choose(key);} catch (Exception e) {logger.warn("LoadBalancer [{}]:  Error choosing server for key {}", name, key, e);return null;}}}

ZoneAvoidanceRule 的核心逻辑如下:使用cas+ 死循环轮询服务器地址

    private int incrementAndGetModulo(int modulo) {for (;;) {int current = nextIndex.get();//取模得到其中一个int next = (current + 1) % modulo;//cas赋值 ,返回nextIndex的机器if (nextIndex.compareAndSet(current, next) && current < modulo)return current;}}

其中,ZoneAvoidanceRule的初始化和负载均衡器ZoneAwareLoadBalancer的初始化一样,也在RibbonClientConfiguration配置类中完成!

 @Bean@ConditionalOnMissingBeanpublic 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;}

Ribbon的负载均衡策略有如下几种

  • RandomRule:随机策略, 随机选择一个Server。
  • RetryRule: 重试策略 。对选定的负载均衡策略机上重试机制,在一个配置时间段内当选择Server不成功,则一直尝试使用subRule的方式选择一个可用的server。
  • RoundRobinRule: 轮询策略 。 轮询index,选择index对应位置的Server。
  • AvailabilityFilteringRule:可用性过滤策略 。 过滤掉一直连接失败的被标记为circuit tripped的后端Server,并过滤掉那些高并发的后端Server或者使用一个AvailabilityPredicate来包含过滤server的逻辑,其实就是检查status里记录的各个Server的运行状态。
  • BestAvailableRule:最低并发策略 。 选择一个最小的并发请求的Server,逐个考察Server,如果Server被tripped了,则跳过。
  • WeightedResponseTimeRule:响应时间加权重策略。根据响应时间加权,响应时间越长,权重越小,被选中的可能性越低。
  • ZoneAvoidanceRule:区域权重策略。默认的负载均衡策略,综合判断server所在区域的性能和server的可用性,轮询选择server并且判断一个AWS Zone的运行性能是否可用,剔除不可用的Zone中的所有server。在没有区域的环境下,类似于轮询(RandomRule)
  • NacosRule: 同集群优先调用

4. Feign的原理

Feign和OpenFeign的区别?

  • Feign:Feign是Netflix开发的声明式、模板化的HTTP客户端,Feign可帮助我们更加便捷、优雅地调用HTTP API。可以单独使用
  • OpenFeign:Spring Cloud openfeign对Feign进行了 增强,使其支持Spring MVC注解,另外还整合了RibbonEureka,从而使得Feign的使用更加方便

Feign的调用原理图(可在每一层做扩展)

OpenFeign的常用配置项:(对应上图,可以在配置中做扩展)

  • 日志配置:有时候我们遇到 Bug,比如接口调用失败、参数没收到等问题,或者想看看调用性能,就需要配置 Feign 的 日志了,以此让 Feign 把请求信息输出来。日志配置分为局部配置和全局配置!
  • 拦截器配置:每次 feign 发起http调用之前,会去执行拦截器中的逻辑,就类似mvc中的拦截器。比如:做权限认证。
  • 超时时间配置:通过 Options 可以配置连接超时时间(默认2秒)和读取超时时间(默认5秒),注意:Feign的底层用的是Ribbon,但超时时间以Feign配置为准
  • 客户端组件配置:Feign 中默认使用 JDK 原生的 URLConnection 发送 HTTP 请求,我们可以集成别的组件来替换掉 URLConnection,比如 Apache HttpClient,OkHttp。
  • GZIP 压缩配置:再配置文件中开启压缩可以有效节约网络资源,提升接口性能
  • 编码器解码器配置:Feign 中提供了自定义的编码解码器设置,同时也提供了多种编码器的实现,比如 Gson、Jaxb、Jackson。 我们可以用不同的编码解码器来处理数据的传输。如果你想传输 XML 格式的数据,可以自定义 XML 编码解 码器来实现获取使用官方提供的 Jaxb

5. OpenFeign是如何整合Ribbon的?

通过上边,我们已经知道Ribbon可以把微服务的 服务名 通过负载均衡策略替换成某一台机器的IP地址,然后通过http请求进行访问!如下所示:

http://mall-order/order/findOrderByUserId ====> http://192.168.100.15/order/findOrderByUserId

Feign则是把参数组装到url中去,实现一个完整的RPC调用

http://mall-order/order/findOrderByUserId/5(参数) ====> http://192.168.100.15/order/findOrderByUserId/5(参数)

①:扫描所有@FeignClient注解,以FactoryBean的形式注册到容器中

Feign的使用需要用到@EnableFeignClients@FeignClient("gulimall-ware")这两个注解,其中,@EnableFeignClients通过@Import向容器中添加了一个bean定义注册器,用于扫描@FeignClient("gulimall-ware")注解,注册bean定义

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Documented
//通过`@Import`向容器中添加了一个bean定义注册器 FeignClientsRegistrar
@Import(FeignClientsRegistrar.class)
public @interface EnableFeignClients {}

进入FeignClientsRegistrarregisterBeanDefinitions方法,查看具体注册了什么

 @Overridepublic void registerBeanDefinitions(AnnotationMetadata metadata,BeanDefinitionRegistry registry) {//注册一下默认配置registerDefaultConfiguration(metadata, registry);//注册所有@FeignClient注解 标注的类registerFeignClients(metadata, registry);}

注册逻辑中,最主要的就是把所有@FeignClient注解 标注的类以FactoryBean的形式注册到容器中

 public void registerFeignClients(AnnotationMetadata metadata,BeanDefinitionRegistry registry) {// 1. 获取一个扫描器ClassPathScanningCandidateComponentProvider scanner = getScanner();scanner.setResourceLoader(this.resourceLoader);Set<String> basePackages;//2. 拿到所有@FeignClient注解标注的类Map<String, Object> attrs = metadata.getAnnotationAttributes(EnableFeignClients.class.getName());AnnotationTypeFilter annotationTypeFilter = new AnnotationTypeFilter(FeignClient.class);final Class<?>[] clients = attrs == null ? null: (Class<?>[]) attrs.get("clients");if (clients == null || clients.length == 0) {scanner.addIncludeFilter(annotationTypeFilter);basePackages = getBasePackages(metadata);}else {final Set<String> clientClasses = new HashSet<>();basePackages = new HashSet<>();for (Class<?> clazz : clients) {basePackages.add(ClassUtils.getPackageName(clazz));clientClasses.add(clazz.getCanonicalName());}AbstractClassTestingTypeFilter filter = new AbstractClassTestingTypeFilter() {@Overrideprotected boolean match(ClassMetadata metadata) {String cleaned = metadata.getClassName().replaceAll("\\$", ".");return clientClasses.contains(cleaned);}};scanner.addIncludeFilter(new AllTypeFilter(Arrays.asList(filter, annotationTypeFilter)));}for (String basePackage : basePackages) {Set<BeanDefinition> candidateComponents = scanner.findCandidateComponents(basePackage);for (BeanDefinition candidateComponent : candidateComponents) {if (candidateComponent instanceof AnnotatedBeanDefinition) {// verify annotated class is an interfaceAnnotatedBeanDefinition beanDefinition = (AnnotatedBeanDefinition) candidateComponent;AnnotationMetadata annotationMetadata = beanDefinition.getMetadata();Assert.isTrue(annotationMetadata.isInterface(),"@FeignClient can only be specified on an interface");Map<String, Object> attributes = annotationMetadata.getAnnotationAttributes(FeignClient.class.getCanonicalName());String name = getClientName(attributes);registerClientConfiguration(registry, name,attributes.get("configuration"));// 3. 把这些类注入容器registerFeignClient(registry, annotationMetadata, attributes);}}}}

修改bean定义为FactoryBean的子类FeignClientFactoryBean

 private void registerFeignClient(BeanDefinitionRegistry registry,AnnotationMetadata annotationMetadata, Map<String, Object> attributes) {String className = annotationMetadata.getClassName();//把bean定义构建成一个FactoryBeanBeanDefinitionBuilder definition = BeanDefinitionBuilder.genericBeanDefinition(FeignClientFactoryBean.class);validate(attributes);definition.addPropertyValue("url", getUrl(attributes));definition.addPropertyValue("path", getPath(attributes));String name = getName(attributes);definition.addPropertyValue("name", name);String contextId = getContextId(attributes);definition.addPropertyValue("contextId", contextId);definition.addPropertyValue("type", className);definition.addPropertyValue("decode404", attributes.get("decode404"));definition.addPropertyValue("fallback", attributes.get("fallback"));definition.addPropertyValue("fallbackFactory", attributes.get("fallbackFactory"));definition.setAutowireMode(AbstractBeanDefinition.AUTOWIRE_BY_TYPE);String alias = contextId + "FeignClient";AbstractBeanDefinition beanDefinition = definition.getBeanDefinition();boolean primary = (Boolean) attributes.get("primary"); // has a default, won't be// nullbeanDefinition.setPrimary(primary);String qualifier = getQualifier(attributes);if (StringUtils.hasText(qualifier)) {alias = qualifier;}BeanDefinitionHolder holder = new BeanDefinitionHolder(beanDefinition, className,new String[] { alias });// 注入!BeanDefinitionReaderUtils.registerBeanDefinition(holder, registry);}

②:RPC调用时,通过LoadBalancerFeignClient整合Ribbon,实现负载均衡调用

既然是把@FeignClient("gulimall-ware")注解标注的类 以FactoryBean的子类FeignClientFactoryBean的形式注入到容器,那么RPC调用时肯定是通过调用FeignClientFactoryBeangetObject方法来使用的!

 @Overridepublic Object getObject() throws Exception {return getTarget();}

getTarget()

 <T> T getTarget() {FeignContext context = this.applicationContext.getBean(FeignContext.class);Feign.Builder builder = feign(context);if (!StringUtils.hasText(this.url)) {if (!this.name.startsWith("http")) {this.url = "http://" + this.name;}else {this.url = this.name;}this.url += cleanPath();return (T) loadBalance(builder, context,new HardCodedTarget<>(this.type, this.name, this.url));}

loadBalance()

 protected <T> T loadBalance(Feign.Builder builder, FeignContext context,HardCodedTarget<T> target) {//获取的其实是LoadBalanceFeignClient,用于整合Ribbon的负载均衡Client client = getOptional(context, Client.class);if (client != null) {builder.client(client);Targeter targeter = get(context, Targeter.class);return targeter.target(this, builder, context, target);}throw new IllegalStateException("No Feign Client for loadBalancing defined. Did you forget to include spring-cloud-starter-netflix-ribbon?");}

Client client = getOptional(context, Client.class);获取的是Feign的客户端实现:

整合Ribbon逻辑:进入LoadBalancerFeignClientexecute方法中,使用Feign的负载均衡器向Ribbon发请求,已达到整合Ribbon的目的!

org.springframework.cloud.openfeign.ribbon.LoadBalancerFeignClient # execute

 @Overridepublic Response execute(Request request, Request.Options options) throws IOException {try {URI asUri = URI.create(request.url());String clientName = asUri.getHost();URI uriWithoutHost = cleanUrl(request.url(), clientName);//使用Feign的负载均衡器向Ribbon发请求,已达到整合Ribbon的目的!FeignLoadBalancer.RibbonRequest ribbonRequest = new FeignLoadBalancer.RibbonRequest(this.delegate, request, uriWithoutHost);IClientConfig requestConfig = getClientConfig(options, clientName);return lbClient(clientName).executeWithLoadBalancer(ribbonRequest, requestConfig).toResponse();}catch (ClientException e) {IOException io = findIOException(e);if (io != null) {throw io;}throw new RuntimeException(e);}}

综上所述,负载均衡的逻辑还是在Ribbon中,而Feign通过整合Ribbon实现了带有负载均衡的RPC调用!

6. 总结图

Ribbon负载均衡原理,Feign是如何整合Ribbon的?相关推荐

  1. Ribbon负载均衡及Feign消费者调用服务

    Ribbon负载均衡及Feign消费者调用服务 微服务调用Ribbon 简介 前面讲了eureka服务注册与发现,但是结合eureka集群的服务调用没讲. 这里的话 就要用到Ribbon,结合eure ...

  2. 微服务(三) 【手摸手带你搭建Spring Cloud】 Ribbon 什么是负载均衡?spring cloud如何实现负载均衡?ribbon负载均衡有几种策略?Ribbon是什么?

    在上一章,我介绍了springcloud的eureka搭建.我们做了服务注册.最后我们还介绍了一些续约,失效剔除等参数配置.已经不需要再通过手动输入ip去访问服务,而是通过中心只需要通过服务名就可以获 ...

  3. Ribbon负载均衡原理

    Ribbon + restTemplate相结合实现负载均衡,具体原理图详见以下截图: LoadBalancerClient 类执行具体的负载均衡,其继承于 LoadBalancerBase. Loa ...

  4. Ribbon负载均衡原理,源码解读

    Ribbon负责均衡原理图 源码详解: @LoadBalanced 标记RestTemplate发起的请求,会被loadBalanced拦截和处理 /*** 创建RestTemplate并注入Spri ...

  5. SpringCloud[04]Ribbon负载均衡服务调用

    文章目录 Ribbon负载均衡服务调用 1. 概述 1. Ribbon是什么 2. Ribbon能做什么 2. Ribbon负载均衡演示 1. 架构说明 2. Ribbon的POM依赖 3. Rest ...

  6. Ribbon负载均衡服务调用

    一:关于Ribbon Spring Cloud Ribbon是基于Netflix Ribbon实现的一套客户端       负载均衡的工具. 简单的说,Ribbon是Netflix发布的开源项目,主要 ...

  7. Ribbon负载均衡 饥饿加载

    需要两份或多份相同的性质的服务的模块,地址与端口不同,服务模块名称相同,访问者通过名称进行访问 让访问者进行负载均衡的选择 在Eureka中发送这个路径我使用的是名字,而不是ip,这里面的负载均衡就是 ...

  8. SpringCloud组件:Ribbon负载均衡策略及执行原理!

    大家好,我是磊哥. 今天我们来看下微服务中非常重要的一个组件:Ribbon.它作为负载均衡器在分布式网络中扮演着非常重要的角色. 本篇主要内容如下: 在介绍 Ribbon 之前,不得不说下负载均衡这个 ...

  9. 【详解】Ribbon 负载均衡服务调用原理及默认轮询负载均衡算法源码解析、手写

    Ribbon 负载均衡服务调用 一.什么是 Ribbon 二.LB负载均衡(Load Balancer)是什么 1.Ribbon 本地负载均衡客户端 VS Nginx 服务端负载均衡的区别 2.LB负 ...

最新文章

  1. 《深入Python》-11. HTTP Web 服务
  2. mysql自动编号_MySQL自动编号与主键
  3. 边缘检测的简单例子(MATLAB)
  4. python的sys.path
  5. 快速排序的性能和名字一样优秀
  6. HashMap vs ConcurrentHashMap — 示例及Iterator探秘
  7. python 编程快速上手,Python编程快速上手
  8. 加分进了字节,MySQL真yyds!
  9. ESP32 + ESP-IDF |GPIO 03 - 定时器轮询按钮的状态,控制LED亮或者灭
  10. Leetcode每日一题:206.reverse-linked-list/solution(反转链表)
  11. “C# 未在本地计算机上注册microsoft.Jet.OLEDB.12.0”的解决方案
  12. OpenCV之鼠标操作
  13. 修改VNR源码接入新版有道中文翻译API
  14. 【Format】ASF/WMV 文件格式解析
  15. 英式和美式的单词拼写差异详细对照表
  16. win7右键显示隐藏文件及扩展名
  17. 计算机歌曲压缩比公式,音频动态压缩第三层(MPEGAudioLayer-3)
  18. 浅谈Python爬虫(四)【英雄联盟人物背景故事爬取】
  19. 【Error】初始化ant design pro项目时遇到“pro 不是内部或外部命令”
  20. 深入浅出讲解 Python 元类(Metaclass)的使用

热门文章

  1. CSS之布局方式(内/外部显示及inline-block显示类型)附<行内块空白间隙解决方案>
  2. c++——抽象类以及string知识点补充
  3. 调节pycharm字体大_字体美化大师里的字体推荐
  4. HashMap由浅入深(jdk8)
  5. java.lang.ClassNotFoundException: org.apache.jsp.WEB_002dINF.classes.views.index_jsp
  6. mysql 数据库之表操作
  7. ubuntu12.04编译rtems doc目录
  8. [译] 使用 python 分析 14 亿条数据
  9. XML文件的读取(XmlParserDemo)
  10. jQuery 源码系列(四)Tokens 词法分析