zl程序教程

您现在的位置是:首页 >  其他

当前栏目

Ribbon 负载均衡源码解读

2023-04-18 13:06:44 时间

 转载请注明出处:

1.什么是Ribbon

  是 Netflix 发布的一个负载均衡器,有助于控制 HTTP 和 TCP客户端行为。在 SpringCloud 中, nacos一般配合Ribbon进行使用,Ribbon提供了客户端负载均衡的功能,Ribbon利用从nacos中读 取到的服务信息,在调用服务节点提供的服务时,会合理(策略)的进行负载。 在SpringCloud中可以将注册中心和Ribbon配合使用,Ribbon自动的从注册中心中获取服务提供者的 列表信息,并基于内置的负载均衡算法,请求服务。

  Ribbon的主要作用:基于Ribbon实现服务调用, 通过拉取到的所有服务列表组成(服务名-请求路径的)映射关系。借助 RestTemplate 最终进行调用负载均衡,当有多个服务提供者时,Ribbon可以根据负载均衡的算法自动的选择需要调用的服务地址

 

2. restTemplate 调用接口时使用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);
}

 

3. Ribbon 原理

  使用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));
    }

  获取到了url、主机名、和服务的id,再将这些信息作为参数传到LoadBalancerClient(this.loadBalancer)的execute的方法中;

  而在LoadBalancerClient的execute方法中可以通过服务id获取到服务列表,并获取合适的服务的端口号,这个方法的实现会进入到 RibbonLoadBalancerClient.execute 方法中;

  • getLoadBalancer(serviceId):根据服务id获取ILoadBalancer,而ILoadBalancer会拿着服务id去eureka中获取服务列表并保存起来。

  • getServer(loadBalancer):利用内置的负载均衡算法,从服务列表中选择一个。也就是127.0.0.1:8080

  从这里可以知道,负载均衡的实现是在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;
        }
    }

 

4.常用的Ribbon负载均衡策略

   
内置负载均衡规则类规则描述
RoundRobinRule 简单轮询服务列表来选择服务器。它是Ribbon默认的负载均衡规则。
AvailabilityFilteringRule 对以下两种服务器进行忽略: (1)在默认情况下,这台服务器如果3次连接失败,这台服务器就会被设置为“短路”状态。短路状态将持续30秒,如果再次连接失败,短路的持续时间就会几何级地增加。 (2)并发数过高的服务器。如果一个服务器的并发连接数过高,配置了AvailabilityFilteringRule规则的客户端也会将其忽略。并发连接数的上限,可以由客户端的..ActiveConnectionsLimit属性进行配置。
WeightedResponseTimeRule 为每一个服务器赋予一个权重值。服务器响应时间越长,这个服务器的权重就越小。这个规则会随机选择服务器,这个权重值会影响服务器的选择。
ZoneAvoidanceRule 以区域可用的服务器为基础进行服务器的选择。使用Zone对服务器进行分类,这个Zone可以理解为一个机房、一个机架等。而后再对Zone内的多个服务做轮询。
BestAvailableRule 忽略那些短路的服务器,并选择并发数较低的服务器。
RandomRule 随机选择一个可用的服务器。
RetryRule 重试机制的选择逻辑

 

  注意:nacos-discovery依赖了ribbon,可以不用再引入ribbon依赖