负载均衡 Ribbon 学习笔记

1. Ribbon原理

1.1 主要模块

  • ribbon:在其他功能区模块和Hystrix之上集成负载平衡,容错,缓存/批处理的API

  • ribbon-core:主要定义通用的调用抽象,ribbon的client处理请求并返回响应

  • ribbon-loadbalancer:负载均衡,宏观上效果是将请求流量均衡的负载到提供服务器上,对于每次请求通过计算获取目标服务器地址

  • ribbon-httpclient:负载均衡器集成的Apache HttpClient之上的REST客户端

  • ribbon-eureka:使用Eureka客户端为注册中心提供动态服务器列表的API

1.2 自动化配置

LoadBalancerAutoConfiguration

// ......

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

@Bean
public SmartInitializingSingleton loadBalancedRestTemplateInitializer(
      final List<RestTemplateCustomizer> customizers) {
   	  return new SmartInitializingSingleton() {
      @Override
      public void afterSingletonsInstantiated() {
         for (RestTemplate restTemplate : 
              LoadBalancerAutoConfiguration.this.restTemplates) {
            for (RestTemplateCustomizer customizer : customizers) {
               customizer.customize(restTemplate);
            }
         }
      }
   };
}

@Configuration
@ConditionalOnMissingClass("org.springframework.retry.support.RetryTemplate")
static class LoadBalancerInterceptorConfig {
	// 拦截器配置
    @Bean
    public LoadBalancerInterceptor ribbonInterceptor(
        LoadBalancerClient loadBalancerClient,
        LoadBalancerRequestFactory requestFactory) {
        return new LoadBalancerInterceptor(loadBalancerClient, requestFactory);
    }
}

// ......

​ Ribbon实现负载均衡自动化配置条件

  • @ConditionalOnClass(RestTemplate.class): RestTemplate类必须存在于当前工程的环境中

  • @ConditionalOnBean(LoadBalancerClient.class): 在Spring的Bean工厂中需要有LoadBalancerClient

    自动化配置流程

  • 创建一个LoadBalancerInterceptor用于实现对客户端的发起请求时进行拦截,以实现客户端负载均衡

  • 创建一个RestTemplateCustomizer用于给RestTemplate增加LoadBalancerInterceptor拦截器

  • 维护一个被@LoadBalanced注解修饰的RestTemplate对象列表并初始化,通过调用RestTemplateCustomizer的实例来给需要客户端负载均衡的RestTemplate增加LoadBalancerInterceptor

1.3 拦截请求

LoadBalancerInterceptor 负载均衡拦截器

​ 当一个被@LoadBalanced注解修饰的RestTemplate对象发起一个HTTP请求的时候,会被LoadBalancerInterceptor拦截执行intercept方法,通过HttpRequest的URI对象中通过getHost获取服务名,然后调用execute方法根据服务名选择实例发起请求。

@Override
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, "......");
   // 选择服务实例发起请求
   return this.loadBalancer.execute(serviceName, 
          requestFactory.createRequest(request, body, execution));
}

1.4 负载均衡器

​ LoadBalancerClient只是一个接口,Ribbon提供了它的一个实现类RibbonLoadBalancerClient

@Override
public <T> T execute(String serviceId, LoadBalancerRequest<T> request) 
             throws IOException {
   // 根据服务Id获取负载均衡器
   ILoadBalancer loadBalancer = getLoadBalancer(serviceId);
   // 根据负载均衡器获取服务
   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));
   return execute(serviceId, ribbonServer, request);
}

​ 每个服务都会拥有一个SpringClientFactory用于创建负载均衡器ILoadBalancer的实现类,ILoadBalancer可以更具所有服务实例选择某一个服务。

​ 实现负载均衡逻辑的接口ILoadBalancer,其实现默认实现类为ZoneAwareLoadBalancer
负载均衡 Ribbon 学习笔记_第1张图片

public interface ILoadBalancer {
    //向负载均衡器中添加服务实例
    void addServers(List<Server> var1);

    //通过负载均衡策略从中选择一个服务实例
    Server chooseServer(Object var1);

