Loading

负载均衡Ribbon高级

1 负载均衡概述

1.1 什么是负载均衡?

  • 在搭建网站的时候,如果单节点的web服务性能和可靠性都无法达到要求;或者是在使用外网服务的时候,经常担心被人攻破,一不小心就会有打开外网端口的请求,通常这个时候加入负载均衡就能有效解决服务问题。
  • 负载均衡是一种基础的网络服务,其原理是通过运行在前面的负载均衡服务,按照指定的负载均衡算法,将流量分配到后端服务集群上,从而为系统提供并行扩展的能力。
  • 负载均衡的应用场景包括流量包、转发规则以及后端服务,由于该服务有内外网个例、健康检查等功能,能够有效提供系统的安全性和可用性。

什么是负载均衡

1.2 客户端负载均衡和服务端负载均衡

1.2.1 客户端负载均衡

  • 客户端会有一个服务器地址列表,在发送请求前通过负载均衡算法选择一个服务器,进行访问;即在客户端进行负载均衡算法分配。

  • 典型应用:Ribbon是客户端负载均衡。

1.2.2 服务端负载均衡

  • 先发送请求到负载均衡服务器或软件,然后通过负载均衡算法,在多个服务器之间选择一个进行访问;即在服务器端进行负载均衡算法分配。

  • 典型应用:

  • 1️⃣硬件:F5等。

  • 2️⃣软件:Nginx等。

2 基于Ribbon实现负载均衡

2.1 搭建环境

  • 准备两个商品微服务(端口分别是9001和9011)让其注册到Eureka集群中。

  • 商品微服务9001的application.yml:

server:
  port: 9001 # 微服务的端口号

spring:
  application:
    name: service-product # 微服务的名称
  datasource:
    url: jdbc:mysql://192.168.1.57:3306/test?useUnicode=true&characterEncoding=UTF-8&autoReconnect=true&useSSL=false&serverTimezone=GMT%2B8&allowPublicKeyRetrieval=true
    driver-class-name: com.mysql.cj.jdbc.Driver
    username: root
    password: 123456
  jpa:
    generate-ddl: true
    show-sql: true
    open-in-view: true
    database: mysql

# 配置 eureka
eureka:
  instance:
    # 主机名称:服务名称修改,其实就是向eureka server中注册的实例id
    instance-id: service-product:9001
    # 显示IP信息
    prefer-ip-address: true
  client:
    service-url: # 此处修改为 Eureka Server的集群地址
      defaultZone: http://eureka7001.com:7001/eureka/,http://eureka7002.com:7002/eureka/,http://eureka7003.com:7003/eureka/


# 微服务info内容详细信息
info:
  app.name: xxx
  company.name: xxx
  build.artifactId: $project.artifactId$
  build.version: $project.version$
  • 商品微服务9011的application.yml:
server:
  port: 9011 # 微服务的端口号

spring:
  application:
    name: service-product # 微服务的名称
  datasource:
    url: jdbc:mysql://192.168.1.57:3306/test?useUnicode=true&characterEncoding=UTF-8&autoReconnect=true&useSSL=false&serverTimezone=GMT%2B8&allowPublicKeyRetrieval=true
    driver-class-name: com.mysql.cj.jdbc.Driver
    username: root
    password: 123456
  jpa:
    generate-ddl: true
    show-sql: true
    open-in-view: true
    database: mysql

# 配置 eureka
eureka:
  instance:
    # 主机名称:服务名称修改,其实就是向eureka server中注册的实例id
    instance-id: service-product:9011
    # 显示IP信息
    prefer-ip-address: true
  client:
    service-url: # 此处修改为 Eureka Server的集群地址
      defaultZone: http://eureka7001.com:7001/eureka/,http://eureka7002.com:7002/eureka/,http://eureka7003.com:7003/eureka/


# 微服务info内容详细信息
info:
  app.name: xxx
  company.name: xxx
  build.artifactId: $project.artifactId$
  build.version: $project.version$

  • 商品微服务9001和9011的ProductController.java
package com.sunxiaping.product.controller;

import com.sunxiaping.product.domain.Product;
import com.sunxiaping.product.service.ProductService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.web.bind.annotation.*;

@RestController
@RequestMapping(value = "/product")
public class ProductController {

    @Autowired
    private ProductService productService;

    @Value("${server.port}")
    private String port;

