Spring Cloud Ribbon源码分析

我不是女神ヾ 2022-08-28 08:59 213阅读 0赞

一、ILoadBalancer

Ribbon实现负载均衡的核心接口为ILoadBalancer

  1. public interface ILoadBalancer {
  2. //添加后端服务
  3. public void addServers(List<Server> newServers);
  4. //选择一个后端服务
  5. public Server chooseServer(Object key);
  6. //标记一个服务不可用
  7. public void markServerDown(Server server);
  8. //获取当前可用的服务列表
  9. public List<Server> getReachableServers();
  10. //获取所有后端服务列表
  11. public List<Server> getAllServers();
  12. }

ILoadBalancer 接口的类层结构如下所示:

watermark_type_ZHJvaWRzYW5zZmFsbGJhY2s_shadow_50_text_Q1NETiBA5oCd57u055qE5rex5bqm_size_20_color_FFFFFF_t_70_g_se_x_16

核心功能实现在BaseLoadBalancer类中,核心组件有IRule、IPing和LoadBalancerStats等。

1. IRule

IRule 接口是对负载均衡策略的一种抽象,可以通过实现这个接口来提供各种适用的负载均衡算法

  1. public interface IRule{
  2. public Server choose(Object key);
  3. public void setLoadBalancer(ILoadBalancer lb);
  4. public ILoadBalancer getLoadBalancer();
  5. }

choose 方法是该接口的核心方法,根据key从指定服务列表中获取对应的服务

2. IPing

IPing 接口判断目标服务是否存活,定义如下:

  1. public interface IPing {
  2. public boolean isAlive(Server server);
  3. }
  4. 可以看到 IPing 接口中只有一个 isAlive() 方法,通过对服务发出"Ping"操作来获取服务响应,从而判断该服务是否可用。

3. LoadBalancerStats

  1. LoadBalancerStats 类记录负载均衡的实时运行信息,用来作为负载均衡策略的运行时输入。
  2. ILoadBalancer接口中的chooseServer方法是实现负载均衡的核心方法,BaseLoadBalancer定义的chooseServer方法实现如下:
  3. public Server chooseServer(Object key) {
  4. if (counter == null) {
  5. counter = createCounter();
  6. }
  7. counter.increment();
  8. if (rule == null) {
  9. return null;
  10. } else {
  11. try {
  12. return rule.choose(key);
  13. } catch (Exception e) {
  14. return null;
  15. }
  16. }
  17. }
  18. 这里使用了 IRule 接口的 choose 方法选择服务信息,负载均衡算法可以分成两大类,即**静态负载均衡算法**和**动态负载均衡算法**。静态负载均衡算法比较容易理解和实现,典型的包括随机(Random)、轮询(Round Robin)和加权轮询(Weighted Round Robin)算法等。典型动态算法包括源 IP 哈希算法、最少连接数算法、服务调用时延算法等。

二、负载均衡策略

  1. Ribbon实现的负载均衡策略如下图所示:
  2. ![watermark_type_ZHJvaWRzYW5zZmFsbGJhY2s_shadow_50_text_Q1NETiBA5oCd57u055qE5rex5bqm_size_20_color_FFFFFF_t_70_g_se_x_16 1][]

RandomRule:随机策略

RoundRobbinRule:轮询策略

RetryRule:请求失败的重试策略,属于容错机制

WeightedResponseTimeRule:该策略与请求的响应时间有关,显然,如果响应时间越长,就代表这个服务的响应能力越有限,那么分配给该服务的权重就应该越小。

BestAvailableRule :选择一个并发请求量最小的服务器,逐个考察服务器然后选择其中活跃请求数最小的服务器。

AvailabilityFilteringRule:通过检查 LoadBalancerStats 中记录的各个服务器的运行状态,过滤掉那些处于一直连接失败或处于高并发状态的后端服务器。

三、Spring Cloud Netflix Ribbon

  1. Spring Cloud Netflix Ribbon组件在Netflix Ribbon组件上进行封装和集成,调用关系如下:

watermark_type_ZHJvaWRzYW5zZmFsbGJhY2s_shadow_50_text_Q1NETiBA5oCd57u055qE5rex5bqm_size_20_color_FFFFFF_t_70_g_se_x_16 2

  1. @LoadBalanced 注解

为什么通过 @LoadBalanced 注解创建的 RestTemplate 就能自动具备客户端负载均衡的能力?

在 Spring Cloud Netflix Ribbon 中存在一个自动配置类——LoadBalancerAutoConfiguration 类。

LoadBalancerAutoConfiguration类的核心功能如下:

  1. 创建一个LoadBalancerInterceptor的Bean,用于实现对客户端发起请求时进行拦截, 以实现客户端负载均衡
  2. 创建一个RestTemplateCustomizer的Bean,用于给RestTemplate增加拦截器
  3. 维护了一个被@LoadBalanced 注解修饰的RestTemplate对象列表, 并在这里进行初始化, 通过调用RestTemplateCustomizer的实例来给需要客户端负载均衡的RestTemplate增加LoadBalancerinterceptor拦截器。