    //标记一个服务的停止
    void markServerDown(Server var1);

    @Deprecated
    List<Server> getServerList(boolean var1);
	
    // 获取可用服务实例
    List<Server> getReachableServers();

    // 获取所有服务实例
    List<Server> getAllServers();
}

ZoneAwareLoadBalancer

public Server chooseServer(Object key) {
    // ......

    Set<String> availableZones = ZoneAvoidanceRule.getAvailableZones(
        						 zoneSnapshot, this.triggeringLoad.get(), 
        						 this.triggeringBlackoutPercentage.get());
    if (availableZones != null && availableZones.size() < zoneSnapshot.keySet().size()) 
    {
        // 会将服务分成几个区域
        String zone = ZoneAvoidanceRule.randomChooseZone(zoneSnapshot, availableZones);
        if (zone != null) {
            BaseLoadBalancer zoneLoadBalancer = this.getLoadBalancer(zone);
            server = zoneLoadBalancer.chooseServer(key);
        }
        if (server != null) {
            return server;
        } else {
            return super.chooseServer(key);
        }
    } else {
        return super.chooseServer(key);
    }
}

1.5 负载均衡策略

​ IRule是负载均衡策略的一个接口,默认使用RoundRobinRule实现最基本的常用的线性负载均衡策略

public Server choose(ILoadBalancer lb, Object key) {
    if (lb == null) {
        log.warn("no load balancer");
        return null;
    }

    Server server = null;
    int count = 0;
    while (server == null && count++ < 10) {
        // 可用服务列表
        List<Server> reachableServers = lb.getReachableServers();
        // 所有服务列表
        List<Server> allServers = lb.getAllServers();
        int upCount = reachableServers.size();
        int serverCount = allServers.size();

        if ((upCount == 0) || (serverCount == 0)) {
            log.warn("No up servers available from load balancer: " + lb);
            return null;
        }
		
        // 获取实例在List中的下标
        int nextServerIndex = incrementAndGetModulo(serverCount);
        server = allServers.get(nextServerIndex);

        if (server == null) {
            /* Transient. */
            Thread.yield();
            continue;
        }

        if (server.isAlive() && (server.isReadyToServe())) {
            return (server);
        }

        // Next.
        server = null;
    }
	// 尝试10次
    if (count >= 10) {
        log.warn("No available alive servers after 10 tries from load balancer: "
                + lb);
    }
    return server;
}

​ Ribbon支持根据不同的服务配置不同的负载均衡策略

​ 配置类

@Configuration
@ExcludeFromComponentScan
public class TestConfiguration1 {
    @Autowired
    private IClientConfig config;
    @Bean
    public IRule ribbonRule(IClientConfig config) { // 自定义为随机规则
        return new RandomRule();
    }
}

microservice-provider-user:          # 服务名
	ribbon:
    	# 自定义负载均衡策略
		NFLoadBalancerRuleClassName: com.netflix.loadbalancer.RandomRule 
		# 脱离Eureka需要提供服务地址
		listOfServers:localhost:8080,localhost:8081


1.6 发起请求

​ 选择完服务实例后执行apply访问服务,传入ServiceInstance

​ 提供服务实例需要的信息

public interface ServiceInstance {

   String getServiceId();

   String getHost();

   int getPort();

   boolean isSecure();

   URI getUri();

   Map<String, String> getMetadata();
}

将HttpRequest包装成ServiceRequestWrapper

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

// InterceptingClientHttpRequest,InterceptingRequestExecution 内部类
@Override
public ClientHttpResponse execute(HttpRequest request, final byte[] body) 
    throws IOException {
    if (this.iterator.hasNext()) {
        ClientHttpRequestInterceptor nextInterceptor = this.iterator.next();
        return nextInterceptor.intercept(request, body, this);
    }
    else {
        // 会调用RibbonHttpRequest重写的getURI()
        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) {
            if (delegate instanceof StreamingHttpOutputMessage) {
                StreamingHttpOutputMessage streamingOutputMessage = 
                    (StreamingHttpOutputMessage) delegate;
                streamingOutputMessage.setBody(
                    new StreamingHttpOutputMessage.Body() {
                        @Override
                        public void writeTo(final OutputStream outputStream) 
                            throws IOException {
                            StreamUtils.copy(body, outputStream);
                        }
                    });
            }
            else {
                StreamUtils.copy(body, delegate.getBody());
            }
        }
        return delegate.execute();
    }
}