    @Value("${spring.cloud.client.ip-address}")
    private String ip;


    @PostMapping(value = "/save")
    public String save(@RequestBody Product product) {
        productService.save(product);
        return "新增成功";
    }

    @GetMapping(value = "/findById/{id}")
    public Product findById(@PathVariable(value = "id") Long id) {
        Product product = productService.findById(id);
        product.setProductName("访问的地址是:" + ip + ":" + port);
        return product;
    }
}
  • 订单微服务的OrderController.java
package com.sunxiaping.order.controller;


import com.sunxiaping.order.domain.Product;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.client.discovery.DiscoveryClient;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.client.RestTemplate;

@RestController
@RequestMapping(value = "/order")
public class OrderController {
    @Autowired
    private RestTemplate restTemplate;

    /**
     * SpringCloud提供的获取元数据的工具类
     * 调用方法获取服务的元数据
     */
    @Autowired
    private DiscoveryClient discoveryClient;

    /**
     * 基于Ribbon的形式调用远程的微服务
     *
     * @param id
     * @return
     */
    @GetMapping(value = "/buy/{id}")
    public Product buy(@PathVariable(value = "id") Long id) {
        Product product = restTemplate.getForObject("http://service-product/product/findById/" + id, Product.class);
        return product;
    }
}
  • 启动Eureka服务集群,将商品微服务启动并注册到Eureka服务集群中,然后通过http://localhost:9002/order/buy/1查看控制台效果。

基于Ribbon实现客户端的负载均衡

2.2 Ribbon内置的负载均衡策略

2.2.1 Ribbon内置的负载均衡策略的概述

  • Ribbon内置了多种负载均衡策略,内部负责复杂均衡的顶层接口为com.netflix.loadbalancer.IRule,实现方式如下:

com.netflix.loadbalancer.IRule的实现

  • com.netflix.loadbalancer.RoundRobinRule:以轮询的方式进行负载均衡。
  • com.netflix.loadbalancer.RandomRule:随机策略。
  • com.netflix.loadbalancer.RetryRule:重试策略。
  • com.netflix.loadbalancer.WeightedResponseTimeRule:权重策略。会计算每个服务的权重,权重越高的被调用的可能性会越大。
  • com.netflix.loadbalancer.BestAvailableRule:最佳策略。遍历所有的服务实例,过滤掉故障实例,并将请求数量最小的实例返回。
  • com.netflix.loadbalancer.AvailabilityFilteringRule:可用过滤策略。过滤掉故障和请求数超过阈值的服务实例,再从剩下的实例中轮询调用。

2.2.1 Ribbon内置的负载均衡策略的使用一

  • 在服务消费者的application.yml中修改负载均衡策略:
# 修改ribbon的负载均衡策略 服务名 - ribbon - NFLoadBalancerRuleClassName :负载均衡策略
service-product:
  ribbon:
    NFLoadBalancerRuleClassName: com.netflix.loadbalancer.RandomRule # 修改ribbon的负载均衡策略为权重策略
  • 消息者完整的application.yml:
server:
  port: 9002 # 微服务的端口号

spring:
  application:
    name: service-order # 微服务的名称
  datasource:
    url: jdbc:mysql://192.168.1.57:3306/test?useUnicode=true&characterEncoding=UTF-8&autoReconnect=true&useSSL=false&serverTimezone=GMT%2B8&allowPublicKeyRetrieval=true
    driver-class-name: com.mysql.cj.jdbc.Driver
    username: root
    password: 123456
  jpa:
    generate-ddl: true
    show-sql: true
    open-in-view: true
    database: mysql

# 配置Eureka
eureka:
  instance:
    # 实例的名称
    instance-id: service-order:9002
    # 显示IP信息
    prefer-ip-address: true
    lease-renewal-interval-in-seconds: 5 # 发送心跳续约间隔(默认30秒)
    lease-expiration-duration-in-seconds: 10 # Eureka Client发送心跳给Eureka Server端后,续约到期时间(默认90秒)
  client:
    healthcheck:
      enabled: true
    service-url: # Eureka Server的地址
      #      defaultZone: http://localhost:9000/eureka/
      defaultZone:  http://eureka7001.com:7001/eureka/,http://eureka7002.com:7002/eureka/

# 修改ribbon的负载均衡策略 服务名 - ribbon - NFLoadBalancerRuleClassName :负载均衡策略
service-product:
  ribbon:
    NFLoadBalancerRuleClassName: com.netflix.loadbalancer.RandomRule # 修改ribbon的负载均衡策略为权重策略

