Ribbon负载均衡分析

男娘i 2023-01-23 04:57 82阅读 0赞

Ribbon使用

ribbon在使用上非常简单,仅仅只需要在配置类上加入配置即可

  1. @Bean
  2. @LoadBalanced
  3. public RestTemplate restTemplate(){
  4. return new RestTemplate();
  5. }

调用时,直接使用在eureka中注册的服务名进行调用,就可以由ribbon来进行负载均衡了

  1. @GetMapping("/checkAndBegin/{userId}")
  2. public Integer findResumeOpenStateEureka(@PathVariable Long userId) {
  3. // List<ServiceInstance> list = discoveryClient.getInstances("lagou-service-resume");
  4. // ServiceInstance serviceInstance = list.get(0);
  5. // String host = serviceInstance.getHost();
  6. // int port = serviceInstance.getPort();
  7. String url = "http://zhao-service-resume/resume/openstate/"+userId;
  8. System.out.println("从eureka中获取了请求地址"+url);
  9. Integer forObject =
  10. restTemplate.getForObject(url, Integer.class);
  11. return forObject;
  12. }

根据要求,zhao-service-resume项目开启多个,并打印请求信息,即可发现负载均衡已经实现 另外目前Ribbon的内置负载均衡策略 file 目前默认使用的是随机负载均衡RandomRule,默认全局生效,但是可以针对不同的调用服务设置不同的负载均衡策略

  1. zhao-service-resume:
  2. ribbon:
  3. NFLoadBalancerRuleClassName:
  4. com.netflix.loadbalancer.RandomRule #负载策略调整

同时,可以自定负载均衡策略并配置

Ribbon源码分析

一般而言,自动装配类就是加载配置的入口。

  1. @Configuration
  2. @Conditional(RibbonAutoConfiguration.RibbonClassesConditions.class)
  3. @RibbonClients
  4. @AutoConfigureAfter(name = "org.springframework.cloud.netflix.eureka.EurekaClientAutoConfiguration")
  5. @AutoConfigureBefore({LoadBalancerAutoConfiguration.class, AsyncLoadBalancerAutoConfiguration.class})
  6. @EnableConfigurationProperties({RibbonEagerLoadProperties.class, ServerIntrospectorProperties.class})
  7. public class RibbonAutoConfiguration {
  8. }

通过上述配置,我们可以先看看LoadBalancerAutoConfiguration的具体内容

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

此处将自动注入添加了@LoadBalanced注解的RestTemplate对象 同时还注入了一个RestTemplate的定制器RestTemplateCustomizer

  1. @Bean
  2. public SmartInitializingSingleton loadBalancedRestTemplateInitializerDeprecated(
  3. final ObjectProvider<List<RestTemplateCustomizer>> restTemplateCustomizers) {
  4. return () -> restTemplateCustomizers.ifAvailable(customizers -> {
  5. for (RestTemplate restTemplate : LoadBalancerAutoConfiguration.this.restTemplates) {
  6. for (RestTemplateCustomizer customizer : customizers) {
  7. customizer.customize(restTemplate);
  8. }
  9. }
  10. });
  11. }

