Ribbon是如何进行负载的

发布时间 2023-08-29 15:32:13作者: zhangliangddq

1.Ribbon是如何进行负载的

    配置类:RibbonClientConfiguration
     @Bean
	@ConditionalOnMissingBean
	public ILoadBalancer ribbonLoadBalancer(IClientConfig config,
			ServerList<Server> serverList, ServerListFilter<Server> serverListFilter,
			IRule rule, IPing ping, ServerListUpdater serverListUpdater) {
		if (this.propertiesFactory.isSet(ILoadBalancer.class, name)) {
			return this.propertiesFactory.get(ILoadBalancer.class, config, name);
		}

          //默认的负载均衡类
		return new ZoneAwareLoadBalancer<>(config, rule, ping, serverList,
				serverListFilter, serverListUpdater);
	}
    
  //DynamicServerListLoadBalancer
  public DynamicServerListLoadBalancer(IClientConfig clientConfig, IRule rule, IPing ping,
                                         ServerList<T> serverList, ServerListFilter<T> filter,
                                         ServerListUpdater serverListUpdater) {
        super(clientConfig, rule, ping);
        this.serverListImpl = serverList;
        this.filter = filter;
        this.serverListUpdater = serverListUpdater;
        if (filter instanceof AbstractServerListFilter) {
            ((AbstractServerListFilter) filter).setLoadBalancerStats(getLoadBalancerStats());
        }
        //初始化
        restOfInit(clientConfig);
    }

//方法
 void restOfInit(IClientConfig clientConfig) {
        boolean primeConnection = this.isEnablePrimingConnections();
        // turn this off to avoid duplicated asynchronous priming done in BaseLoadBalancer.setServerList()
        this.setEnablePrimingConnections(false);
        enableAndInitLearnNewServersFeature();

        updateListOfServers();
        if (primeConnection && this.getPrimeConnections() != null) {
            this.getPrimeConnections()
                    .primeConnections(getReachableServers());
        }
        this.setEnablePrimingConnections(primeConnection);
        LOGGER.info("DynamicServerListLoadBalancer for client {} initialized: {}", clientConfig.getClientName(), this.toString());
    }


  负载均衡类:DynamicServerListLoadBalancer

  //定时任务处理类
  PollingServerListUpdater
  @Override
    public synchronized void start(final UpdateAction updateAction) {
        if (isActive.compareAndSet(false, true)) {
            final Runnable wrapperRunnable = new Runnable() {
                @Override
                public void run() {
                    if (!isActive.get()) {
                        if (scheduledFuture != null) {
                            scheduledFuture.cancel(true);
                        }
                        return;
                    }
                    try {
                        updateAction.doUpdate();
                        lastUpdated = System.currentTimeMillis();
                    } catch (Exception e) {
                        logger.warn("Failed one update cycle", e);
                    }
                }
            };
            
            //默认时间 30秒 因此一个新的服务端上线后,客户端需要最多30秒才能进行使用
            scheduledFuture = getRefreshExecutor().scheduleWithFixedDelay(
                    wrapperRunnable,
                    initialDelayMs,
                    refreshIntervalMs,
                    TimeUnit.MILLISECONDS
            );
        } else {
            logger.info("Already active, no-op");
        }
    }
//


2. Ribbon负载均衡策略

  1. RoundRobinRule(轮询策略,按照服务顺序依次循环调用) 默认
  2. WeightedResponseTimeRule(权重比策略,优先选择权重比高的服务,也就是服务响应时间比较短的,响应时间越长权重比越低)
  3. RandomRule(随机策略,服务提供者列表随机选择一个服务)
  4. BestAvailableRule(最小连接数策略,获取服务列表中连接数最小的服务实例)
  5. RetryRule(重试策略,重试获取已经失效的服务,指定时间没有获取到返回NULL)
  6. AvailabilityFilteringRule(可用性敏感策略,过滤非健康服务实例,选择lianji)
  7. ZoneAvoidanceRule(区域敏感策略)

3.具体的调用执行

@Override
  public Response execute(Request request, Request.Options options) throws IOException {
  	try {
  		URI asUri = URI.create(request.url());
  		String clientName = asUri.getHost();
  		URI uriWithoutHost = cleanUrl(request.url(), clientName);
  		FeignLoadBalancer.RibbonRequest ribbonRequest = new FeignLoadBalancer.RibbonRequest(
  				this.delegate, request, uriWithoutHost);
          
          //获取配置 即初始化配置 
  		IClientConfig requestConfig = getClientConfig(options, clientName);
          
          //lbClient会创建client 此时才会加载服务端 每间隔30秒调用一次
  		return lbClient(clientName)
  				.executeWithLoadBalancer(ribbonRequest, requestConfig).toResponse();
  	}
  	catch (ClientException e) {
  		IOException io = findIOException(e);
  		if (io != null) {
  			throw io;
  		}
  		throw new RuntimeException(e);
  	}
  }
  

4. Ribbon-eager-load(饥饿加载)模式

Ribbon 对于负载 Client 是在服务启动后,发生调用的时候才会去创建 Client,所以在第一次发生 http 请求调用的时候,不光要算上 http 的请求时间,还要算上 Client 的创建时间,所以第一次调用的时候才会很慢,具体内容参考第三点
Ribbon 的 DynamicServerListLoadBalancer 会将 feign 客户端进行负载,然后进行调用,第一次调用的时间就是会长一些,第二次调用直接进行请求可以看到调用时间很快。

5. 开启饥饿负载

ribbon:
nacos:
  enabled: true # 开启naocos轮询
eager-load:
 enabled: true  # 开启Ribbon的饥饿加载模式(防止第一次请求超时的问题)
 clients: Lxlxxx-system2 # 指定需要开启的服务(需要开启Ribbon的饥饿加载模式)
 ReadTimeout: 10000
 ConnectTimeout: 10000
 MaxAutoRetries: 0
 MaxAutoRetriesNextServer: 1
 OkToRetryOnAllOperations: false

在项目启动的时候,可以从日志看到,已经把 Lxlxxx-system2 服务进行加载,从而避免了第一次请求超时的情况

其实这种饥饿加载模式,类似于“客户端负载预热”的一个操作,项目启动的时候进行加载,防止服务之间调用可以因为数据量、业务逻辑处理复杂性导致接口超时,如果你的服务之间调用业务处理比较复杂、且慢,不妨可以试试这种解决方式。