Ribbon 负载均衡源码解读
转载请注明出处:
1.什么是Ribbon
是 Netflix 发布的一个负载均衡器,有助于控制 HTTP 和 TCP客户端行为。在 SpringCloud 中, nacos一般配合Ribbon进行使用,Ribbon提供了客户端负载均衡的功能,Ribbon利用从nacos中读 取到的服务信息,在调用服务节点提供的服务时,会合理(策略)的进行负载。 在SpringCloud中可以将注册中心和Ribbon配合使用,Ribbon自动的从注册中心中获取服务提供者的 列表信息,并基于内置的负载均衡算法,请求服务。
首先需要定义 RestTemplate 使用 Ribbon 策略;
@LoadBalanced @Bean public RestTemplate restTemplate() { return new RestTemplate(); }
本地使用 RestTemplate 调用远程接口;
@Autowired
private RestTemplate restTemplate;
@RequestMapping(value = "/echo/{id}", method = RequestMethod.GET)
public String echo(@PathVariable Long id) {
return restTemplate.getForObject("http://member-service/member/get/" + id, String.class);
}
使用RestTemplate对象只需要访问一个带有对象名称的路径,也就是http://userservice/user/XX,就可以访问到相对应的接口,这其中离不开LoadBalancerInterceptor的帮助,它会去RestTemplate的请求进行拦截,然后从Eureka中获取服务id与端口号,随后利用负载均衡算法得到真实的服务地址信息,替换服务id。
在LoadBalancerInterceptor这个类中,会有一个intercept方法,其拦截了用户的HttpRequest请求,通过调用以下api ;
public ClientHttpResponse intercept(final HttpRequest request, final byte[] body, final ClientHttpRequestExecution execution) throws IOException {
// 获取请求uri,也就是 http://user-service/user/8
URI originalUri = request.getURI();
// 获取uri路径的主机名,其实就是服务id,user-service
String serviceName = originalUri.getHost();
Assert.state(serviceName != null, "Request URI does not contain a valid hostname: " + originalUri);
// 处理服务id,和用户请求
return (ClientHttpResponse)this.loadBalancer.execute(serviceName, this.requestFactory.createRequest(request, body, execution));
}
而在LoadBalancerClient的execute方法中可以通过服务id获取到服务列表,并获取合适的服务的端口号,这个方法的实现会进入到 RibbonLoadBalancerClient.execute 方法中;
从这里可以知道,负载均衡的实现是在getServer方法中实现的
protected Server getServer(ILoadBalancer loadBalancer, Object hint) {
return loadBalancer == null ? null : loadBalancer.chooseServer(hint != null ? hint : "default");
}
查看 loadBalancer.chooseServer 的实现,进入 BaseLoadBalancer 类
public Server chooseServer(Object key) {
if (this.counter == null) {
this.counter = this.createCounter();
}
this.counter.increment();
if (this.rule == null) {
return null;
} else {
try {
return this.rule.choose(key);
} catch (Exception var3) {
logger.warn("LoadBalancer [{}]: Error choosing server for key {}", new Object[]{this.name, key, var3});
return null;
}
}
}
从方法名中可以看出 rule.choose 为定义的负载均衡规则的选择,查看这个方法的实现:
通过快捷键查看该方法的实现类可以看到 项目中所支持的负载均衡规则;若打断点 可以发现,前面传过来的default对应的是RoundRobinRule对象 ; RoundRobinRule对应的是一个轮询的规则,所以这里采用的默认负载均衡规则是轮询;
集成了nacos 后,使用 NacosRule 规则进行负载均衡:
public Server choose(Object key) {
try {
String clusterName = this.nacosDiscoveryProperties.getClusterName();
String group = this.nacosDiscoveryProperties.getGroup();
DynamicServerListLoadBalancer loadBalancer = (DynamicServerListLoadBalancer)this.getLoadBalancer();
String name = loadBalancer.getName();
// 根据group,name 等获取nacos 中的服务组
NamingService namingService = this.nacosServiceManager.getNamingService(this.nacosDiscoveryProperties.getNacosProperties());
// 获取服务组对应的所有实例信息:updateServiceNow 里面去调用 /instance/list接口查询服务信息
List<Instance> instances = namingService.selectInstances(name, group, true);
if (CollectionUtils.isEmpty(instances)) {
LOGGER.warn("no instance in service {}", name);
return null;
} else {
List<Instance> instancesToChoose = instances;
Instance instance = ExtendBalancer.getHostByRandomWeight2(instancesToChoose);
// 返回nacos的服务实例
return new NacosServer(instance);
}
} catch (Exception var10) {
LOGGER.warn("NacosRule error", var10);
return null;
}
}
内置负载均衡规则类 | 规则描述 |
---|---|
RoundRobinRule | 简单轮询服务列表来选择服务器。它是Ribbon默认的负载均衡规则。 |
AvailabilityFilteringRule | |
WeightedResponseTimeRule | 为每一个服务器赋予一个权重值。服务器响应时间越长,这个服务器的权重就越小。这个规则会随机选择服务器,这个权重值会影响服务器的选择。 |
ZoneAvoidanceRule | 以区域可用的服务器为基础进行服务器的选择。使用Zone对服务器进行分类,这个Zone可以理解为一个机房、一个机架等。而后再对Zone内的多个服务做轮询。 |
BestAvailableRule | 忽略那些短路的服务器,并选择并发数较低的服务器。 |
RandomRule | 随机选择一个可用的服务器。 |
RetryRule | 重试机制的选择逻辑 |
注意:nacos-discovery依赖了ribbon,可以不用再引入ribbon依赖