Spring Cloud学习——客户端负载均衡:Ribbon

Sping Cloud Ribbon是一个基于HTTP和TCP的客户端负载均衡工具,它基于Netfix Ribbon实现。通过Spring Cloud的封装,可以让我们轻松地将面向服务的Rest模板请求自动转换成客户端负载均衡的服务调用。

RestTemplate和Ribbon相结合

Ribbon在Netflix组件是非常重要的一个组件,在Zuul中使用Ribbon做负载均衡,以及Feign组件的结合等。在Spring Cloud 中,作为开发中,做的最多的可能是将RestTemplate和Ribbon相结合,你可能会这样写,只需要在RestTemplate的bean上面加入@LoadBalanced注解即可在使用RestTemplate发送HTTP请求时,自动实现负载均衡调用

@LoadBalanced
@Bean
public RestTemplate restTemplate(){
    return new RestTemplate();
}

由此可见,主要的实现应该就在@LoadBalanced上,那我们就来分析下其源码。

源码解析

从@LoadBalaced注解源码的注释上可以知道,该注解用来给RestTemplate做标记,以使用负载均衡的客户端(LoadBalancerClient)来配置它。

通过搜索LoadBalancerClient可以发现,这是Spring Cloud中定义的一个接口。

public interface LoadBalancerClient extends ServiceInstanceChooser {
	ServiceInstance choose(String var1);
	
    <T> T execute(String var1, LoadBalancerRequest<T> var2) throws IOException;

    <T> T execute(String var1, ServiceInstance var2, LoadBalancerRequest<T> var3) throws IOException;

    URI reconstructURI(ServiceInstance var1, URI var2);
}

从该接口中,我们可以通过定义的抽象方法来了解客户端负载均衡器中应该具备的能力。

  • ServiceInstance choose(String var1): 根据传入的服务名serviceId,从负载均衡器中挑选一个对应服务的实例。
  • ServiceInstance choose(String var1): 使用从负载均衡器中挑选出的服务实例来执行请求内容。
  • URI reconstructURI(ServiceInstance var1, URI var2): 为系统构建一个合适的host:post形式的URI。

LoadBalancerAutoConfiguration为实现客户端负载均衡器的自动化配置类。通过查看源码,我们可以验证这一点:

@Configuration
@ConditionalOnClass({RestTemplate.class})
@ConditionalOnBean({LoadBalancerClient.class})
@EnableConfigurationProperties({LoadBalancerRetryProperties.class})
public class LoadBalancerAutoConfiguration {
	// 1.@AutoWired也会自动装载集合类list,会将合适的RestTemplate添加到restTemplates中
    // 而至于加载哪些RestTemplate,就是标注了@LoadBalanced的RestTemplate
    // 上面我们看到@LoadBalanced有一个@Qualifier就是特殊标注的含义,所以普通的没有添加@LoadBalanced
    // 则不会被添加到restTemplates中的
    @LoadBalanced
    @Autowired(
        required = false
    )
    private List<RestTemplate> restTemplates = Collections.emptyList();
    @Autowired(
        required = false
    )
    private List<LoadBalancerRequestTransformer> transformers = Collections.emptyList();

    public LoadBalancerAutoConfiguration() {
    }

	// 2.SmartInitializingSingleton接口的实现类会在项目初始化之后被调用其afterSingletonsInstantiated方法
    @Bean
    public SmartInitializingSingleton loadBalancedRestTemplateInitializer(final List<RestTemplateCustomizer> customizers) {
        return new SmartInitializingSingleton() {
            public void afterSingletonsInstantiated() {
                Iterator var1 = LoadBalancerAutoConfiguration.this.restTemplates.iterator();

                while(var1.hasNext()) {
                    RestTemplate restTemplate = (RestTemplate)var1.next();
                    Iterator var3 = customizers.iterator();

                    while(var3.hasNext()) {
                        RestTemplateCustomizer customizer = (RestTemplateCustomizer)var3.next();
                        customizer.customize(restTemplate);
                    }
                }

            }
        };
    }
	// 3.LoadBalancerRequestFactory被创建
    @Bean
    @ConditionalOnMissingBean
    public LoadBalancerRequestFactory loadBalancerRequestFactory(LoadBalancerClient loadBalancerClient) {
        return new LoadBalancerRequestFactory(loadBalancerClient, this.transformers);
    }

