定制设计Ribbon负载均衡原理,Feign是如何整合Ribbon的?

文章目录

1. 什么是?

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

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

定制设计常见的负载均衡算法:

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

       

2. 的使用

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

<!--添加ribbon的依赖--><dependency>    <groupId>org.springframework.cloud</groupId>    <artifactId>spring-cloud-starter-netflix-ribbon</artifactId></dependency>
  • 1
  • 2
  • 3
  • 4
  • 5

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

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

①:为RestTemplate添加@LoadBalanced注解

@Configurationpublic class RestConfig {    @Bean    @LoadBalanced    public RestTemplate restTemplate() {        return new RestTemplate();    } 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

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

@Autowiredprivate 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;}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11

       

①:定制设计自定义负载均衡策略

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

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

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

//继承 AbstractLoadBalancerRule 类@Slf4jpublic class NacosRandomWithWeightRule extends AbstractLoadBalancerRule {    @Autowired    private NacosDiscoveryProperties nacosDiscoveryProperties;    @Override    public 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;    }    @Override    public void initWithNiwsConfig(IClientConfig clientConfig) {    }
  • 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

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

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

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

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

       

②:Ribbon的饥饿加载

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

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

ribbon:  eager-load:    # 开启ribbon饥饿加载,源码对应属性配置类:RibbonEagerLoadProperties    enabled: true    # 配置mall-user使用ribbon饥饿加载,多个使用逗号分隔    clients: mall-order
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

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

       

3. Ribbon的负载均衡原理

       

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

       上面的使用案例中,如果不加@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 {
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11

      而 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 发起调用时,会先走此拦截器,实现负载均衡	@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);				}			}		});	}	@Bean	@ConditionalOnMissingBean	public LoadBalancerRequestFactory loadBalancerRequestFactory(			LoadBalancerClient loadBalancerClient) {		return new LoadBalancerRequestFactory(loadBalancerClient, this.transformers);	}	@Configuration	@ConditionalOnMissingClass("org.springframework.retry.support.RetryTemplate")	static class LoadBalancerInterceptorConfig {		//向容器中放入一个带有负载均衡功能的拦截器		@Bean		public LoadBalancerInterceptor ribbonInterceptor(				LoadBalancerClient loadBalancerClient,				LoadBalancerRequestFactory requestFactory) {			return new LoadBalancerInterceptor(loadBalancerClient, requestFactory);		}		//这是一个函数式接口,此处实现的是接口的customize方法,定义了一个操作,操作内容如下:		// 传入一个restTemplate,并把上面的拦截器 放入restTemplate中		//当有请求经过ribbon,通过 restTemplate 发起调用时		//会先走此拦截器,实现负载均衡		@Bean		@ConditionalOnMissingBean		public RestTemplateCustomizer restTemplateCustomizer(				final LoadBalancerInterceptor loadBalancerInterceptor) {			return restTemplate -> {				List<ClientHttpRequestInterceptor> list = new ArrayList<>(						restTemplate.getInterceptors());				list.add(loadBalancerInterceptor);				restTemplate.setInterceptors(list);			};		}	}
  • 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

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

@Target({ ElementType.FIELD, ElementType.PARAMETER, ElementType.METHOD })@Retention(RetentionPolicy.RUNTIME)@Documented@Inherited@Qualifier  //spring的限定符public @interface LoadBalanced {}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9

@Qualifier限定符的作用:

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

       

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

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

	@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);		//负载均衡器的 execute 方法		return this.loadBalancer.execute(serviceName,				this.requestFactory.createRequest(request, body, execution));	}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
	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);	}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15

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

       

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

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

	@Bean	@ConditionalOnMissingBean	public ILoadBalancer ribbonLoadBalancer(IClientConfig config,			ServerList<Server> serverList, ServerListFilter<Server> serverListFilter,			IRule rule, IPing ping, ServerListUpdater serverListUpdater) {		// 从配置类中找一个负载均衡器 ILoadBalancer		if (this.propertiesFactory.isSet(ILoadBalancer.class, name)) {			return this.propertiesFactory.get(ILoadBalancer.class, config, name);		}		// 如果没有就创建一个负载均衡器的实现 ZoneAwareLoadBalancer		return new ZoneAwareLoadBalancer<>(config, rule, ping, serverList,				serverListFilter, serverListUpdater);	}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13

可以看到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());    }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18

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

    @Override    public synchronized void start(final UpdateAction updateAction) {        if (isActive.compareAndSet(false, true)) {        	//开启一个线程,执行更新任务            final Runnable wrapperRunnable = new Runnable() {                @Override                public 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() {        @Override        public void doUpdate() {            updateListOfServers();        }    };	// updateListOfServers方法如下:    @VisibleForTesting    public 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);    }
  • 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

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

    @VisibleForTesting    public 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);    }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18

       

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);	}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15

进入负载均衡算法选择服务器的方法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;            }        }    }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18

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;        }    }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10

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

	@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;	}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11

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和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 {}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

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

	@Override	public void registerBeanDefinitions(AnnotationMetadata metadata,			BeanDefinitionRegistry registry) {		//注册一下默认配置		registerDefaultConfiguration(metadata, registry);		//注册所有@FeignClient注解 标注的类		registerFeignClients(metadata, registry);	}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

注册逻辑中,最主要的就是把所有@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() {				@Override				protected 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 interface					AnnotatedBeanDefinition 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);				}			}		}	}
  • 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

修改bean定义为FactoryBean的子类FeignClientFactoryBean

	private void registerFeignClient(BeanDefinitionRegistry registry,			AnnotationMetadata annotationMetadata, Map<String, Object> attributes) {		String className = annotationMetadata.getClassName();		//把bean定义构建成一个FactoryBean		BeanDefinitionBuilder 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																// null		beanDefinition.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);	}
  • 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

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

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

	@Override	public Object getObject() throws Exception {		return getTarget();	}
  • 1
  • 2
  • 3
  • 4

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));		}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15

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?");	}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13

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

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

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

	@Override	public 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);		}	}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23

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

       

6. 总结图

网站建设定制开发 软件系统开发定制 定制软件开发 软件开发定制 定制app开发 app开发定制 app开发定制公司 电商商城定制开发 定制小程序开发 定制开发小程序 客户管理系统开发定制 定制网站 定制开发 crm开发定制 开发公司 小程序开发定制 定制软件 收款定制开发 企业网站定制开发 定制化开发 android系统定制开发 定制小程序开发费用 定制设计 专注app软件定制开发 软件开发定制定制 知名网站建设定制 软件定制开发供应商 应用系统定制开发 软件系统定制开发 企业管理系统定制开发 系统定制开发