面试官:如何设计一个Ribbon负载均衡组件

448 阅读3分钟

1:什么是Ribbon

Spring Cloud Ribbon是一个基于HTTP和TCP的客户端负载均衡工具,它基于Netflix Ribbon实现.....

2:@LoadBalanced注解的原理

1: 为什么在RestTemplate上加上该注解就可以实现负载均衡

  • Ribbon底层还是要依赖于RestTemplate进行通信,这毋庸置疑。既然要让请求能够达到负载均衡的效果,那么就必须要对请求做一些特别的处理。那么拦截器 过滤器 就可以很好的实现这个效果。

  • 拦截器要拦截RestTemplate发起的请求,在此之前,例如可以拿到所有的实例A的服务,然后根据负载均衡策略选择其中一个服务,再进行对其调用

2:在没有Ribbon之前的调用,服务A直接通过url地址直接访问

截屏2022-04-26 下午4.11.22.png

3:Ribbon调用过程

截屏2022-04-26 下午4.14.07.png

2:所以要搞懂Ribbon的原理就有几个点需要搞清楚

  • 2.1:拦截器在什么时候设置的

  • 2.2:如何选择一个负载均衡策略

  • 2.3:如何从注册中心获取所有服务实例列表

3: 拦截器在什么时候设置的

根据SpringBoot自动装配的原理,我们找到该配置类 截屏2022-04-26 下午4.20.35.png

在该配置上有有一行注解 @AutoConfigureBefore({LoadBalancerAutoConfiguration.class, AsyncLoadBalancerAutoConfiguration.class}),我们进入到LoadBalancerAutoConfiguration.class

restTemplates 是所有被 @LoadBalanced 注解标记的 RestTemplate的集合

@LoadBalanced
@Autowired(required = false)
private List<RestTemplate> restTemplates = Collections.emptyList();

开始设置拦截器

@Configuration
@ConditionalOnMissingClass("org.springframework.retry.support.RetryTemplate")
static class LoadBalancerInterceptorConfig {

   //注入一个拦截器的Bean
   @Bean
   public LoadBalancerInterceptor ribbonInterceptor(
         LoadBalancerClient loadBalancerClient,
         LoadBalancerRequestFactory requestFactory) {
      return new LoadBalancerInterceptor(loadBalancerClient, requestFactory);
   }
   
   //遍历所有被@LoadBalanced标记的RestTemnplate的集合,然后遍历,为每一个
   //RestTemplate都设置一个LoadBalancerInterceptor的拦截器,就是上面这个拦截器
   //所以在RestTemplate在执行的时候,就会经过一系列的拦截器处理,包括LoadBalancerInterceptor
   //这个拦截器
   @Bean
   @ConditionalOnMissingBean
   public RestTemplateCustomizer restTemplateCustomizer(
         final LoadBalancerInterceptor loadBalancerInterceptor) {
      return restTemplate -> {
         List<ClientHttpRequestInterceptor> list = new ArrayList<>(
               restTemplate.getInterceptors());
         list.add(loadBalancerInterceptor);
         restTemplate.setInterceptors(list);
      };
   }

所以在RestTemplate发起请求的时候会经过LoadBalancerInterceptor这个拦截器处理,也就是执行其中的 intercept() 方法

@Override
public ClientHttpResponse intercept(final HttpRequest request, final byte[] body,
      final ClientHttpRequestExecution execution) throws IOException {
   //拿到请求的URL
   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));
}



public <T> T execute(String serviceId, LoadBalancerRequest<T> request, Object hint) throws IOException {
    //选择一个负载均衡策略
    ILoadBalancer loadBalancer = this.getLoadBalancer(serviceId);
    //根据负载均衡策略选择一个服务
    Server server = this.getServer(loadBalancer, hint);
    if (server == null) {
        throw new IllegalStateException("No instances available for " + serviceId);
    } else {
        RibbonLoadBalancerClient.RibbonServer ribbonServer = new RibbonLoadBalancerClient.RibbonServer(serviceId, server, this.isSecure(server, serviceId), this.serverIntrospector(serviceId).getMetadata(server));
        //最后发起调用
        return this.execute(serviceId, (ServiceInstance)ribbonServer, (LoadBalancerRequest)request);
    }
}

4: 如何选择一个负载均衡组件

//该方法就是获取一个负载均衡策略的组件
ILoadBalancer loadBalancer = this.getLoadBalancer(serviceId);

但是你会发现,一直跟进源码确实直接调用Spring底层的 getBean() 方法直接获取一个负载均衡策略的组件,所以在此之前,一定是将负载均衡策略相关的Bean已经注入到 Spring容器中了 具体的实现就在 org.springframework.cloud.netflix.ribbon.RibbonClientConfiguration.class

@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);
}

然后根据不同的负载均衡策略选择一个服务

//根据负载均衡策略选择一个服务
Server server = this.getServer(loadBalancer, hint);
//最后根据不同的IRule规则选择其中的一个服务返回
rule.choose(key);

5: 如何从注册中心获取所有服务实例列表

记得之前默认加载了一个new ZoneAwareLoadBalancer()的负载均衡组件,在该类的构造方法中调用了父类 DynamicServerListLoadBalancer.class的构造函数

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();
    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());
}
public void enableAndInitLearnNewServersFeature() {
    LOGGER.info("Using serverListUpdater {}", serverListUpdater.getClass().getSimpleName());
    //进入
    serverListUpdater.start(updateAction);
}
@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);
                }
            }
        };
        
        //每隔一段时间就会执行updateAction.doUpdate();方法去更新本地服务列表缓存
        scheduledFuture = getRefreshExecutor().scheduleWithFixedDelay(
                wrapperRunnable,
                initialDelayMs,
                refreshIntervalMs,
                TimeUnit.MILLISECONDS
        );
    } else {
        logger.info("Already active, no-op");
    }
}

updateAction.doUpdate();

public void updateListOfServers() {
    List<T> servers = new ArrayList();
    if (this.serverListImpl != null) {
        //从注册中心拿到所有的服务实例
        servers = this.serverListImpl.getUpdatedListOfServers();
        LOGGER.debug("List of Servers for {} obtained from Discovery client: {}", this.getIdentifier(), servers);
        if (this.filter != null) {
            servers = this.filter.getFilteredListOfServers((List)servers);
            LOGGER.debug("Filtered List of Servers for {} obtained from Discovery client: {}", this.getIdentifier(), servers);
        }
    }
    //更新本地缓存
    this.updateAllServerList((List)servers);
}

6:总结

  • 1:Ribbon会拿到所有被@LoadBalanced标注的RestTemplate集合
  • 2:会给每一个RestTemplate设置一个拦截器
  • 3:在拦截器中Ribbon会获取所有注册实例
  • 4:根据负载均衡策略选择一个服实例
  • 5:拿到服务的IP+端口,最后通过RestTemplate发起服务调用