    @Configuration
    @ConditionalOnClass({RetryTemplate.class})
    public static class RetryInterceptorAutoConfiguration {
        public RetryInterceptorAutoConfiguration() {
        }

        @Bean
        @ConditionalOnMissingBean
        public RetryLoadBalancerInterceptor ribbonInterceptor(LoadBalancerClient loadBalancerClient, LoadBalancerRetryProperties properties, LoadBalancedRetryPolicyFactory lbRetryPolicyFactory, LoadBalancerRequestFactory requestFactory) {
            return new RetryLoadBalancerInterceptor(loadBalancerClient, properties, lbRetryPolicyFactory, requestFactory);
        }

        @Bean
        @ConditionalOnMissingBean
        public RestTemplateCustomizer restTemplateCustomizer(final RetryLoadBalancerInterceptor loadBalancerInterceptor) {
            return new RestTemplateCustomizer() {
                public void customize(RestTemplate restTemplate) {
                    List<ClientHttpRequestInterceptor> list = new ArrayList(restTemplate.getInterceptors());
                    list.add(loadBalancerInterceptor);
                    restTemplate.setInterceptors(list);
                }
            };
        }
    }

    @Configuration
    @ConditionalOnClass({RetryTemplate.class})
    public static class RetryAutoConfiguration {
        public RetryAutoConfiguration() {
        }

        @Bean
        public RetryTemplate retryTemplate() {
            RetryTemplate template = new RetryTemplate();
            template.setThrowLastExceptionOnExhausted(true);
            return template;
        }

        @Bean
        @ConditionalOnMissingBean
        public LoadBalancedRetryPolicyFactory loadBalancedRetryPolicyFactory() {
            return new NeverRetryFactory();
        }
    }

    @Configuration
    @ConditionalOnMissingClass({"org.springframework.retry.support.RetryTemplate"})
    static class LoadBalancerInterceptorConfig {
        LoadBalancerInterceptorConfig() {
        }
        
		//4、创建拦截器 ,将LoadBalancerClient接口的实现类和3方法中创建的LoadBalancerRequestFactory
        // 注入到该方法中,同时成为LoadBalancerInterceptor的参数
        @Bean
        public LoadBalancerInterceptor ribbonInterceptor(LoadBalancerClient loadBalancerClient, LoadBalancerRequestFactory requestFactory) {
            return new LoadBalancerInterceptor(loadBalancerClient, requestFactory);
        }
		
		//5、给RestTemplate增加LoadBalancerInterceptor,方法4中创建的LoadBalancerInterceptor会被作为方法参数注入进来
        @Bean
        @ConditionalOnMissingBean
        public RestTemplateCustomizer restTemplateCustomizer(final LoadBalancerInterceptor loadBalancerInterceptor) {
            return new RestTemplateCustomizer() {
                public void customize(RestTemplate restTemplate) {
                    List<ClientHttpRequestInterceptor> list = new ArrayList(restTemplate.getInterceptors());
                    list.add(loadBalancerInterceptor);
                    restTemplate.setInterceptors(list);
                }
            };
        }
    }
}

从LoadBalancerAutoConfiguration类头上的注解可以知道,Ribbon实现的负载均衡自动化配置需要满足下面两个条件:

  • @ConditionalOnClass({RestTemplate.class}): RestTemplate类必须存在于当前工程的环境中。
  • @ConditionalOnBean({LoadBalancerClient.class}): 在Spring的Bean工程中必须有LoadBalancerClient的实现Bean。

总结:经过以上的一堆注释可知,该类的主要作用就是给添加了@LoadBalanced注解的RestTemplate类,添加拦截器LoadBalancerInterceptor,该拦截器拦截到请求后将请求重新处理,就在这个拦截器中实现了负载均衡的相关功能。