# 微服务info内容详细信息
info:
  app.name: xxx
  company.name: xxx
  build.artifactId: $project.artifactId$
  build.version: $project.version$

修改Ribbon的负载均衡策略为随机策略

2.2.2 Ribbon内置的负载均衡策略的使用二

Ribbon内置的负载均衡策略的使用

  • 在启动类扫描不到的地方新建一个自定义的Rule配置类(比如启动类的包名是com.sunxiaping.order,而自定义Rule配置类的包名是com.sunxiaping.ribbon.rule)。
package com.sunxiaping.ribbon.rule;

import com.netflix.loadbalancer.IRule;
import com.netflix.loadbalancer.RandomRule;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class SelfRule {


    /**
     * 替换Ribbon内置的负载均衡策略
     *
     * @return
     */
    @Bean
    public IRule iRule() {
        return new RandomRule();
    }
}
  • 在启动类上标注@RibbonClient注解:
package com.sunxiaping.order;

import com.sunxiaping.ribbon.rule.SelfRule;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.EnableEurekaClient;
import org.springframework.cloud.netflix.ribbon.RibbonClient;


@SpringBootApplication
@EnableEurekaClient
//在启动类上标注@RibbonClient注解,这样该微服务启动的时候就能去加载自定义的Ribbon配置类,从而使得配置生效
@RibbonClient(name = "service-product",configuration = SelfRule.class)
public class OrderApplication {
    public static void main(String[] args) {
        SpringApplication.run(OrderApplication.class, args);
    }
}

2.2.3 Ribbon的策略选择

  • 如果每个机器配置一样,建议不修改策略(推荐)。
  • 如果部分机器配置强,则可以改为WeightedResponseTimeRule。

2.3 请求重试机制

  • 在实际生产环境中,Ribbon做客户端负载均衡的时候,Ribbon默认的负载均衡算法是轮询,一旦访问到的那台微服务提供者突然宕机了,此时就会出现404的情况,这时可以使用Ribbon的请求重试机制,Ribbon的请求重试机制基于Spring的retry(Spring的重试框架)。

  • 使用:

  • 1️⃣在微服务消费者导入spring-retry的Maven坐标:

<dependency>
    <groupId>org.springframework.retry</groupId>
    <artifactId>spring-retry</artifactId>
</dependency>
  • 2️⃣修改微服务消费者的application.yml:
  • 修改部分:
# Ribbon的重试机制
service-product:
  ribbon:
#    修改ribbon的负载均衡策略 服务名 - ribbon - NFLoadBalancerRuleClassName :负载均衡策略
#    NFLoadBalancerRuleClassName: com.netflix.loadbalancer.RandomRule # 修改ribbon的负载均衡策略为权重策略
    # Ribbon的重试机制参数
    ConnectTimeout: 250 # Ribbon的连接超时时间
    ReadTimeout: 1000 # Ribbon的数据读取超时时间
    OkToRetryOnAllOperations: true # 是否对所有操作都进行重试
    MaxAutoRetriesNextServer: 2 # 切换实例的重试次数
    MaxAutoRetries: 1 # 对当前实例的重试次数
  • 完整部分:
server:
  port: 9002 # 微服务的端口号

spring:
  application:
    name: service-order # 微服务的名称
  datasource:
    url: jdbc:mysql://192.168.1.57:3306/test?useUnicode=true&characterEncoding=UTF-8&autoReconnect=true&useSSL=false&serverTimezone=GMT%2B8&allowPublicKeyRetrieval=true
    driver-class-name: com.mysql.cj.jdbc.Driver
    username: root
    password: 123456
  jpa:
    generate-ddl: true
    show-sql: true
    open-in-view: true
    database: mysql


# 配置Eureka
eureka:
  instance:
    # 实例的名称
    instance-id: service-order:9002
    # 显示IP信息
    prefer-ip-address: true
    lease-renewal-interval-in-seconds: 5 # 发送心跳续约间隔(默认30秒)
    lease-expiration-duration-in-seconds: 10 # Eureka Client发送心跳给Eureka Server端后,续约到期时间(默认90秒)
  client:
    healthcheck:
      enabled: true
    service-url: # Eureka Server的地址
      #      defaultZone: http://localhost:9000/eureka/
      defaultZone:  http://eureka7001.com:7001/eureka/,http://eureka7002.com:7002/eureka/

