SpringCloud4️⃣负载均衡 Ribbon

👉回顾 Eureka

  1. 部署 eureka-server,将 eureke-client 注册到 Eureka 中。
  2. 以服务名代替主机和 IP(服务发现)。
  3. @LoadBalanced 实现负载均衡,其原理是本文的 Ribbon。

1、Ribbon

Ribbon 是 Netflix 开发的,提供客户端负载均衡能力。

远程调用流程:

  1. 服务消费者通过 RestTemplate 发起远程调用,HTTP 请求被 Ribbon 拦截。

  2. Ribbon 从 eureka-server 拉取服务实例列表,基于负载均衡算法选择服务实例。

  3. 远程调用。

    image-20220615234527166

Ribbon 底层如何通过 service 服务名,实现服务拉取和远程调用呢?

👇 分析源码,关键类如下:

  • LoadBalancerInterceptor:拦截 RestTemplate 的请求,实现服务拉取和负载均衡,用实际地址信息替换服务名称。
  • LoadBalcnerClient
  • IRule:负载均衡策略。

2、源码分析

示例:order-service 向 user-service 发起远程调用。

2.1、拦截器

LoadBalancerInterceptor

拦截客户端 HTTP 请求

  1. 获取 URI

  2. 获取主机名,即服务名

  3. 调用负载均衡器

    @Override
    public ClientHttpResponse intercept(final HttpRequest request, final byte[] body,
                                        final ClientHttpRequestExecution execution) throws IOException {
        // 获取URI
        final URI originalUri = request.getURI();
        // 获取服务名
        String serviceName = originalUri.getHost();
        Assert.state(serviceName != null,
                     "Request URI does not contain a valid hostname: " + originalUri);
        // 负载均衡器
        return this.loadBalancer.execute(serviceName,
                                         this.requestFactory.createRequest(request, body, execution));
    }
    

2.2、负载均衡器

2.2.1、接口

LoadBalancerClient 接口

客户端负载均衡器。

Hint:此处的 serviceId 即服务名。

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

2.2.2、实现类

RibbonLoadBalancerClient

  • 实现方法:调用重载方法。

    @Override
    public <T> T execute(String serviceId, LoadBalancerRequest<T> request)
        throws IOException {
        return execute(serviceId, request, null);
    }
    
  • 重载方法

    1. 基于服务名获取负载均衡器

    2. 基于 IRule 进行负载均衡

    public <T> T execute(String serviceId, LoadBalancerRequest<T> request, Object hint)
      throws IOException {
      // 获取负载均衡器
      ILoadBalancer loadBalancer = getLoadBalancer(serviceId);
      // 负载均衡
      Server server = getServer(loadBalancer, hint);
      if (server == null) {
          throw new IllegalStateException("No instances available for " + serviceId);
      }
      RibbonServer ribbonServer = new RibbonServer(serviceId, server,
                                                   isSecure(server, serviceId),
                                                   serverIntrospector(serviceId).getMetadata(server));
    
      return execute(serviceId, ribbonServer, request);
    }
    

负载均衡器

loadBalancer 类

image-20220618122037215

负载均衡

getServer()

Hint:方法参数 hint 表示要使用的负载均衡策略。

protected Server getServer(ILoadBalancer loadBalancer, Object hint) {
   if (loadBalancer == null) {
      return null;
   }
   // Use 'default' on a null hint, or just pass it on?
   return loadBalancer.chooseServer(hint != null ? hint : "default");
}

ZoneAwareLoadBalancer 类

Hint:方法参数 key 表示要使用的负载均衡策略,即上个方法的 hint

@Override
public Server chooseServer(Object key) {
    if (!ENABLED.get() || getLoadBalancerStats().getAvailableZones().size() <= 1) {
        logger.debug("Zone aware logic disabled or there is only one zone");
        // 调用超类BaseLoadBalancer
        return super.chooseServer(key);
    }
	// ...
}

BaseLoadBalancer 类

Hint

  1. 方法参数 key 表示要使用的负载均衡策略。

  2. IRule 类 定义了负载均衡策略。

    public Server chooseServer(Object key) {
        if (counter == null) {
            counter = createCounter();
        }
        counter.increment();
        if (rule == null) {
            return null;
        } else {
            try {
                // 选择实例
                return rule.choose(key);
            } catch (Exception e) {
                logger.warn("LoadBalancer [{}]:  Error choosing server for key {}", name, key, e);
                return null;
            }
        }
    }
    

最终获取到一个实例

image-20220618123936045

2.3、图示流程

  1. 服务消费者发起 HTTP 请求

  2. RibbonLoadBalancerClient 拦截请求,先后获取 URI服务名称

    http://userservice/user/1
    user-service
    
  3. DynamicServerListLoadBalancer 根据服务名称(user-service)向 Eureka 拉取服务列表

  4. IRule 基于内置负载均衡规则,从列表中选择一个实例

    localhost:8081
    
  5. RibbonLoadBalancerClient 以真实地址(localhost:8081)替代服务名(userservice),发起真实请求。

    http://localhost:8081/user/1
    

    image-20210713224724673

3、负载均衡策略

3.1、IRule

IRule 接口定义了负载均衡规则

  • 接口实现关系

    image-20220616235451562

  • 说明

    含义 选择规则
    RoundRobinRule 轮询 轮询服务列表(默认)
    WeightedResponseTimeRule 权重响应 基于响应时间为每个服务器分配动态权重,加权轮询
    ZoneAvoidanceRule 区域避免 基于区域(Zone)和可用性,轮询
    AvailabilityFilteringRule 可用过滤 忽略 2 类服务器:
    连续读取或连接失败而处于断路状态;超过可配置限制的活动连接(高并发)
    BestAvailableRule 最佳可用 忽略断路服务器,选择并发请求低的服务器
    RandomRule 随机 随机选择一个可用服务器
    RetryRule 重试 向现有规则添加重试逻辑

3.2、设置策略

默认采用 RoundRobinRule

可通过代码或配置方式选择策略。

  1. 代码方式:在 order-service 的任意 @Configuration 配置类中,将 IRule 实现类注册到 Spring 中。

    (所有微服务都会应用此规则,粒度大)

    // 如:在启动类中
    @Bean
    public IRule randomRule(){
        return new RandomRule();
    }
    
  2. 配置方式:在 order-service 的 application.yml 中

    (可指定微服务,细粒度)

    userservice: # 给指定微服务配置负载均衡规则
      ribbon:
        NFLoadBalancerRuleClassName: com.netflix.loadbalancer.RandomRule # 负载均衡规则 
    

Hint:通常使用默认的负载均衡规则 RoundRobinRule,不做修改。

4、饥饿加载

Ribbon 默认是懒加载机制

即首次发起远程调用时,才创建 LoadBalanceClient,请求时间长。

饥饿加载:在服务启动时创建 LoadBalanceClient

ribbon:
  eager-load:
    enabled: true
    clients: userservice
posted @ 2022-06-02 17:53  Jaywee  阅读(36)  评论(0编辑  收藏  举报

👇