LoadBalancerInterceptor.intercept()执行负载均衡拦截器方法
public ClientHttpResponse intercept(final HttpRequest request, final byte[] body,
                                    final ClientHttpRequestExecution execution) throws IOException {
    final URI originalUri = request.getURI();
    String serviceName = originalUri.getHost();
    Assert.state(serviceName != null, "Request URI does not contain a valid hostname: " + originalUri);
    // 真正执行的方法
    // private LoadBalancerClient loadBalancer; LoadBalancerClient默认实现类为RibbonLoadBalancerClient
    return this.loadBalancer.execute(serviceName, requestFactory.createRequest(request, body, execution));
}
 
// RibbonLoadBalancerClient.execute()
public <T> T execute(String serviceId, LoadBalancerRequest<T> request) throws IOException {
    // 1.根据用户请求的serviceId来获取具体的LoadBalanced
    ILoadBalancer loadBalancer = getLoadBalancer(serviceId);
 
    // 2.获取具体的server(也就是定位到哪台服务器的哪个端口号的具体服务信息)
    Server server = getServer(loadBalancer);
    if (server == null) {
        throw new IllegalStateException("No instances available for " + serviceId);
    }
    RibbonServer ribbonServer = new RibbonServer(serviceId, server, isSecure(server,
                                                                             serviceId), serverIntrospector(serviceId).getMetadata(server));
 
    // 3.执行HTTP请求
    return execute(serviceId, ribbonServer, request);

注意:通过这个方法的分析可以看到,里面通过一系列的算法根据用户输入的serviceId(也就是服务名)来获取到具体的服务所在host、port,然后重新封装HTTP请求,最后执行该HTTP请求即可。

下面逐个方法来分析:

getLoadBalancer()

protected ILoadBalancer getLoadBalancer(String serviceId) {
    return this.clientFactory.getLoadBalancer(serviceId);
}
 
// SpringClientFactory.getLoadBalancer()
public ILoadBalancer getLoadBalancer(String name) {
    return getInstance(name, ILoadBalancer.class);
}
 
// SpringClientFactory.getInstance()
public <C> C getInstance(String name, Class<C> type) {
    C instance = super.getInstance(name, type);
    if (instance != null) {
        return instance;
    }
    IClientConfig config = getInstance(name, IClientConfig.class);
    return instantiateWithConfig(getContext(name), type, config);
}
 
// NamedContextFactory.getInstance()
public <T> T getInstance(String name, Class<T> type) {
    AnnotationConfigApplicationContext context = getContext(name);
    if (BeanFactoryUtils.beanNamesForTypeIncludingAncestors(context,
                                                            type).length > 0) {
        // 主要就是这句,从容器中获取ILoadBalancer的实现,目前默认实现为
        // ZoneAwareLoadBalancer
        return context.getBean(type);
    }
    return null;
}

总结:通过上述一连串的方法调用可知,在最终是从容器中获取ILoadBalancer的实现,目前默认实现为ZoneAwareLoadBalancer。

getServer(loadBalancer)

protected Server getServer(ILoadBalancer loadBalancer) {
    if (loadBalancer == null) {
        return null;
    }
    // 具体执行为ZoneAwareLoadBalancer.chooseServer()
    return loadBalancer.chooseServer("default"); // TODO: better handling of key
}
 
// ZoneAwareLoadBalancer.chooseServer()
public Server chooseServer(Object key) {
    // 1.由于笔者测试的server,可用的zone为1个,所以会直接走super.chooseServer()
    if (!ENABLED.get() || getLoadBalancerStats().getAvailableZones().size() <= 1) {
        logger.debug("Zone aware logic disabled or there is only one zone");
        return super.chooseServer(key);
    }
    //如果是多region,则会走下面的方法,暂时注释掉
    ...
}
        
    //BaseLoadBalancer.chooseServer()
   public Server chooseServer(Object key) {
        if (counter == null) {
            counter = createCounter();
        }
        counter.increment();
        if (rule == null) {
            return null;
        } else {
            try {
                // rule为ZoneAvoidanceRule
                return rule.choose(key);
            } catch (Exception e) {
                logger.warn("LoadBalancer [{}]:  Error choosing server for key {}", name, key, e);
                return null;
            }
        }
    }
        
//PredicateBasedRule.choose(key)
    public Server choose(Object key) {
        ILoadBalancer lb = getLoadBalancer();
        // 重点在这里,从所有的server中根据对应的rule来获取一个具体的server
        Optional<Server> server = getPredicate().chooseRoundRobinAfterFiltering(lb.getAllServers(), key);
        if (server.isPresent()) {
            return server.get();
        } else {
            return null;
        }       
    }

总结:getServer()的主要功能就是根据具体的rule来选择特定的Server,重要的实现实际都在这个方法里。

RibbonLoadBalancerClient.execute(serviceId, ribbonServer, request)执行HTTP请求

public <T> T execute(String serviceId, ServiceInstance serviceInstance, LoadBalancerRequest<T> request) throws IOException {
    ...
    RibbonLoadBalancerContext context = this.clientFactory
        .getLoadBalancerContext(serviceId);
    RibbonStatsRecorder statsRecorder = new RibbonStatsRecorder(context, server);
 
    try {
        // 核心方法在这里,是一个回调方法,
        // 具体就是回调LoadBalancerRequestFactory.createRequest()中的apply()方法
        T returnVal = request.apply(serviceInstance);
        statsRecorder.recordStats(returnVal);
        return returnVal;
    }
    ...
}
 
    // 回调方法
    public LoadBalancerRequest<ClientHttpResponse> createRequest(final HttpRequest request,
                                                                 final byte[] body, final ClientHttpRequestExecution execution) {
        return new LoadBalancerRequest<ClientHttpResponse>() {
 
            @Override
            // 回调方法在这里
            public ClientHttpResponse apply(final ServiceInstance instance)
            throws Exception {
                HttpRequest serviceRequest = new ServiceRequestWrapper(request, instance, loadBalancer);
                if (transformers != null) {
                    for (LoadBalancerRequestTransformer transformer : transformers) {
                        serviceRequest = transformer.transformRequest(serviceRequest, instance);
                    }
                }
                // 真正要执行的方法
                return execution.execute(serviceRequest, body);
            }
 
        };
    }
 
    //InterceptingRequestExecution.execute()
    public ClientHttpResponse execute(HttpRequest request, byte[] body) throws IOException {
        if (this.iterator.hasNext()) {
            ClientHttpRequestInterceptor nextInterceptor = this.iterator.next();
            return nextInterceptor.intercept(request, body, this);
        }
        // 注意:此时已经没有iterator,直接执行request请求
        else {
            // 1.根据URI获得请求,并封装头部
            ClientHttpRequest delegate = requestFactory.createRequest(request.getURI(), request.getMethod());
            for (Map.Entry<String, List<String>> entry : request.getHeaders().entrySet()) {
                List<String> values = entry.getValue();
                for (String value : values) {
                    delegate.getHeaders().add(entry.getKey(), value);
                }
            }
            if (body.length > 0) {
                StreamUtils.copy(body, delegate.getBody());
            }
            // 2.本质就是对HttpURLConnection的执行
            return delegate.execute();
        }
    }
}

