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;
}

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

相关推荐
大傻^7 小时前
钱的教育架构师思维:从现金流到资产负债表的系统工程
微服务·架构设计·系统思维·财务自由·技术债管理
05大叔11 小时前
微服务,拆分原则,远程调用,服务治理,OpenFeign
微服务·云原生·架构
彭于晏Yan13 小时前
Springboot实现微服务监控
spring boot·后端·微服务
小江的记录本13 小时前
【Spring Boot—— .yml(YAML)】Spring Boot中.yml文件的基础语法、高级特性、实践技巧
xml·java·spring boot·后端·spring·spring cloud·架构
xiaolingting16 小时前
Gateway 网关流控与限流架构指南
spring cloud·架构·gateway·sentinel
唯一世17 小时前
Open Feign最佳实践
java·spring cloud
淘源码d17 小时前
基于Spring Boot + Vue的诊所管理系统(源码)全栈开发指南
java·vue.js·spring boot·后端·源码·门诊系统·诊所系统
喵叔哟17 小时前
8. 【Blazor全栈开发实战指南】--路由与导航
数据库·微服务·.net
Predestination王瀞潞18 小时前
MVC 架构→分层架构→微服务架构 架构演进
微服务·架构·mvc
indexsunny18 小时前
互联网大厂Java面试实战:微服务与Spring Boot在电商场景下的应用解析
java·spring boot·redis·docker·微服务·kubernetes·oauth2