Ribbon 源码分析【Ribbon 负载均衡】

前言

在 Spring Cloud 2020 版本以后,移除了对 Netflix 的依赖,也就移除了负载均衡器 Ribbon,Spring Cloud 官方推荐使用 Loadbalancer 替换 Ribbon,而在 LoadBalancer 之前 Spring Cloud 一直使用的是 Ribbon 来做负载[均衡器的,而且 Ribbon 的负载均衡策略也比 Loadbalancer 更为丰富,本篇分享一些关于 Ribbon 相关的源码。

Ribbon 的自动配置

老规矩我们在分析 Ribbon 的源码之前还是去看一下 spring-cloud-netflix-ribbon 的 META-INF 包下的 spring.factories 文件中的内容,内容如下:

java 复制代码
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
org.springframework.cloud.netflix.ribbon.RibbonAutoConfiguration

Ribbon 的 META-INF 包下的 spring.factories 文件中的内容非常简单,只有一个 RibbonAutoConfiguration 类,我们来看下这个类都有什么逻辑。

RibbonAutoConfiguration 类源码分析

RibbonAutoConfiguration 是 Ribbon 的全局配置,主要是加载 Ribbon 客户端工厂、负载均衡客户端、配置类工厂、重试机制工厂等,RibbonAutoConfiguration 类在启动的时候就会被加载,。

java 复制代码
//标记为配置类
@Configuration
//注入的条件
@Conditional({RibbonAutoConfiguration.RibbonClassesConditions.class})
//为所有 RibbonClients 提供默认客户端配置
@RibbonClients
//加载配置类后加载 EurekaClientAutoConfiguration
@AutoConfigureAfter(
    name = {"org.springframework.cloud.netflix.eureka.EurekaClientAutoConfiguration"}
)
//加载配置类之前加载 LoadBalancerAutoConfiguration
@AutoConfigureBefore({LoadBalancerAutoConfiguration.class, AsyncLoadBalancerAutoConfiguration.class})
//使用 RibbonEagerLoadProperties ServerIntrospectorProperties
@EnableConfigurationProperties({RibbonEagerLoadProperties.class, ServerIntrospectorProperties.class})
public class RibbonAutoConfiguration {
    
	//注入 configurations
	@Autowired(
        required = false
    )
    private List<RibbonClientSpecification> configurations = new ArrayList();
	
	//Ribbon 的饥饿加载模式配置类 Ribbon 在进行客户端负载均衡的时候并不是在服务启动时候就初始化好的 而是在调用的时候才会去创建相应的 client 开启饥饿模式可以提前加载 client
    @Autowired
    private RibbonEagerLoadProperties ribbonEagerLoadProperties;

    public RibbonAutoConfiguration() {
    }

    @Bean
    public HasFeatures ribbonFeature() {
        return HasFeatures.namedFeature("Ribbon", Ribbon.class);
    }
	
	//创建 Ribbon 客户端负载均衡器的工厂
    @Bean
    public SpringClientFactory springClientFactory() {
        SpringClientFactory factory = new SpringClientFactory();
        factory.setConfigurations(this.configurations);
        return factory;
    }
	
	//Ribbon 负载均衡器客户端 可以查询所有服务实例
    @Bean
    @ConditionalOnMissingBean({LoadBalancerClient.class})
    public LoadBalancerClient loadBalancerClient() {
        return new RibbonLoadBalancerClient(this.springClientFactory());
    }

	//Ribbon 重试工厂
    @Bean
    @ConditionalOnClass(
        name = {"org.springframework.retry.support.RetryTemplate"}
    )
    @ConditionalOnMissingBean
    public LoadBalancedRetryFactory loadBalancedRetryPolicyFactory(final SpringClientFactory clientFactory) {
        return new RibbonLoadBalancedRetryFactory(clientFactory);
    }

	//获取配置的
    @Bean
    @ConditionalOnMissingBean
    public PropertiesFactory propertiesFactory() {
        return new PropertiesFactory();
    }

	//如果配置了饥饿加载 就初始化 RibbonApplicationContextInitializer
    @Bean
    @ConditionalOnProperty({"ribbon.eager-load.enabled"})
    public RibbonApplicationContextInitializer ribbonApplicationContextInitializer() {
        return new RibbonApplicationContextInitializer(this.springClientFactory(), this.ribbonEagerLoadProperties.getClients());
    }

    static class RibbonClassesConditions extends AllNestedConditions {
        RibbonClassesConditions() {
            super(ConfigurationPhase.PARSE_CONFIGURATION);
        }

        @ConditionalOnClass({Ribbon.class})
        static class RibbonPresent {
            RibbonPresent() {
            }
        }

        @ConditionalOnClass({AsyncRestTemplate.class})
        static class AsyncRestTemplatePresent {
            AsyncRestTemplatePresent() {
            }
        }

        @ConditionalOnClass({RestTemplate.class})
        static class RestTemplatePresent {
            RestTemplatePresent() {
            }
        }

        @ConditionalOnClass({IClient.class})
        static class IClientPresent {
            IClientPresent() {
            }
        }
    }

    private static class OnRibbonRestClientCondition extends AnyNestedCondition {
        OnRibbonRestClientCondition() {
            super(ConfigurationPhase.REGISTER_BEAN);
        }

        @ConditionalOnProperty({"ribbon.restclient.enabled"})
        static class RibbonProperty {
            RibbonProperty() {
            }
        }

        /** @deprecated */
        @Deprecated
        @ConditionalOnProperty({"ribbon.http.client.enabled"})
        static class ZuulProperty {
            ZuulProperty() {
            }
        }
    }

    @Target({ElementType.TYPE, ElementType.METHOD})
    @Retention(RetentionPolicy.RUNTIME)
    @Documented
    @Conditional({RibbonAutoConfiguration.OnRibbonRestClientCondition.class})
    @interface ConditionalOnRibbonRestClient {
    }

    @Configuration(
        proxyBeanMethods = false
    )
    @ConditionalOnClass({HttpRequest.class})
    @RibbonAutoConfiguration.ConditionalOnRibbonRestClient
    protected static class RibbonClientHttpRequestFactoryConfiguration {
        @Autowired
        private SpringClientFactory springClientFactory;

        protected RibbonClientHttpRequestFactoryConfiguration() {
        }

        @Bean
        public RestTemplateCustomizer restTemplateCustomizer(final RibbonClientHttpRequestFactory ribbonClientHttpRequestFactory) {
            return (restTemplate) -> {
                restTemplate.setRequestFactory(ribbonClientHttpRequestFactory);
            };
        }

        @Bean
        public RibbonClientHttpRequestFactory ribbonClientHttpRequestFactory() {
            return new RibbonClientHttpRequestFactory(this.springClientFactory);
        }
    }
}

RibbonClientConfiguration 类源码分析

RibbonClientConfiguration 类在加载机制是在第一次执行 Feign 请求时候才会被加载,RibbonClientConfiguration 加载时候会注入 Ribbon 客户端配置类、负载均衡算法、服务列表、负载均衡器、服务列表过滤器、负载均衡器上下文、重试处理器、服务拦截器、预处理器等。

java 复制代码
@Configuration(
    proxyBeanMethods = false
)
@EnableConfigurationProperties
@Import({HttpClientConfiguration.class, OkHttpRibbonConfiguration.class, RestClientRibbonConfiguration.class, HttpClientRibbonConfiguration.class})
public class RibbonClientConfiguration {
	//默认连接超时时间  1 秒
    public static final int DEFAULT_CONNECT_TIMEOUT = 1000;
	//默认读取超时时间  1 秒
    public static final int DEFAULT_READ_TIMEOUT = 1000;
	//默认的 GZIP 负载
    public static final boolean DEFAULT_GZIP_PAYLOAD = true;
	//Ribbon 客户端名称
    @RibbonClientName
    private String name = "client";
	//Ribbon 配置工厂
    @Autowired
    private PropertiesFactory propertiesFactory;

    public RibbonClientConfiguration() {
    }
	
	//Ribbon 客户端配置
    @Bean
    @ConditionalOnMissingBean
    public IClientConfig ribbonClientConfig() {
		//默认的配置类
        DefaultClientConfigImpl config = new DefaultClientConfigImpl();
        //加载配置
		config.loadProperties(this.name);
		//设置超时时间
        config.set(CommonClientConfigKey.ConnectTimeout, 1000);
		//设置读取超时时间
        config.set(CommonClientConfigKey.ReadTimeout, 1000);
		//Gzip 负载
        config.set(CommonClientConfigKey.GZipPayload, true);
        return config;
    }

	//负载均衡算法
    @Bean
    @ConditionalOnMissingBean
    public IRule ribbonRule(IClientConfig config) {
        if (this.propertiesFactory.isSet(IRule.class, this.name)) {
            return (IRule)this.propertiesFactory.get(IRule.class, config, this.name);
        } else {
			//默认负载均衡算法(先过滤再轮训的负载均衡算法)
            ZoneAvoidanceRule rule = new ZoneAvoidanceRule();
            rule.initWithNiwsConfig(config);
            return rule;
        }
    }

	//检测服务健康状态
    @Bean
    @ConditionalOnMissingBean
    public IPing ribbonPing(IClientConfig config) {
        return (IPing)(this.propertiesFactory.isSet(IPing.class, this.name) ? (IPing)this.propertiesFactory.get(IPing.class, config, this.name) : new DummyPing());
    }

	//服务列表
    @Bean
    @ConditionalOnMissingBean
    public ServerList<Server> ribbonServerList(IClientConfig config) {
        if (this.propertiesFactory.isSet(ServerList.class, this.name)) {
            return (ServerList)this.propertiesFactory.get(ServerList.class, config, this.name);
        } else {
            ConfigurationBasedServerList serverList = new ConfigurationBasedServerList();
            serverList.initWithNiwsConfig(config);
            return serverList;
        }
    }
	
	//服务更新列表
    @Bean
    @ConditionalOnMissingBean
    public ServerListUpdater ribbonServerListUpdater(IClientConfig config) {
        return new PollingServerListUpdater(config);
    }
	
	//负载均衡器 
    @Bean
    @ConditionalOnMissingBean
    public ILoadBalancer ribbonLoadBalancer(IClientConfig config, ServerList<Server> serverList, ServerListFilter<Server> serverListFilter, IRule rule, IPing ping, ServerListUpdater serverListUpdater) {
        return (ILoadBalancer)(this.propertiesFactory.isSet(ILoadBalancer.class, this.name) ? (ILoadBalancer)this.propertiesFactory.get(ILoadBalancer.class, config, this.name) : new ZoneAwareLoadBalancer(config, rule, ping, serverList, serverListFilter, serverListUpdater));
    }
	
	//Ribbon 服务列表过滤器
    @Bean
    @ConditionalOnMissingBean
    public ServerListFilter<Server> ribbonServerListFilter(IClientConfig config) {
        if (this.propertiesFactory.isSet(ServerListFilter.class, this.name)) {
            return (ServerListFilter)this.propertiesFactory.get(ServerListFilter.class, config, this.name);
        } else {
            ZonePreferenceServerListFilter filter = new ZonePreferenceServerListFilter();
            filter.initWithNiwsConfig(config);
            return filter;
        }
    }
	
	//Ribbon 负载均衡器上下文
    @Bean
    @ConditionalOnMissingBean
    public RibbonLoadBalancerContext ribbonLoadBalancerContext(ILoadBalancer loadBalancer, IClientConfig config, RetryHandler retryHandler) {
        return new RibbonLoadBalancerContext(loadBalancer, config, retryHandler);
    }
	
	//重试处理器
    @Bean
    @ConditionalOnMissingBean
    public RetryHandler retryHandler(IClientConfig config) {
        return new DefaultLoadBalancerRetryHandler(config);
    }
	
	//服务拦截器
    @Bean
    @ConditionalOnMissingBean
    public ServerIntrospector serverIntrospector() {
        return new DefaultServerIntrospector();
    }
	
	//预处理器
    @PostConstruct
    public void preprocess() {
        RibbonUtils.setRibbonProperty(this.name, CommonClientConfigKey.DeploymentContextBasedVipAddresses.key(), this.name);
    }

    static class OverrideRestClient extends RestClient {
        private IClientConfig config;
        private ServerIntrospector serverIntrospector;

        protected OverrideRestClient(IClientConfig config, ServerIntrospector serverIntrospector) {
            this.config = config;
            this.serverIntrospector = serverIntrospector;
            this.initWithNiwsConfig(this.config);
        }

        public URI reconstructURIWithServer(Server server, URI original) {
            URI uri = RibbonUtils.updateToSecureConnectionIfNeeded(original, this.config, this.serverIntrospector, server);
            return super.reconstructURIWithServer(server, uri);
        }

        protected Client apacheHttpClientSpecificInitialization() {
            ApacheHttpClient4 apache = (ApacheHttpClient4)super.apacheHttpClientSpecificInitialization();
            apache.getClientHandler().getHttpClient().getParams().setParameter("http.protocol.cookie-policy", "ignoreCookies");
            return apache;
        }
    }
}

Ribbion 负载均衡算法分析

Ribbon 作为一个负载均衡器,其具备丰富的负载均衡策略,这也是其相比 Spring Cloud LoadBalancer 的优势,LoadBalancer 只提供了随机和轮训两种负载均衡算法,下面我们来分析一下 Ribbon 复杂均衡算法,IRule 是 Ribbon 负载均衡算法的接口。

IRule 接口源码分析

IRule 是 Ribbon 负载均衡算法的接口,接口定义了三个方法,如下:

  • choose(Object var1):根据指定的算法中从服务列表中选取一个要访问的可用服务。
  • setLoadBalancer(ILoadBalancer var1):设置负载均衡器。
  • getLoadBalancer():获取负载均衡器。
java 复制代码
package com.netflix.loadbalancer;

public interface IRule {
	
	//根据指定的算法中从服务列表中选取一个要访问的可用服务
    Server choose(Object var1);
	
	//设置负载均衡器
    void setLoadBalancer(ILoadBalancer var1);

	//获取负载均衡器
    ILoadBalancer getLoadBalancer();
}

IRule 的实现类如下:

  • RandomRule:随机算法,统计服务个数,使用服务个数,生成一个随机数下标,通过生成的随机数,返回一个可用的服务。
  • RoundRobinRule:轮训算法,依次执行。
  • WeightedResponseTimeRule:响应时间权重轮询算法,继承自RoundRobinRule,是对轮询算法进行的扩展,加入了权重计算。
  • RetryRule:重试算法,实际调用的也是轮询算法,只是在没有获取到服务时,会进行循环重试操作,超过设置的时间限制,则会退出,默认为 500ms。
  • BestAvailableRule:最小并发算法,会遍历所有的服务提供者,选择并发量最小的那个服务。
  • AvailabilityFilteringRule:可用性断言过滤器算法。
  • ZoneAvoidanceRule :先过滤后轮训的算法,也是默认的负载均衡算法。

RandomRule 随机算法源码分析

RandomRule#choose 是 Ribbom 负载均衡随机算法的实现,主要逻辑如下:

  • ILoadBalancer 为空判断,如果 ILoadBalancer 为空,则直接返回服务为 null。
  • ILoadBalancer 不为空,且 Server 也不为空,则直接返回 Server 。
  • ILoadBalancer 不为空,且 Server 为空,获取所有服务列表,使用服务列表数量获取一个随机数,使用这个随机数在可用服务列表中获取服务。
  • 如果上一步获取的服务为空,则当前线程让出 CPU 资源,再次重新循环获取,出现这种情况可能是服务出现瞬时状态,如果获取的服务不为空,则判断服务是否还存活着,服务存活,直接返回服务,负责则会让出 CPU 资源,再次重新循环获取服务。
java 复制代码
//com.netflix.loadbalancer.RandomRule#choose(com.netflix.loadbalancer.ILoadBalancer, java.lang.Object)
public Server choose(ILoadBalancer lb, Object key) {
	//负载均衡器为空判断
	if (lb == null) {
		//负载均衡器为空 则直接返回空
		return null;
	} else {
		//服务
		Server server = null;
		//开启while 循环
		while(server == null) {
			//判断当前线程是否被中断
			if (Thread.interrupted()) {
				//如果当前线程被中断  直接返回 null
				return null;
			}
			//获取所有可以访问的服务实例列表
			List<Server> upList = lb.getReachableServers();
			//获取所有的服务实例列表
			List<Server> allList = lb.getAllServers();
			int serverCount = allList.size();
			//判断所有服务列表的个数是否为0
			if (serverCount == 0) {
				//所有服务列表的个数为 0 返回 null
				return null;
			}
			//使用所有服务列表来获取一个随机数
			int index = this.chooseRandomInt(serverCount);
			//在可用服务列表中 获取随机数下标的服务
			server = (Server)upList.get(index);
			//判断服务服务是否为 nul
			if (server == null) {
				//如果服务为空 则当前线程让出 CPU 资源 再次重新循环获取 出现这种情况可能是服务出现瞬时状态
				Thread.yield();
			} else {
				//判断服务是否还存活着
				if (server.isAlive()) {
					//服务存活 直接返回服务
					return server;
				}
				//有服务 服务又没有存活 则会让出 CPU 资源 再继续循环获取
				server = null;
				Thread.yield();
			}
		}
		//server 不为空 直接返回
		return server;
	}
}

RoundRobinRule 轮训算法源码分析

RoundRobinRule#choose 方法是 Ribbon 负载均衡轮训算法的实现,具体逻辑如下:

  • ILoadBalancer 为空判断,如果 ILoadBalancer 为空,则直接返回服务为 null。
  • ILoadBalancer 不为空,执行 while 循环获取 Server。
  • 判断 Server 是否为空且 count++ 是否小于10,满足条件,则判断所有服务数量和所有可用服务数量是否都不等于0,如果都不等于0,则使用轮训算法获取服务,对服务为空及可用性判断后返回,否则直接返回 null。
  • 轮训算法的核心就是 next = (current + 1) % modulo;,也就是当前服务的请求总数+1,然后和服务总数取模,得到下标,也就是需要返回的服务对象。
java 复制代码
//com.netflix.loadbalancer.RoundRobinRule#choose(com.netflix.loadbalancer.ILoadBalancer, java.lang.Object)
public Server choose(ILoadBalancer lb, Object key) {
	//判断负载均衡器
	if (lb == null) {
		log.warn("no load balancer");
		//负载均衡器为空 返回 null
		return null;
	} else {
		//负载均衡器不为空
		Server server = null;
		//计数
		int count = 0;
		//while 循环
		while(true) {
			//服务是否为空 count++是否小于10
			if (server == null && count++ < 10) {
				//满足条件 获取所有的可用服务列表
				List<Server> reachableServers = lb.getReachableServers();
				//获取所有的服务列表
				List<Server> allServers = lb.getAllServers();
				//可用服务列表数量
				int upCount = reachableServers.size();
				//所有服务列表数量
				int serverCount = allServers.size();
				//可用服务列表和所有服务列表是否都不等于0
				if (upCount != 0 && serverCount != 0) {
					//获取服务索引 每次都会 ++
					int nextServerIndex = this.incrementAndGetModulo(serverCount);
					//从所用服务列表中根据索引获取服务
					server = (Server)allServers.get(nextServerIndex);
					//服务为空判断
					if (server == null) {
						//服务为空  然后 cpu 资源 等待下一次循环
						Thread.yield();
					} else {
						//服务不为空 进行服务存活判断 和服务是否准备就绪
						if (server.isAlive() && server.isReadyToServe()) {
							//满足条件 返回服务
							return server;
						}
						//否则给服务复制为  null
						server = null;
					}
					//跳出循环
					continue;
				}

				log.warn("No up servers available from load balancer: " + lb);
				return null;
			}
			//跳出while 循环的条件
			if (count >= 10) {
				log.warn("No available alive servers after 10 tries from load balancer: " + lb);
			}

			return server;
		}
	}
}


//com.netflix.loadbalancer.RoundRobinRule#incrementAndGetModulo
private int incrementAndGetModulo(int modulo) {
	//当前Server请求总数
	int current;
	//下一席
	int next;
	do {
		current = this.nextServerCyclicCounter.get();
		//modulo 服务总数
		next = (current + 1) % modulo;
	} while(!this.nextServerCyclicCounter.compareAndSet(current, next));

	return next;
}

WeightedResponseTimeRule 类源码分析

WeightedResponseTimeRule 是 Ribbon 负载均衡响应时间权重的实现,该类继承了 RoundRobinRule,对轮询算法进行了扩展,加入了权重计算,可以为每个服务分配动态权重,然后然后加权循环,权重高的则会优先执行,我们先来看看该来的构造方法。

java 复制代码
//com.netflix.loadbalancer.ResponseTimeWeightedRule#ResponseTimeWeightedRule()
public ResponseTimeWeightedRule() {
}

//com.netflix.loadbalancer.ResponseTimeWeightedRule#ResponseTimeWeightedRule()
public ResponseTimeWeightedRule(ILoadBalancer lb) {
	//调用了父类的构造方法 也就是 RoundRobinRule#RoundRobinRule
	super(lb);
}

WeightedResponseTimeRule 提供了一个无参构造方法和一个有参构造方法,负载均衡需要使用 ILoadBalancer,无参构造方法我们就不深究了,我们重点看一下有参构造方法,有参构造方法中调用了 super(lb),也就是调用了父类 RoundRobinRule 的构造方法,我们来看一下 RoundRobinRule 的构造方法。

java 复制代码
//com.netflix.loadbalancer.RoundRobinRule#RoundRobinRule(com.netflix.loadbalancer.ILoadBalancer)
public RoundRobinRule(ILoadBalancer lb) {
	this();
	//调用了  WeightedResponseTimeRule#setLoadBalancer 方法
	this.setLoadBalancer(lb);
}

RoundRobinRule#RoundRobinRule 方法中又调用了 this.setLoadBalancer(lb) 方法,也就是调用了 WeightedResponseTimeRule#setLoadBalancer 方法,我们接着往下看。

java 复制代码
//com.netflix.loadbalancer.WeightedResponseTimeRule#setLoadBalancer
public void setLoadBalancer(ILoadBalancer lb) {
	//还是先调用了父类的 setLoadBalancer 方法 com.netflix.loadbalancer.AbstractLoadBalancerRule#setLoadBalancer
	super.setLoadBalancer(lb);
	//判断 负载均衡器的类型 是否是 BaseLoadBalancer
	if (lb instanceof BaseLoadBalancer) {
		//是 获取负载均衡器的 name
		this.name = ((BaseLoadBalancer)lb).getName();
	}
	//调用 ResponseTimeWeightedRule#initialize 方法
	this.initialize(lb);
}

WeightedResponseTimeRule#setLoadBalancer 方法中先调用了 父类的 AbstractLoadBalancerRule#setLoadBalancer 方法(RoundRobinRule 类继承了 AbstractLoadBalancerRule),接着判断了 ILoadBalancer 的类型,最后调用了 ResponseTimeWeightedRule#initialize 方法,我们接着看。

ResponseTimeWeightedRule#initialize 方法源码分析

ResponseTimeWeightedRule#initialize 方法首先会判断服务权重计时器是否为空,不为空则清除服务权重计时器,然后创建服务权重计时器,并立刻开启定时任务执行权重计算(默认每30秒执行一次),开启任务后也会先执行一次权重计算,最后会在 JVM 关闭时候把权重计算清除。

java 复制代码
//com.netflix.loadbalancer.ResponseTimeWeightedRule#initialize
void initialize(ILoadBalancer lb) {
	//判断服务权重计时器是否为空
	if (this.serverWeightTimer != null) {
		//服务权重计数器不为空 则清除服务权重计时器
		this.serverWeightTimer.cancel();
	}
	//创建服务权重计时器
	this.serverWeightTimer = new Timer("NFLoadBalancer-serverWeightTimer-" + this.name, true);
	//执行定时任务 0秒后执行任务 定时执行  间隔时间默认30秒 private int serverWeightTaskTimerInterval = 30000;
	this.serverWeightTimer.schedule(new ResponseTimeWeightedRule.DynamicServerWeightTask(), 0L, (long)this.serverWeightTaskTimerInterval);
	//创建服务权重
	ResponseTimeWeightedRule.ServerWeight sw = new ResponseTimeWeightedRule.ServerWeight();
	//计算权重
	sw.maintainWeights();
	//jvm关闭时 通知定时任务 权重清空
	Runtime.getRuntime().addShutdownHook(new Thread(new Runnable() {
		public void run() {
			ResponseTimeWeightedRule.logger.info("Stopping NFLoadBalancer-serverWeightTimer-{}", ResponseTimeWeightedRule.this.name);
			ResponseTimeWeightedRule.this.serverWeightTimer.cancel();
		}
	}));
}

DynamicServerWeightTask 源码分析

DynamicServerWeightTask 是执行服务权重分配的任务类,该类的主要逻辑在于调用了 ServerWeight#maintainWeights 方法,我们重点关注该方法即可。

java 复制代码
class DynamicServerWeightTask extends TimerTask {
	DynamicServerWeightTask() {
	}
	
	//定时任务的 run 方法
	public void run() {
		//创建一个服务权重
		ResponseTimeWeightedRule.ServerWeight serverWeight = ResponseTimeWeightedRule.this.new ServerWeight();

		try {
			//调用权重计算方法  ServerWeight#maintainWeights
			serverWeight.maintainWeights();
		} catch (Exception var3) {
			ResponseTimeWeightedRule.logger.error("Error running DynamicServerWeightTask for {}", ResponseTimeWeightedRule.this.name, var3);
		}

	}
}

ServerWeight#maintainWeights 方法源码分析

ServerWeight#maintainWeights 方法主要是用来维持服务权重的,主要逻辑如下:

  • 获取负载均衡器,负载均衡器为空判断,如果负载均衡器为空,不予处理。
  • 使用 CAS 设置当前服务是否正在进行权重分配中,设置成功继续执行业务逻辑,否则不予处理。
  • 对负载均衡器统计数据为空判断,不为空继续执行业务逻辑,为空则不予处理。
  • 循环遍历所有服务,得到总响应时间,总响应时间等于每个服务的平均响应时间求和。
  • 循环服务列表,设置服务的权重,服务权重=总响应时间-服务的平均响应时间,这样服务的响应时间越长,其服务权重则越低。
java 复制代码
public void maintainWeights() {
	//获取负载均衡器
	ILoadBalancer lb = ResponseTimeWeightedRule.this.getLoadBalancer();
	//负载均衡器为空判断
	if (lb != null) {
	    //负载均衡器不为空
	    //使用 CAS 设置当前服务正在进行权重分配中
		if (ResponseTimeWeightedRule.this.serverWeightAssignmentInProgress.compareAndSet(false, true)) {
			try {
				ResponseTimeWeightedRule.logger.info("Weight adjusting job started");
				//强转之后获取负载均衡器的统计数据
				AbstractLoadBalancer nlb = (AbstractLoadBalancer)lb;
				LoadBalancerStats stats = nlb.getLoadBalancerStats();
				//负载均衡器的统计数据为空判断
				if (stats != null) {
				    //不为空 初始化响应时间
					double totalResponseTime = 0.0D;
                    //服务统计数据
					ServerStats ss;
					//迭代遍历所有服务  总响应时间 totalResponseTime 等于每个服务的平均响应时间求和 totalResponseTime += ss.getResponseTimeAvg()
					for(Iterator var6 = nlb.getAllServers().iterator(); var6.hasNext(); totalResponseTime += ss.getResponseTimeAvg()) {
						//得到服务
						Server server = (Server)var6.next();
						//获取单个服务统计信息
						ss = stats.getSingleServerStat(server);
					}
                    //当前权重
					Double weightSoFar = 0.0D;
					//权重列表
					List<Double> finalWeights = new ArrayList();
					//获取所有服务
					Iterator var20 = nlb.getAllServers().iterator();
                    //迭代遍历所有服务
					while(var20.hasNext()) {
					    //获取服务
						Server serverx = (Server)var20.next();
						//获取服务的统计信息
						ServerStats ssx = stats.getSingleServerStat(serverx);
						//服务权重=总响应时间-服务的平均响应时间 这样服务的响应时间越长 其服务权重则越低
						double weight = totalResponseTime - ssx.getResponseTimeAvg();
						weightSoFar = weightSoFar + weight;
						//设置权重
						finalWeights.add(weightSoFar);
					}
                    //设置权重列表
					ResponseTimeWeightedRule.this.setWeights(finalWeights);
					return;
				}
			} catch (Exception var16) {
				ResponseTimeWeightedRule.logger.error("Error calculating server weights", var16);
				return;
			} finally {
				ResponseTimeWeightedRule.this.serverWeightAssignmentInProgress.set(false);
			}

		}
	}
}

WeightedResponseTimeRule#choose 方法源码分析

WeightedResponseTimeRule#choose 方法是响应时间权重轮训负载均衡规则的实现,具体逻辑如下:

  • ILoadBalancer 为空判断,如果 ILoadBalancer 为空,则直接返回服务为 null。
  • ILoadBalancer 不为空,执行 while 循环获取 Server。
  • 判断当前线程是否被中断,如果被中断,则直接返回 null。
  • 获取所有的服务列表,如果服务为空,则直接返回 null。
  • 找出最大的权重。
  • 判断是否有进行权重初始化,如果没有进行权重初始化,则调用父类 RoundRobinRule 的随机算法得到 server,如果进行了权重初始化,则使用最大权重生成一个大于或等于 0.0 且小于 1.0 的随机浮点数,也就是权重,然后遍历权重列表,从权重列表中找到大于计算出来的权重的索引,根据得到的索引去服务列表中获取服务。

WeightedResponseTimeRule 响应时间权重轮训规则的实现方式就是使用定时任务去计算权重,定时任务中会判断服务的平均响应时间,平均响应时间越大,权重越小,在进行负载均衡时,会使用权重成一个随机数,然后循环服务列表,找到权重大于这个随机数服务。

java 复制代码
//com.netflix.loadbalancer.WeightedResponseTimeRule#choose
public Server choose(ILoadBalancer lb, Object key) {
	//负载均衡器为空判断
	if (lb == null) {
		//负载均衡器为空 直接返回 null
		return null;
	} else {
		//定义 Serveri
		Server server = null;
		//while 循环
		while(server == null) {
			//获取服务权重集合
			List<Double> currentWeights = this.accumulatedWeights;
			//当前线程是否被中断
			if (Thread.interrupted()) {
				//当前线程被中断 直接返回 null
				return null;
			}
			//获取所有服务列表
			List<Server> allList = lb.getAllServers();
			//服务列表的 size 也就是服务的个数
			int serverCount = allList.size();
			//服务个数为0 判断
			if (serverCount == 0) {
				//服务个数为0 直接返回 null
				return null;
			}
			//服务索引
			int serverIndex = 0;
			//找出最大的权重
			double maxTotalWeight = currentWeights.size() == 0 ? 0.0D : (Double)currentWeights.get(currentWeights.size() - 1);
			//最大权重小于 0.001 且 服务数等于权重数 
			if (!(maxTotalWeight < 0.001D) && serverCount == currentWeights.size()) {
				//最大权重小于 0.001 且 服务数等于权重数 取非 表示采用权重轮训算法
				//返回一个大于或等于 0.0 且小于 1.0 的随机浮点数 也就是权重
				double randomWeight = this.random.nextDouble() * maxTotalWeight;
				int n = 0;
				//迭代遍历权重列表
				for(Iterator var13 = currentWeights.iterator(); var13.hasNext(); ++n) {
					Double d = (Double)var13.next();
					//权重列表中的权重大于计算出来的权重 也就找到了 server 索引下标
					if (d >= randomWeight) {
						serverIndex = n;
						break;
					}
				}
				//根据索引去服务列表中获取服务
				server = (Server)allList.get(serverIndex);
			} else {
				//表示没有初始化权重  使用父类 RoundRobinRule 的随机算法 得到server
				server = super.choose(this.getLoadBalancer(), key);
				//server 为空判断
				if (server == null) {
					//不为空直接返回server
					return server;
				}
			}
			//server 为空判断
			if (server == null) {
				//server 为空 线程休眠
				Thread.yield();
			} else {
				//服务是否是活跃的
				if (server.isAlive()) {
					//是返回 server 
					return server;
				}
				//否则给server 赋值为 null
				server = null;
			}
		}
		//server 不为空 直接返回server
		return server;
	}
}

RetryRule#choose 方法源码分析

RetryRule#choose 方法是重试负载均衡规则的实现,具体逻辑如下:

  • 获取当前请求时间,计算出限制时间(允许请求的最大时间)。
  • 使用轮训负载负载均衡规则获取一个服务。
  • 如果获取到的服务不为空 || 服务不是活跃的 && 当前时间小于限制时间,则创建一个中断线程的任务,中断时间为 deadline - System.currentTimeMillis()。
  • 开启 while 循环使用轮训的负载均衡规则获取服务,如果服务不为空是活跃的或者当前时间大于限制时间,则结束循环,返回 Server。

可以看出 RetryRule 实际调用的是轮询算法,在没有获取到服务时,会进行循环重试操作,超过设置的时间限制(默认为500 毫秒),则会退出。

java 复制代码
//com.netflix.loadbalancer.RetryRule#choose(com.netflix.loadbalancer.ILoadBalancer, java.lang.Object)
public Server choose(ILoadBalancer lb, Object key) {
    //获取当前请求时间
    long requestTime = System.currentTimeMillis();
    //限制时间(允许请求的最大时间)=当前时间+500毫秒  long maxRetryMillis = 500L;
    long deadline = requestTime + this.maxRetryMillis;
    //应答的 server
    Server answer = null;
    //IRule subRule = new RoundRobinRule();
    //使用轮询算法获取一个服务
    answer = this.subRule.choose(key);
    if ((answer == null || !answer.isAlive()) && System.currentTimeMillis() < deadline) {
        //服务不为空
        //服务不是活跃的
        //当前时间小于限制时间
        //创建一个中断线程的任务  deadline - System.currentTimeMillis() 后打断线程
        InterruptTask task = new InterruptTask(deadline - System.currentTimeMillis());
        //开启 while 循环 只要当前线程没有被中断 就一直循环执行
        while(!Thread.interrupted()) {
            //使用轮询算法获取一个服务
            answer = this.subRule.choose(key);
            if (answer != null && answer.isAlive() || System.currentTimeMillis() >= deadline) {
                //服务不为空 && 服务是活跃的 || 当前时间已经大于限制时间 则退出 while 循环
                break;
            }
            //线程让出 cpu 资源
            Thread.yield();
        }
        //while 循环结束 清除任务
        task.cancel();
    }
    //返回服务
    return answer != null && answer.isAlive() ? answer : null;
}

BestAvailableRule#choose 方法源码分析

BestAvailableRule 继承了ClientConfigEnabledRoundRobinRule,主要逻辑如下:

  • 判断负载均衡器统计信息是否为空,如果为空则调用父类的负载均衡算法,也就是轮询算法规则(ClientConfigEnabledRoundRobinRule#choose)。
  • 负载均衡器统计信息不为空,获取所有服务,判断当前时间服务断路器是否打开,短路打开不做处理,断路器如果没有打开,则判断并发连接数是否小于最小并发连接数,小于则完成赋值,返回 Server。
  • 会遍历所有的服务提供者,选择并发量最小的那个服务,这个算法,可以将请求转发到压力最小的服务器,但是如果副本数太多,每次都要循环计算出最小并发,还是比较好资源的。
  • 最后会再次对 Server 为空进行判断,如果 Server 为空则调用父类的负载均衡算法,也就是轮询算法规则(ClientConfigEnabledRoundRobinRule#choose)。
java 复制代码
//com.netflix.loadbalancer.BestAvailableRule#choose
public Server choose(Object key) {
    //负载均衡器统计信息为空判断
    if (this.loadBalancerStats == null) {
        //如果统计信息为空 调用父类的负载均衡规则 也就是轮询算法规则 ClientConfigEnabledRoundRobinRule#choose
        return super.choose(key);
    } else {
        //不为空 获取所有的 server
        List<Server> serverList = this.getLoadBalancer().getAllServers();
        //最小并发连接数
        int minimalConcurrentConnections = 2147483647;
        //当前时间
        long currentTime = System.currentTimeMillis();
        Server chosen = null;
        //迭代遍历 服务列表
        Iterator var7 = serverList.iterator();

        while(var7.hasNext()) {
            Server server = (Server)var7.next();
            //获取服务统计信息
            ServerStats serverStats = this.loadBalancerStats.getSingleServerStat(server);
            //服务断路器是否打开
            if (!serverStats.isCircuitBreakerTripped(currentTime)) {
                //服务断路器没有打开
                //获取并发连接数
                int concurrentConnections = serverStats.getActiveRequestsCount(currentTime);
                //判断并发连接数是否小于最小并发连接数
                if (concurrentConnections < minimalConcurrentConnections) {
                    //小于 则把当前并发连接数赋值给 最小并发连接数
                    minimalConcurrentConnections = concurrentConnections;
                    //赋值 server
                    chosen = server;
                }
            }
        }
        //server 为 null 判断
        if (chosen == null) {
            //调用父类的负载均衡算法 其实就是轮询的算法  ClientConfigEnabledRoundRobinRule#choose
            return super.choose(key);
        } else {
            return chosen;
        }
    }
}

//com.netflix.loadbalancer.ClientConfigEnabledRoundRobinRule#choose
public Server choose(Object key) {
    //轮训规则为空判断
    if (this.roundRobinRule != null) {
        //轮训规则不为空 执行轮训规则算法
        return this.roundRobinRule.choose(key);
    } else {
        throw new IllegalArgumentException("This class has not been initialized with the RoundRobinRule class");
    }
}

AvailabilityFilteringRule#choose 方法源码分析

可用性过滤规则 AvailabilityFilteringRule 继承自 PredicateBasedRule,它和其他规则不一样,它将服务委托给 AbstractServerPredicate 过滤掉一些连接失败且高并发的 Server。

java 复制代码
//com.netflix.loadbalancer.AvailabilityFilteringRule#choose
public Server choose(Object key) {
    int count = 0;
    //RoundRobinRule roundRobinRule = new RoundRobinRule();
    //循环使用轮训算法获取服务  最大循环10次
    for(Server server = this.roundRobinRule.choose(key); count++ <= 10; server = this.roundRobinRule.choose(key)) {
        //断言规则判断 sever 是否符合规则 符合规则返回 不符合规则重新选择
        if (this.predicate.apply(new PredicateKey(server))) {
            return server;
        }
    }
    //如果循环了10次还没有找到 server 就调用父类的方法 PredicateBasedRule#choose
    return super.choose(key);
}

//com.netflix.loadbalancer.PredicateBasedRule#choose
public Server choose(Object key) {
    //获取负载均衡器
    ILoadBalancer lb = this.getLoadBalancer();
    //从断言中循环过滤后返回 server
    Optional<Server> server = this.getPredicate().chooseRoundRobinAfterFiltering(lb.getAllServers(), key);
    //返回 server
    return server.isPresent() ? (Server)server.get() : null;
}

ZoneAvoidanceRule 源码分析

ZoneAvoidanceRule 继承了 PredicateBasedRule 类,ZoneAvoidanceRule 的构造方法中传入了 ZoneAvoidancePredicate 和 AvailabilityPredicate,ZoneAvoidancePredicate 判断一个 Zone 的运行性能是否可用,剔除不可用的 Zone Server,AvailabilityPredicate 用于过滤掉连接数过多的 Server,该规则会先过滤,然后使用轮询算法,选出可用的服务,Ribbon 默认使用的也是这个负载均衡策略。

java 复制代码
//com.netflix.loadbalancer.ZoneAvoidanceRule#ZoneAvoidanceRule
public ZoneAvoidanceRule() {
    //区域断言
    ZoneAvoidancePredicate zonePredicate = new ZoneAvoidancePredicate(this);
    //可用性断言
    AvailabilityPredicate availabilityPredicate = new AvailabilityPredicate(this);
    this.compositePredicate = this.createCompositePredicate(zonePredicate, availabilityPredicate);
}

//com.netflix.loadbalancer.PredicateBasedRule#choose
public Server choose(Object key) {
    //获取负载均衡器
    ILoadBalancer lb = this.getLoadBalancer();
    //从断言中循环过滤后返回 server
    Optional<Server> server = this.getPredicate().chooseRoundRobinAfterFiltering(lb.getAllServers(), key);
    //返回 server
    return server.isPresent() ? (Server)server.get() : null;
}

如有不正确的地方请各位指出纠正。

相关推荐
掘金-我是哪吒1 小时前
微服务mysql,redis,elasticsearch, kibana,cassandra,mongodb, kafka
redis·mysql·mongodb·elasticsearch·微服务
茶馆大橘2 小时前
微服务系列六:分布式事务与seata
分布式·docker·微服务·nacos·seata·springcloud
想进大厂的小王5 小时前
项目架构介绍以及Spring cloud、redis、mq 等组件的基本认识
redis·分布式·后端·spring cloud·微服务·架构
九卷技术录5 小时前
(微服务)服务治理:几种开源限流算法库/应用软件介绍和使用
微服务·服务治理·限流算法
customer085 小时前
【开源免费】基于SpringBoot+Vue.JS医院管理系统(JAVA毕业设计)
java·vue.js·spring boot·后端·spring cloud·开源·intellij-idea
杨荧6 小时前
【JAVA毕业设计】基于Vue和SpringBoot的服装商城系统学科竞赛管理系统
java·开发语言·vue.js·spring boot·spring cloud·java-ee·kafka
阿伟*rui6 小时前
认识微服务,微服务的拆分,服务治理(nacos注册中心,远程调用)
微服务·架构·firefox
想进大厂的小王9 小时前
Spring-cloud 微服务 服务注册_服务发现-Eureka
微服务·eureka·服务发现
Gemini199513 小时前
分布式和微服务的区别
分布式·微服务·架构