到此为止,URI的请求基本已经结束了。

总结

1)用户创建RestTemplate

2)添加了ribbon依赖后,会在项目启动的时候自动往RestTemplate中添加LoadBalancerInterceptor拦截器

3)用户根据RestTemplate发起请求时,会将请求转发到LoadBalancerInterceptor去执行,该拦截器会根据指定的负载均衡方式获取该次请求对应的应用服务端IP、port

4)根据获取到的IP、port重新封装请求,发送HTTP请求,返回具体响应。

负载均衡器

通过之前的分析,我们已经对Sping Cloud 如何使用Ribbon有了基本的了解。虽然Spring Cloud 中定义了LoadBalancerClient 作为负载均衡器的通用接口,并且针对Ribbon实现了RibbonLoadBalancerClient,但是它再具体实现客户端负载均衡时,是通过Ribbon的ILoadBalancer接口实现的。下面我们根据ILoadBalancer接口的实现类逐个看看它是如何实现客户端负载均衡的。

DynamicServerListLoadBalancer

它的继承类为BaseLoadBalancer,它的实现类为DynamicServerListLoadBalancer,这三者之间的关系如下:
在这里插入图片描述
查看上述三个类的源码,可用发现,配置以下信息,IClientConfig、IRule、IPing、ServerList、ServerListFilter和ILoadBalancer,查看BaseLoadBalancer类,它默认的情况下,实现了以下配置:

  • IClientConfig ribbonClientConfig: DefaultClientConfigImpl配置
  • IRule ribbonRule: RoundRobinRule 路由策略
  • IPing ribbonPing: DummyPing
  • ServerList ribbonServerList: ConfigurationBasedServerList
  • ServerListFilter ribbonServerListFilter: ZonePreferenceServerListFilter
  • ILoadBalancer ribbonLoadBalancer: ZoneAwareLoadBalancer