在LoadBalancerAutoConfiguration 类中,维护着一个被 @LoadBalanced 修饰的 RestTemplate 对象的列表。在初始化的过程中,对于所有被 @LoadBalanced 注解修饰的 RestTemplate,调用 RestTemplateCustomizer 的 customize 方法进行定制化,该定制化的过程就是对目标 RestTemplate 增加拦截器 LoadBalancerInterceptor,如下所示:

  1. @Configuration(proxyBeanMethods = false)
  2. @ConditionalOnMissingClass("org.springframework.retry.support.RetryTemplate")
  3. static class LoadBalancerInterceptorConfig {
  4. @Bean
  5. public LoadBalancerInterceptor loadBalancerInterceptor(
  6. LoadBalancerClient loadBalancerClient,
  7. LoadBalancerRequestFactory requestFactory) {
  8. return new LoadBalancerInterceptor(loadBalancerClient, requestFactory);
  9. }
  10. @Bean
  11. @ConditionalOnMissingBean
  12. public RestTemplateCustomizer restTemplateCustomizer(
  13. final LoadBalancerInterceptor loadBalancerInterceptor) {
  14. return restTemplate -> {
  15. List<ClientHttpRequestInterceptor> list = new ArrayList<>(
  16. restTemplate.getInterceptors());
  17. list.add(loadBalancerInterceptor);
  18. restTemplate.setInterceptors(list);
  19. };
  20. }
  21. }
  22. 这个 LoadBalancerInterceptor 用于实时拦截,可以看到它的构造函数中传入了一个对象 LoadBalancerClient,而在它的拦截方法本质上就是使用 LoadBalanceClient 来执行真正的负载均衡。LoadBalancerInterceptor 类代码如下所示:
  23. public class LoadBalancerInterceptor implements ClientHttpRequestInterceptor {
  24. private LoadBalancerClient loadBalancer;
  25. private LoadBalancerRequestFactory requestFactory;
  26. public LoadBalancerInterceptor(LoadBalancerClient loadBalancer,
  27. LoadBalancerRequestFactory requestFactory) {
  28. this.loadBalancer = loadBalancer;
  29. this.requestFactory = requestFactory;
  30. }
  31. public LoadBalancerInterceptor(LoadBalancerClient loadBalancer) {
  32. // for backwards compatibility
  33. this(loadBalancer, new LoadBalancerRequestFactory(loadBalancer));
  34. }
  35. @Override
  36. public ClientHttpResponse intercept(final HttpRequest request, final byte[] body,
  37. final ClientHttpRequestExecution execution) throws IOException {
  38. final URI originalUri = request.getURI();
  39. String serviceName = originalUri.getHost();
  40. Assert.state(serviceName != null,
  41. "Request URI does not contain a valid hostname: " + originalUri);
  42. return this.loadBalancer.execute(serviceName,
  43. this.requestFactory.createRequest(request, body, execution));
  44. }
  45. }
  46. 可以看到这里的拦截方法 intercept 直接调用了 LoadBalancerClient execute 方法完成对请求的负载均衡执行
  1. LoadBalanceClient 接口

    public interface LoadBalancerClient extends ServiceInstanceChooser {

    T execute(String serviceId, LoadBalancerRequest request) throws IOException;

    T execute(String serviceId, ServiceInstance serviceInstance,

    1. LoadBalancerRequest<T> request) throws IOException;

    URI reconstructURI(ServiceInstance instance, URI original);
    }

    这里有两个 execute 重载方法,用于根据负载均衡器所确定的服务实例来执行服务调用。而 reconstructURI 方法则用于构建服务 URI,使用负载均衡所选择的 ServiceInstance 信息重新构造访问 URI,也就是用服务实例的 host 和 port 再加上服务的端点路径来构造一个真正可供访问的服务。

    LoadBalancerClient接口的实现类有RibbonLoadBalancerClient和BlockingLoadBalancerClient,在RibbonLoadBalancerClient类中choose方法

    public ServiceInstance choose(String serviceId, Object hint) {
    Server server = getServer(getLoadBalancer(serviceId), hint);
    if (server == null) {

    1. return null;

    }
    return new RibbonServer(serviceId, server, isSecure(server, serviceId),

    1. serverIntrospector(serviceId).getMetadata(server));

    }

choose方法最终调用getServer获取服务

  1. protected Server getServer(ILoadBalancer loadBalancer, Object hint) {
  2. if (loadBalancer == null) {
  3. return null;
  4. }
  5. return loadBalancer.chooseServer(hint != null ? hint : "default");
  6. }
  7. 这里的 loadBalancer 对象就是前面介绍的 Netflix Ribbon 中的 ILoadBalancer 接口的实现类。这样,我们就把 Spring Cloud Netflix Ribbon Netflix Ribbon 的整体协作流程串联起来。

发表评论

表情:
评论列表 (有 0 条评论,213人围观)

还没有评论,来说两句吧...

相关阅读

    相关 Ribbon分析

    Ribbon是Netflix公司开源的一个客户端负载均衡的项目,一般配合Eureka使用。不过为了降低其他干扰因素,专注于Ribbon,这一次我们脱离Eureka讲Ribbon