最后经过一系列的包装会在SimpleBufferingClientHttpRrequest#executeInternal中完成访问

@Override
protected ClientHttpResponse executeInternal(HttpHeaders headers, byte[] bufferedOutput) throws IOException {
   addHeaders(this.connection, headers);
   // JDK <1.8 doesn't support getOutputStream with HTTP DELETE
   if (getMethod() == HttpMethod.DELETE && bufferedOutput.length == 0) {
      this.connection.setDoOutput(false);
   }
   if (this.connection.getDoOutput() && this.outputStreaming) {
      this.connection.setFixedLengthStreamingMode(bufferedOutput.length);
   }
   // 连接服务
   this.connection.connect();
   if (this.connection.getDoOutput()) {
      FileCopyUtils.copy(bufferedOutput, this.connection.getOutputStream());
   }
   else {
      // Immediately trigger the request in a no-output scenario as well
      this.connection.getResponseCode();
   }
   return new SimpleClientHttpResponse(this.connection);
}

2. Spring Cloud集成

2.1 依赖管理

<dependency>
   <groupId>org.springframework.cloudgroupId>
   <artifactId>spring-cloud-starter-ribbonartifactId>
dependency>

传递依赖

<dependency>
   <groupId>org.springframework.cloudgroupId>
   <artifactId>spring-cloud-starter-netflix-ribbonartifactId>
dependency>

<dependency>
    <groupId>org.springframework.cloudgroupId>
    <artifactId>spring-cloud-netflix-coreartifactId>
dependency>

<dependency>
    <groupId>com.netflix.ribbongroupId>
    <artifactId>ribbonartifactId>
dependency>

<dependency>
    <groupId>com.netflix.ribbongroupId>
    <artifactId>ribbon-coreartifactId>
dependency>

<dependency>
    <groupId>com.netflix.ribbongroupId>
    <artifactId>ribbon-httpclientartifactId>
dependency>

<dependency>
    <groupId>com.netflix.ribbongroupId>
    <artifactId>ribbon-loadbalancerartifactId>
dependency>

2.2 配置文件

这个和普通的微服务没有区别,也属于Eureka的客户端

spring.application.name=service-ribbon

server.port=8763

eureka.instance.prefer-ip-address=true
eureka.client.service-url.defaultZone=http://localhost:8761/eureka
feign.httpclient.connection-timeout=50000

# 调试超时被熔断使用
# hystrix.command.default.execution.isolation.thread.timeoutInMilliseconds=5000

2.3 启动类

最为主要的区别就是在RestTemplate加上了负载均衡@LoadBalanced支持

@SpringBootApplication
@EnableDiscoveryClient
public class ServiceRibbonApplication {

   public static void main(String[] args) {
      SpringApplication.run(ServiceRibbonApplication.class, args);
   }

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

2.4 RestTemplate

​ 其实RestTemplate是Spring Web中就已经提供了,并不是ribbon中的类,是用@LoadBalanced注解通过负载均衡客户端LoadBalancerClient来配置的。

3. 总结

主要流程:

​ 首先是要申明一个负载均衡类RestTemplate,它主要是通过我们的在配置文件中配置路径/服务映射关系(默认为服务名对应的Eureka中心的地址),在发起服务请求的时候拦截该请求(通过注入LoadBalancerInterceptor),然后根据服务编号获取服务主机等信息,然后根据负载均衡策略获取具体的服务所在host、port,在此期间会进行服务的获取,判断可用性,服务更新,重试机制等,最后重新封装HTTP请求,最后执行包装HTTP请求返回的结果。

你可能感兴趣的:(微服务,微服务,负载均衡,Ribbon)