IClientConfig: 用于对客户端或者负载均衡的配置,它的默认实现类为DefaultClientConfigImpl。

IRule: 用于复杂均衡的策略,它有三个方法,其中choose()是根据key 来获取server,setLoadBalancer()和getLoadBalancer()是用来设置和获取ILoadBalancer的,它的源码如下:

public interface IRule{

    public Server choose(Object key);
    
    public void setLoadBalancer(ILoadBalancer lb);
    
    public ILoadBalancer getLoadBalancer();    
}

IRule有很多默认的实现类,这些实现类根据不同的算法和逻辑来处理负载均衡。Ribbon实现的IRule有一下。在大多数情况下,这些默认的实现类是可以满足需求的,如果有特性的需求,可以自己实现。

  • BestAvailableRule 选择最小请求数

  • ClientConfigEnabledRoundRobinRule 轮询

  • RandomRule 随机选择一个server

  • RoundRobinRule 轮询选择server

  • RetryRule 根据轮询的方式重试

  • WeightedResponseTimeRule 根据响应时间去分配一个weight ,weight越低,被选择的可能性就越低

  • ZoneAvoidanceRule 根据server的zone区域和可用性来轮询选择

在这里插入图片描述
IPing是用来想server发生"ping",来判断该server是否有响应,从而判断该server是否可用。它有一个isAlive()方法,它的源码如下:

public interface IPing {
    public boolean isAlive(Server server);
}

IPing的实现类有PingUrl、PingConstant、NoOpPing、DummyPing和NIWSDiscoveryPing。它门之间的关系如下:

在这里插入图片描述

  • PingUrl 真实的去ping 某个url,判断其是否alive
  • PingConstant 固定返回某服务是否可用,默认返回true,即可用
  • NoOpPing 不去ping,直接返回true,即可用。
  • DummyPing 直接返回true,并实现了initWithNiwsConfig方法。
  • NIWSDiscoveryPing,根据DiscoveryEnabledServer的InstanceInfo的InstanceStatus去判断,如果为InstanceStatus.UP,则为可用,否则不可用。

ServerList: 是定义获取所有的server的注册列表信息的接口,它的代码如下:

public interface ServerList<T extends Server> {

    public List<T> getInitialListOfServers();
    public List<T> getUpdatedListOfServers();   

}

ServerListFilter: 定于了可根据配置去过滤或者根据特性动态获取符合条件的server列表的方法,代码如下:

public interface ServerListFilter<T extends Server> {
    public List<T> getFilteredListOfServers(List<T> servers);
}

总结: 负载均衡器是从EurekaClient获取服务信息,并根据IRule去路由,并且根据IPing去判断服务的可用性。LoadBalancerClient是在初始化的时候,会向Eureka回去服务注册列表,并且向通过10s一次向EurekaClient发送“ping”,来判断服务的可用性,如果服务的可用性发生了改变或者服务数量和之前的不一致,则更新或者重新拉取。LoadBalancerClient有了这些服务注册列表,就可以根据具体的IRule来进行负载均衡。

总结

综上所述,Ribbon的负载均衡,主要通过LoadBalancerClient来实现的,而LoadBalancerClient具体交给了ILoadBalancer来处理,ILoadBalancer通过配置IRule、IPing等信息,并向EurekaClient获取注册列表的信息,并默认10秒一次向EurekaClient发送“ping”,进而检查是否更新服务列表,最后,得到注册列表后,ILoadBalancer根据IRule的策略进行负载均衡。

猜你喜欢

转载自blog.csdn.net/hxyascx/article/details/88097172