# Ribbon的重试机制
service-product:
  ribbon:
#    修改ribbon的负载均衡策略 服务名 - ribbon - NFLoadBalancerRuleClassName :负载均衡策略
#    NFLoadBalancerRuleClassName: com.netflix.loadbalancer.RandomRule # 修改ribbon的负载均衡策略为权重策略
    # Ribbon的重试机制参数
    ConnectTimeout: 250 # Ribbon的连接超时时间
    ReadTimeout: 1000 # Ribbon的数据读取超时时间
    OkToRetryOnAllOperations: true # 是否对所有操作都进行重试
    MaxAutoRetriesNextServer: 2 # 切换实例的重试次数
    MaxAutoRetries: 1 # 对当前实例的重试次数

# 微服务info内容详细信息
info:
  app.name: xxx
  company.name: xxx
  build.artifactId: $project.artifactId$
  build.version: $project.version$

# 开启日志debug
logging:
  level:
    root: debug

3 Ribbon中负载均衡的源码分析

3.1 Ribbon中的关键组件

Ribbon中的关键组件

  • ServerList:可以响应客户端的特定服务的服务器列表。
  • ServerListFilter:可以动态获取具有所需特征的候选服务器列表的过滤器。
  • ServetListUpdater:用于执行动态服务器的列表更新。
  • Rule:负载均衡策略,用于确定从服务器列表返回哪个服务器。
  • Ping:客户端用于快速检查服务器当时是否处于活动状态。
  • LoaderBalancer:负载均衡器,负载负载均衡调度的管理。

3.2 @LoadBalanced注解

  • 使用Ribbon完成客户端负载均衡往往是从一个注解开始的:
package com.sunxiaping.order.config;

import org.springframework.cloud.client.loadbalancer.LoadBalanced;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.client.RestTemplate;

@Configuration
public class SpringConfig {

    @Bean
    @LoadBalanced
    public RestTemplate restTemplate() {
        return new RestTemplate();
    }
}
  • @LoadBalanced注解的源码如下:
/**
 * Annotation to mark a RestTemplate bean to be configured to use a LoadBalancerClient.
 * @author Spencer Gibb
 */
@Target({ ElementType.FIELD, ElementType.PARAMETER, ElementType.METHOD })
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@Qualifier
public @interface LoadBalanced {
}
  • 可以知道@LoadBalanced注解就是用来给RestTemplate做标记,方便我们对RestTemplate添加一个LoadBalancerClient,以实现客户端负载均衡。

3.3 自动装配

  • 根据SpringBoot中的自动装配规则可以在spring-cloud-netflix-ribbon-2.1.0.RELEASE.jar中找到spring.factories,内容如下:
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
org.springframework.cloud.netflix.ribbon.RibbonAutoConfiguration
  • RibbonAutoConfiguration的部分源码如下:
@Configuration
@Conditional({RibbonAutoConfiguration.RibbonClassesConditions.class})
@RibbonClients
@AutoConfigureAfter(
    name = {"org.springframework.cloud.netflix.eureka.EurekaClientAutoConfiguration"}
)
@AutoConfigureBefore({LoadBalancerAutoConfiguration.class, AsyncLoadBalancerAutoConfiguration.class})
@EnableConfigurationProperties({RibbonEagerLoadProperties.class, ServerIntrospectorProperties.class})
public class RibbonAutoConfiguration {
    @Autowired(
        required = false
    )
    private List<RibbonClientSpecification> configurations = new ArrayList();
    @Autowired
    private RibbonEagerLoadProperties ribbonEagerLoadProperties;

    public RibbonAutoConfiguration() {
    }
    //其他略
}    
  • 可以知道RibbonAutoConfiguration引入了LoadBalancerAutoConfiguration配置类。

3.4 负载均衡调用

  • LoadBalancerAutoConfiguration的源码如下:
@Configuration
@ConditionalOnClass(RestTemplate.class)
@ConditionalOnBean(LoadBalancerClient.class)
@EnableConfigurationProperties(LoadBalancerRetryProperties.class)
public class LoadBalancerAutoConfiguration {

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

