LoadBalanced restTemplate
首先在spring.cloud.common包的spring.factories中定义了org.springframework.boot.autoconfigure.EnableAutoConfiguration=org.springframework.cloud.client.loadbalancer.LoadBalancerAutoConfiguration。LoadBalancerAutoConfiguration中获取了@LoadBalanced的RestTemplate。
@LoadBalanced
@Autowired(required = false)
private List<RestTemplate> restTemplates = Collections.emptyList();
然后对被@LoadBalanced标注的restTemplates注入拦截器LoadBalancerInterceptor。
@Bean
public LoadBalancerInterceptor ribbonInterceptor(
LoadBalancerClient loadBalancerClient,
LoadBalancerRequestFactory requestFactory) {
return new LoadBalancerInterceptor(loadBalancerClient, requestFactory);
}
restTemplate的http请求的方法,最终都会调用到doExecute()方法,在doExecute()方法中会创建InterceptingClientHttpRequest,这个request包含了刚才被注入的拦截器,在真正的http调用之前,会先执行负载均衡拦截器的intercept方法,在该方法中又会调用RibbonLoadBalancerClient的execute方法。
主要逻辑
execute方法中,先获取ILoadBalancer实例,在调用ILoadBalancer实例的chooseServer方法获取到具体Server实例
ILoadBalancer loadBalancer = getLoadBalancer(serviceId);
Server server = getServer(loadBalancer, hint);
ILoadBalancer如何获取
会根据serviceId创建一个子容器,让这个子容器去加载@RibbonClient或@RibbonClients引入的配置类(默认是RibbonClientConfiguration,如果自定义了配置类会覆盖默认配置,conditinalOnMissingBean),引入的配置类中含有ILoadBalancer,再从子容器获取ILoadBalancer bean(默认是ZoneAwareLoadBalancer)。
RibbonLoadBalancerClient.getLoadBalancer(String serviceId)
SpringClientFactory.getLoadBalancer(String name)
getInstance(name, ILoadBalancer.class)
super.getInstance(name, type)
getContext(String name)
createContext(String name)
context.getBean(type)
NamedContextFactory#createContext(String name):
//自定义配置类
if (this.configurations.containsKey(name)) {
//configurations就是RibbonClient通过xxRegistrar引入引入的配置类
for (Class<?> configuration : this.configurations.get(name)
.getConfiguration()) {
context.register(configuration);
}
}
//默认配置类
for (Map.Entry<String, C> entry : this.configurations.entrySet()) {
if (entry.getKey().startsWith("default.")) {
for (Class<?> configuration : entry.getValue().getConfiguration()) {
context.register(configuration);
}
}
}
//子容器名称就是serviceId
protected String generateDisplayName(String name) {
return this.getClass().getSimpleName() + "-" + name;
}
例子
在入口类中加入@RibbonClient
@EnableDiscoveryClient
@SpringBootApplication
@RibbonClient(name = "nacos-payment-provider",configuration= MyConfig.class)
public class OrderNacosMain83
自定义配置类,其实被spingboot入口类扫到也无大碍,只不过父容器也会定义一份ribbon相关的bean,而这些用不到,只会用子容器的
@Configuration
public class MyConfig {
@Bean
public IRule rule() {
return new RoundRobinRule();
}
}
如何获取serverList
ZoneAwareLoadBalancer构造方法执行时,会调用NacosNamingService#selectInstances(String serviceName, String groupName, List clusters, boolean healthy, boolean subscribe).
核心接口
IRule
RetryRule
底层还是调用子RUle(默认RoudRobinRule),如果返回的Server不可用,如果会在一个时间范围内重试(默认500ms)。
public Server choose(ILoadBalancer lb, Object key) {
answer = subRule.choose(key);
if (((answer == null) || (!answer.isAlive()))
&& (System.currentTimeMillis() < deadline)) {
//开启一个延时任务,中断当前线程
InterruptTask task = new InterruptTask(deadline
- System.currentTimeMillis());
while (!Thread.interrupted()) {
answer = subRule.choose(key);
if (((answer == null) || (!answer.isAlive()))
&& (System.currentTimeMillis() < deadline)) {
/* pause and retry hoping it's transient */
Thread.yield();
} else {
break;
}
}
task.cancel();
}
if ((answer == null) || (!answer.isAlive())) {
return null;
} else {
return answer;
}
}
BestAvailableRule
字面意思上理解是并发连接数最少的Server
WeightedResponseTimeRule
根据平均响应时间来选择,权值=总的响应时间-单个响应时间+前一个权值,平均响应时间小的权重大,会维护一个权重集合,定时更新这个集合,这个集合的每个元素的权值是前面元素的权值和,权值大的说明区间范围大,被选中的概率更大,并不是一定会选择权值最大的。权值还没更新时,使用RoundRobin作为备选
比如A,B,C,D4个节点,平均响应时间分别为10,5,12,20,总响应时间47,
计算出来的权值为:37,79,114,141
相当于得到4个区间:[0,37] [37,79] [79,114] [114,141]
再从0~141随机取值,第一个大于随机值的权值即为选中Server
if (maxTotalWeight < 0.001d || serverCount != currentWeights.size()) {
server = super.choose(getLoadBalancer(), key);
if(server == null) {
return server;
}
} else {
// generate a random weight between 0 (inclusive) to maxTotalWeight (exclusive)
//0~总权值内随机获取
double randomWeight = random.nextDouble() * maxTotalWeight;
// pick the server index based on the randomIndex
int n = 0;
for (Double d : currentWeights) {
if (d >= randomWeight) {
serverIndex = n;
break;
} else {
n++;
}
}
server = allList.get(serverIndex);
}
AvailabilityFilteringRule
也是对RoundRobin的封装,会对Server可用性进行过滤,熔断了的或活动的请求数超出限制的,在剩下的Server中轮询