继续进入RestTemplateCustomizer的定制器代码,我们发现在定制器中加入了一个拦截器

  1. @Configuration
  2. @ConditionalOnMissingClass("org.springframework.retry.support.RetryTemplate")
  3. static class LoadBalancerInterceptorConfig {
  4. @Bean
  5. public LoadBalancerInterceptor ribbonInterceptor(
  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. }

ClientHttpRequestInterceptor的拦截具体内容为,根据获取到的请求路径和请求地址进行负载均衡

  1. @Override
  2. public ClientHttpResponse intercept(final HttpRequest request, final byte[] body,
  3. final ClientHttpRequestExecution execution) throws IOException {
  4. final URI originalUri = request.getURI();
  5. String serviceName = originalUri.getHost();
  6. Assert.state(serviceName != null, "Request URI does not contain a valid hostname: " + originalUri);
  7. return this.loadBalancer.execute(serviceName, requestFactory.createRequest(request, body, execution));
  8. }

执行负载均衡的代码

  1. public <T> T execute(String serviceId, LoadBalancerRequest<T> request, Object hint) throws IOException {
  2. ILoadBalancer loadBalancer = getLoadBalancer(serviceId);
  3. Server server = getServer(loadBalancer, hint);
  4. if (server == null) {
  5. throw new IllegalStateException("No instances available for " + serviceId);
  6. }
  7. RibbonServer ribbonServer = new RibbonServer(serviceId, server, isSecure(server,
  8. serviceId), serverIntrospector(serviceId).getMetadata(server));
  9. return execute(serviceId, ribbonServer, request);
  10. }

从这段代码可以看出,第一行根据配置,选出相应的负载均衡策略。第二行就是根据相应的负载均衡策略选择一个服务端进行服务请求,达到负载均衡的目的 最后在BaseLoadBalancer中执行了根据不同的策略选择服务的操作

  1. public Server chooseServer(Object key) {
  2. if (counter == null) {
  3. counter = createCounter();
  4. }
  5. counter.increment();
  6. if (rule == null) {
  7. return null;
  8. } else {
  9. try {
  10. return rule.choose(key);
  11. } catch (Exception e) {
  12. logger.warn("LoadBalancer [{}]: Error choosing server for key {}", name, key, e);
  13. return null;
  14. }
  15. }
  16. }

考虑完了上面的主逻辑之后,还有一个问题,就是服务列表是什么时候获取到的。 在RibbonAutoConfigration中注入了SpringClientFactory,而SpringClientFactory又注入了RibbonClientConfiguration

  1. public SpringClientFactory() {
  2. super(RibbonClientConfiguration.class, NAMESPACE, "ribbon.client.name");
  3. }

RibbonClientConfiguration中进行了注入客户端操作的相关操作,包括负载均衡策略,客户端配置,服务列表等,其中最重要的就是如何获取和更新服务列表

  1. @ConditionalOnMissingBean
  2. @SuppressWarnings("unchecked")
  3. public ServerList<Server> ribbonServerList(IClientConfig config) {
  4. if (this.propertiesFactory.isSet(ServerList.class, name)) {
  5. return this.propertiesFactory.get(ServerList.class, config, name);
  6. }
  7. ConfigurationBasedServerList serverList = new ConfigurationBasedServerList();
  8. serverList.initWithNiwsConfig(config);
  9. return serverList;
  10. }
  11. @Bean
  12. @ConditionalOnMissingBean
  13. public ServerListUpdater ribbonServerListUpdater(IClientConfig config) {
  14. return new PollingServerListUpdater(config);
  15. }
  16. @Bean
  17. @ConditionalOnMissingBean
  18. public ILoadBalancer ribbonLoadBalancer(IClientConfig config,
  19. ServerList<Server> serverList, ServerListFilter<Server> serverListFilter,
  20. IRule rule, IPing ping, ServerListUpdater serverListUpdater) {
  21. if (this.propertiesFactory.isSet(ILoadBalancer.class, name)) {
  22. return this.propertiesFactory.get(ILoadBalancer.class, config, name);
  23. }
  24. return new ZoneAwareLoadBalancer<>(config, rule, ping, serverList,
  25. serverListFilter, serverListUpdater);
  26. }

在ribbonList方法中并未有获取serverList的操作,在ribbonLoadBalancer中进行了使用,那么究竟怎么一回事呢?实际上是在ZoneAwareLoadBalancer的父类DynamicServerListLoadBalancer中进行了重新的赋值并且执行了定时任务进行更新。

  1. void restOfInit(IClientConfig clientConfig) {
  2. boolean primeConnection = this.isEnablePrimingConnections();
  3. // turn this off to avoid duplicated asynchronous priming done in BaseLoadBalancer.setServerList()
  4. this.setEnablePrimingConnections(false);
  5. enableAndInitLearnNewServersFeature();
  6. updateListOfServers();
  7. if (primeConnection && this.getPrimeConnections() != null) {
  8. this.getPrimeConnections()
  9. .primeConnections(getReachableServers());
  10. }
  11. this.setEnablePrimingConnections(primeConnection);
  12. LOGGER.info("DynamicServerListLoadBalancer for client {} initialized: {}", clientConfig.getClientName(), this.toString());
  13. }

首先通过updateAction.doUpdate();更新,然后通过getRefreshExecutor()进行获取

  1. @Override
  2. public synchronized void start(final UpdateAction updateAction) {
  3. if (isActive.compareAndSet(false, true)) {
  4. final Runnable wrapperRunnable = new Runnable() {
  5. @Override
  6. public void run() {
  7. if (!isActive.get()) {
  8. if (scheduledFuture != null) {
  9. scheduledFuture.cancel(true);
  10. }
  11. return;
  12. }
  13. try {
  14. updateAction.doUpdate();
  15. lastUpdated = System.currentTimeMillis();
  16. } catch (Exception e) {
  17. logger.warn("Failed one update cycle", e);
  18. }
  19. }
  20. };
  21. scheduledFuture = getRefreshExecutor().scheduleWithFixedDelay(
  22. wrapperRunnable,
  23. initialDelayMs,
  24. refreshIntervalMs,
  25. TimeUnit.MILLISECONDS
  26. );
  27. } else {
  28. logger.info("Already active, no-op");
  29. }
  30. }

欢迎搜索关注本人的公众号【微瞰技术】,以及总结的分类面试题https://github.com/zhendiao/JavaInterview

file

发表评论

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

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

相关阅读

    相关 负载均衡Ribbon

    1. Feign 默认集成了 Ribbon Feign 是一个声明式的伪 Http 客户端,它使得写 Http 客户端变得更简单。使用 Feign,只需要创建一个接口并注

    相关 负载均衡---ribbon

    Ribbon:提供云端负载均衡,有多种负载均衡策略可供选择,可配合服务发现和断路器使用。 上一篇简单讲解了eureka的使用,这一篇文章基于上一篇的基础上,讲一下spring

    相关 Ribbon负载均衡

    1. 集中式负载均衡 > 在客户端和服务端之间使用独立的负载均衡设施(可以是硬件,如F5, 也可以是软件,如nginx、LVS等), 由该设施负责把访问请求通过某种策略转发