	@Bean
	public SmartInitializingSingleton loadBalancedRestTemplateInitializerDeprecated(
			final ObjectProvider<List<RestTemplateCustomizer>> restTemplateCustomizers) {
		return () -> restTemplateCustomizers.ifAvailable(customizers -> {
            for (RestTemplate restTemplate : LoadBalancerAutoConfiguration.this.restTemplates) {
                for (RestTemplateCustomizer customizer : customizers) {
                    customizer.customize(restTemplate);
                }
            }
        });
	}

	@Autowired(required = false)
	private List<LoadBalancerRequestTransformer> transformers = Collections.emptyList();

	@Bean
	@ConditionalOnMissingBean
	public LoadBalancerRequestFactory loadBalancerRequestFactory(
			LoadBalancerClient loadBalancerClient) {
		return new LoadBalancerRequestFactory(loadBalancerClient, transformers);
	}

	@Configuration
	@ConditionalOnMissingClass("org.springframework.retry.support.RetryTemplate")
	static class LoadBalancerInterceptorConfig {
        //①创建LoadBalancerInterceptor的Bean,用于实现对客户端请求进行拦截,以实现客户端负载均衡
		@Bean
		public LoadBalancerInterceptor ribbonInterceptor(
				LoadBalancerClient loadBalancerClient,
				LoadBalancerRequestFactory requestFactory) {
			return new LoadBalancerInterceptor(loadBalancerClient, requestFactory);
		}
		//②创建RestTemplateCustomizer的Bean,用于给RestTemplate增加LoadBalancerInterceptor烂机器。
		@Bean
		@ConditionalOnMissingBean
		public RestTemplateCustomizer restTemplateCustomizer(
				final LoadBalancerInterceptor loadBalancerInterceptor) {
			return restTemplate -> {
                //③维护了一个被@LoadBalanced注解修饰的RestTemplate对象列表,并在这里进行初始化,通过调用RestTemplateCustomizer的实例来给需要客户端负载均衡的RestTemplate增加LoadBalancerInterceptor拦截器
                List<ClientHttpRequestInterceptor> list = new ArrayList<>(
                        restTemplate.getInterceptors());
                list.add(loadBalancerInterceptor);
                restTemplate.setInterceptors(list);
            };
		}
	}

	@Configuration
	@ConditionalOnClass(RetryTemplate.class)
	public static class RetryAutoConfiguration {
		@Bean
		@ConditionalOnMissingBean
		public LoadBalancedRetryFactory loadBalancedRetryFactory() {
			return new LoadBalancedRetryFactory() {};
		}
	}

	@Configuration
	@ConditionalOnClass(RetryTemplate.class)
	public static class RetryInterceptorAutoConfiguration {
		@Bean
		@ConditionalOnMissingBean
		public RetryLoadBalancerInterceptor ribbonInterceptor(
				LoadBalancerClient loadBalancerClient, LoadBalancerRetryProperties properties,
				LoadBalancerRequestFactory requestFactory,
				LoadBalancedRetryFactory loadBalancedRetryFactory) {
			return new RetryLoadBalancerInterceptor(loadBalancerClient, properties,
					requestFactory, loadBalancedRetryFactory);
		}

		@Bean
		@ConditionalOnMissingBean
		public RestTemplateCustomizer restTemplateCustomizer(
				final RetryLoadBalancerInterceptor loadBalancerInterceptor) {
			return restTemplate -> {
                List<ClientHttpRequestInterceptor> list = new ArrayList<>(
                        restTemplate.getInterceptors());
                list.add(loadBalancerInterceptor);
                restTemplate.setInterceptors(list);
            };
		}
	}
}
  • 在该自动配置类中,主要做如下的三件事:

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

  • 2️⃣创建了一个RestTemplateCustomizer的Bean,用于给RestTemplate增加LoadBalancerInterceptor拦截器。

  • 3️⃣维护了一个被@LoadBalanced注解修改的RestTemplate对象列表,并在这里进行初始化,通过调用RestTemplateCustomizer的实例来给需要客户端负载均衡的RestTemplate增加LoadBalancerInterceptor拦截器。

  • LoadBalancerInterceptor的源码如下:

public class LoadBalancerInterceptor implements ClientHttpRequestInterceptor {

	private LoadBalancerClient loadBalancer;
	private LoadBalancerRequestFactory requestFactory;

	public LoadBalancerInterceptor(LoadBalancerClient loadBalancer, LoadBalancerRequestFactory requestFactory) {
		this.loadBalancer = loadBalancer;
		this.requestFactory = requestFactory;
	}

	public LoadBalancerInterceptor(LoadBalancerClient loadBalancer) {
		// for backwards compatibility
		this(loadBalancer, new LoadBalancerRequestFactory(loadBalancer));
	}

	@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, "Request URI does not contain a valid hostname: " + originalUri);
		return this.loadBalancer.execute(serviceName, requestFactory.createRequest(request, body, execution));
	}
}
  • 通过源码和之前的自动化配置类,我们可以看到在拦截器中注入了LoadBalancerClient的实现。当一个被@LoadBalanced注解修饰的RestTemplate对象向外发起HTTP请求时,会被LoadBalancerInterceptor类的intercept方法拦截。
  • LoadBalancerClient是一个抽象的负载均衡接口,其实现类是RibbonLoadBalancerClient。
public class RibbonLoadBalancerClient implements LoadBalancerClient {
    private SpringClientFactory clientFactory;

    public RibbonLoadBalancerClient(SpringClientFactory clientFactory) {
        this.clientFactory = clientFactory;
    }

    public URI reconstructURI(ServiceInstance instance, URI original) {
        Assert.notNull(instance, "instance can not be null");
        String serviceId = instance.getServiceId();
        RibbonLoadBalancerContext context = this.clientFactory.getLoadBalancerContext(serviceId);
        URI uri;
        Server server;
        if (instance instanceof RibbonLoadBalancerClient.RibbonServer) {
            RibbonLoadBalancerClient.RibbonServer ribbonServer = (RibbonLoadBalancerClient.RibbonServer)instance;
            server = ribbonServer.getServer();
            uri = RibbonUtils.updateToSecureConnectionIfNeeded(original, ribbonServer);
        } else {
            server = new Server(instance.getScheme(), instance.getHost(), instance.getPort());
            IClientConfig clientConfig = this.clientFactory.getClientConfig(serviceId);
            ServerIntrospector serverIntrospector = this.serverIntrospector(serviceId);
            uri = RibbonUtils.updateToSecureConnectionIfNeeded(original, clientConfig, serverIntrospector, server);
        }

        return context.reconstructURIWithServer(server, uri);
    }
	//根据传入的服务id,从负载均衡中为指定的服务选择一个服务实例
    public ServiceInstance choose(String serviceId) {
        return this.choose(serviceId, (Object)null);
    }
	
    public ServiceInstance choose(String serviceId, Object hint) {
        Server server = this.getServer(this.getLoadBalancer(serviceId), hint);
        return server == null ? null : new RibbonLoadBalancerClient.RibbonServer(serviceId, server, this.isSecure(server, serviceId), this.serverIntrospector(serviceId).getMetadata(server));
    }
	
    //根据传入的服务id,指定的负责均衡器中的服务实例的执行请求
    public <T> T execute(String serviceId, LoadBalancerRequest<T> request) throws IOException {
        return this.execute(serviceId, (LoadBalancerRequest)request, (Object)null);
    }
	//根据传入的服务实例,执行请求
    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);
        }
    }

    //根据传入的服务实例,执行请求
    public <T> T execute(String serviceId, ServiceInstance serviceInstance, LoadBalancerRequest<T> request) throws IOException {
        Server server = null;
        if (serviceInstance instanceof RibbonLoadBalancerClient.RibbonServer) {
            server = ((RibbonLoadBalancerClient.RibbonServer)serviceInstance).getServer();
        }

        if (server == null) {
            throw new IllegalStateException("No instances available for " + serviceId);
        } else {
            RibbonLoadBalancerContext context = this.clientFactory.getLoadBalancerContext(serviceId);
            RibbonStatsRecorder statsRecorder = new RibbonStatsRecorder(context, server);

            try {
                T returnVal = request.apply(serviceInstance);
                statsRecorder.recordStats(returnVal);
                return returnVal;
            } catch (IOException var8) {
                statsRecorder.recordStats(var8);
                throw var8;
            } catch (Exception var9) {
                statsRecorder.recordStats(var9);
                ReflectionUtils.rethrowRuntimeException(var9);
                return null;
            }
        }
    }
	//略
}    
  • 从RibbonLoadBalancerClient代码可以看出,实际负载均衡是通过ILoadBalancer来实现的。
posted @ 2020-10-03 16:18  许大仙  阅读(480)  评论(0编辑  收藏  举报