关于java:Spring-Cloud-Ribbon入门和实战

2次阅读

共计 18231 个字符,预计需要花费 46 分钟才能阅读完成。

文章首发:Spring Cloud Ribbon 入门和实战

Ribbon 是什么

Ribbon 是一个客户端负载均衡器,它赋予了利用一些摆布 HTTP 与 TCP 行为的能力,这里的负载平衡是客户端的负载平衡,也有人称为后端负载平衡是过程内负载平衡的一种。Ribbon 是 SpringCloud 生态里的不可短少的组件,有了它,是个服务的横向扩大更加不便了。此外想 Feign 和 Zuul 默认是集成了 Ribbon。

Ribbon 是 Neflix 开源的一个组件,目前 Ribbon 早已进入保护状态,然而就目前的状况来看,Spring Cloud Netflix 的一些组件还是能够应用。

Spring Cloud Loadbalancer 是 Spring Cloud 社区开源的组件,目标是代替进入保护状态的 Ribbon,然而 Loadbalancer 还是有很长的一段路要走。

Ribbon 入门

因为客户端负载平衡须要从注册核心获取服务列表,所以须要集成注册核心。

创立父级工程 cloud-ribbon-practice

    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.2.3.RELEASE</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>

    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
        <java.version>1.8</java.version>
        <spring.cloud-version>Hoxton.SR3</spring.cloud-version>
    </properties>

    <dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-dependencies</artifactId>
                <version>${spring.cloud-version}</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
        </dependencies>
    </dependencyManagement>

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-actuator</artifactId>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>

创立注册核心 cloud-eureka-server

<dependencies>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
        </dependency>
    </dependencies>

配置文件 application.xml

server:
  port: 8761
eureka:
  instance:
    hostname: localhost
  client:
    registerWithEureka: false
    fetchRegistry: false
    serviceUrl:
      defaultZone: http://${eureka.instance.hostname}:${server.port}/eureka/

启动类

@SpringBootApplication
@EnableEurekaServer
public class EurekaServerApplication {public static void main(String[] args) {SpringApplication.run(EurekaServerApplication.class, args);
    }
}

创立源服务工程 cloud-ribbon-hello-b1、cloud-ribbon-hello-b2、cloud-ribbon-hello-b3

    <dependencies>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
        </dependency>
    </dependencies>

配置文件 application.yml

b1

spring:
  application:
    name: ribbon-service-b
server:
  port: 7777
eureka:
  client:
    serviceUrl:
      defaultZone: http://localhost:8761/eureka/
  instance:
    prefer-ip-address: true

b2

spring:
  application:
    name: ribbon-service-b
server:
  port: 7778
eureka:
  client:
    serviceUrl:
      defaultZone: http://localhost:8761/eureka/
  instance:
    prefer-ip-address: true

b3

spring:
  application:
    name: ribbon-service-b
server:
  port: 7779
eureka:
  client:
    serviceUrl:
      defaultZone: http://localhost:8761/eureka/
  instance:
    prefer-ip-address: true

b1、b2、b3 的 controller

@RestController
public class OrderController {@Value("${server.port}")
    private Integer port;

    @Value("${spring.application.name}")
    private String name;

    @GetMapping("/test")
    public String add() {return "this service name is" + name + "and port is" + port;}

}

b1、b2、b3 的启动类

@SpringBootApplication
@EnableDiscoveryClient
public class ServiceB1Application {public static void main(String[] args) {SpringApplication.run(ServiceB1Application.class, args);
    }
}

@SpringBootApplication
@EnableDiscoveryClient
public class ServiceB2Application {public static void main(String[] args) {SpringApplication.run(ServiceB2Application.class, args);
    }
}

@SpringBootApplication
@EnableDiscoveryClient
public class ServiceB3Application {public static void main(String[] args) {SpringApplication.run(ServiceB3Application.class, args);
    }
}

创立服务调用者 cloud-ribbon-hello-a

spring-cloud-starter-netflix-eureka-client曾经集成了 ribbon。不须要额定引入,间接应用即可。

    <dependencies>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
        </dependency>
    </dependencies>

配置文件 application.yml

spring:
  application:
    name: ribbon-hello-a
server:
  port: 7776
eureka:
  client:
    serviceUrl:
      defaultZone: http://localhost:8761/eureka/
  instance:
    prefer-ip-address: true

启动类,RestTemplate 应用了@LoadBalanced,这样 RestTemplate 就开启了 ribbon 的负载平衡了。

@SpringBootApplication
@EnableDiscoveryClient
public class ServiceAApplication {public static void main(String[] args) {SpringApplication.run(ServiceAApplication.class, args);
    }

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

controller

@RestController
@RequestMapping("ribbon")
public class TestController {

    @Autowired
    private RestTemplate restTemplate;

    @GetMapping("/test")
    public String test(){String body = restTemplate.getForEntity("http://RIBBON-SERVICE-B/test", String.class).getBody();
        return body;
    }

}

测试,应用 postman 拜访几次:http://localhost:7776/ribbon/…

Ribbon 实战

从上一节能够看到,开启负载平衡只需通过 @LoadBalanced 注解即可。负载平衡中又很多的负载平衡策略,如轮询(Round Robin)、权重(Weight)、ip_hash 等。这些丰盛的策略让咱们在构建利用的时候,有很多抉择的余地,能够依据理论的业务场景抉择最合适的策略。

在 Ribbon 中一共提供了 7 中负载平衡策略:

策略类 命名 形容
RandomRule 随机策略 随机抉择 Server
RoundRobinRule 轮询策略 按程序抉择 Server
RetryRule 重试策略 在一个配置时间段内当抉择 Server 不胜利,则始终尝试抉择一个可用的 Server
BestAvailableRule 最低并发策略 卓哥考查 Server,如果 Server 断路器关上,则疏忽,再抉择其中并发连贯最低的 Server
AvailabilityFilteringRule 可用过滤策略 过滤始终连贯失败并标记为 circuit tripped 的 Server,过滤掉那些高并发连贯的 Server(active connections 超过配置的阈值)
ResponseTimeWeightedRule 响应工夫加权策略 曾经被弃用,作用同 WeightedResponseTimeRule
WeightedResponseTimeRule 响应工夫加权策略 依据 Server 的响应工夫调配权重,响应工夫越长,权重越低,被选中的概率就越低。响应工夫越短,权重越高,被抉择到的概率越高
ZoneAvoidanceRule 区域衡量策略 综合判断 Server 所在区域的性能和 Server 的可用性轮询抉择 Server,并且判断一个 AWS Zone 的运行性能是否可用,剔除不可用的 Zone 中的所有 Server

在下面的入门案例中 Ribbon 的默认负载平衡策略是轮询策略,

Ribbon 自定义配置负载平衡策略

全局配置

应用 Ribbon 时配置全局的负载平衡策略,须要加一个配置类。改配置类须要被 @ComponentScan 扫描到能力全局失效。

@Configuration
public class GlobalRuleConfig {
    @Bean
    public IRule ribbonRule() {return new RandomRule();
    }
}

下面配置了随机的策略,屡次拜访 http://localhost:7776/ribbon/…

基于 @RibbonClient@RibbonClients注解的配置

配置类,留神:编写自定义配置类,须要特地留神的是官网文档明确给出了正告:这个自定义配置类不能放在 @ComponentScan 所扫描的包以及其子包下(即不能放在主启动类所在的包及其子包下,因而咱们须要新建一个包来放该配置类),否则咱们自定义的这个配置类就会被所有的 Ribbon 客户端所共享,也就达不到特殊化定制的目标了

@Configuration
public class AnnoRuleConfig {
    @Bean
    public IRule ribbonRule() {return new RandomRule();
    }
}

启动类的配置

@SpringBootApplication
@EnableDiscoveryClient
@RibbonClient(name = "ribbon-service-b", configuration = AnnoRuleConfig.class)
public class ServiceAApplication {public static void main(String[] args) {SpringApplication.run(ServiceAApplication.class, args);
    }

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

通过 @RibbonClient 指定某个服务的负载平衡策略,其余没有被指定的,就是用默认的负载平衡策略。该注解能够把其余的配置类作为另外一个 IOC 容器导入到利用中,相当于加载了两个齐全不相干的 Spring 的 beans 配置文件,此时利用中会有两个 IOC 容器。

@RibbonClient(name = "RIBBON-SERVICE-B", configuration = AnnoRuleConfig.class)

也能够应用一下的形式,指定多个服务的负载平衡策略

@RibbonClients(value = {@RibbonClient(name = "RIBBON-SERVICE-B", configuration = AnnoRuleConfig.class),
        @RibbonClient(name = "RIBBON-SERVICE-C", configuration = AnnoRuleConfig.class)
})

基于配置文件

上面对服务 ribbon-service-b 的负载平衡策略应用

RIBBON-SERVICE-B:
  ribbon:
    NFLoadBalancerRuleClassName: com.netflix.loadbalancer.RandomRule

Ribbon 超时与重试

应用 HTTP 发动申请难免会产生问题,在 F 版开始 Ribbon 的重试机制默认是开启的,须要增加对超时工夫与重试策略的配置。列入上面 ribbon-service-b 服务的配置

RIBBON-SERVICE-B:
  ribbon:
    NFLoadBalancerRuleClassName: com.netflix.loadbalancer.RandomRule
    ConnectTimeout: 3000
    ReadTimeout: 60000
    MaxAutoRetries: 3 #对第一次申请的服务的重试次数
    MaxAutoRetriesNextServer: 1 #要重试的下一个服务的最大数量(不包含第一个服务)OkToRetryOnAllOperations: true

也能够全局配置

ribbon:
  ConnectTimeout: 3000
  ReadTimeout: 60000
  MaxAutoRetries: 3 #对第一次申请的服务的重试次数
  MaxAutoRetriesNextServer: 1 #要重试的下一个服务的最大数量(不包含第一个服务)OkToRetryOnAllOperations: true

个别 Ribbon 都是搭配 OpenFeign 这类 Http 客户端或者其余 RPC 应用。因为这样去调用近程服务会更加优雅和不便。而 OpenFeign 默认是继承了 Ribbon,对于 Ribbon 的超时工夫配置也是很简略。

对于网络抖动这些能够应用spring-retry,spring-retry 是 spring 提供的一个基于 spring 的重试框架,十分好用。

Ribbon 饥饿加载

Ribbon 在进行客户端负载平衡的时候,并不是启动时就加载上下文,而是在理论申请的时候采取创立。因为要加载上下文的起因,在第一次调用时可能会很慢,甚至导致超时。所以咱们能够指定 Ribbon 客户端开启立刻加载(饥饿加载),在利用启动的时候就立刻加载所有配置项的应用程序上下文。

ribbon:
  eager-load:
    clients: ribbon-service-b, ribbon-service-order
    enabled: true

自定义 Ribbon 客户端

在 Ribbon 的 1.2.0 版本之后,就能够应用配置文件来定制 Ribbon 客户端,其实本质就是应用配置文件来指定一些默认加载类,从而更改 Ribbon 客户端的行为,并且应用这种形式优先级最高,优先级高于应用注解 @RibbonClient 指定的配置和源码中加载的相干的 Bean。看下表:

配置项 阐明
<clientName>.ribbon.NFLoadBalancerClassName 指定 ILoadBalancer 的实现类
<clientName>.ribbon.NFLoadBalancerRuleClassName 指定 IRule 的实现类
<clientName>.ribbon.NFLoadBalancerPingClassName 指定 IPing 的实现类
<clientName>.ribbon.NiWSServerListClassName 指定 ServerList 的实现类
<clientName>.ribbon.NIWSServerListFilterClassName 指定 ServerListFilter 的实现类

例如:这里应用的实现的 RIbbon 提供的实现

RIBBON-SERVICE-B:
  ribbon:
    NFLoadBalancerRuleClassName: com.netflix.loadbalancer.RandomRule
    NiWSServerListClassName: com.netflix.loadbalancer.ConfigurationBasedServerList

Ribbon 脱离 Eureka 应用

在默认的状况下,Ribbon 客户端须要从 Eureka 注册核心读取服务注册信息列表,来达到一种动静负载平衡的性能。当应用的注册核心是公共的注册核心,例如:社区公益 Eureka(http://eureka.springcloud.cn)…

ribbon: 
  eureka:
    enabled: false
#RIBBON-SERVICE- B 服务的获取地址
RIBBON-SERVICE-B:
  ribbon:
    listOfServers: http://localhost:8088/

Ribbon 进阶

外围工作原理

Ribbon 的外围接口:

接口 形容 默认实现
IClientConfig 定义 Ribbon 中治理配置的接口 DefaultClientConfigImpl
IRule 定义 Ribbon 中负载平衡策略的接口 ZoneAdvoidanceRule
IPing 定义定期 Ping 服务查看可用性的接口 DummyPing
ServerList\<Server> 定义获取服务列表办法的接口 ConfigurationBasedServerList
ServerListFilter\<Server> 定义特定冀望获取服务列表办法的接口 ZonePreferenceServerListFilter
ILoadBalancer 定义负载平衡抉择服务的外围办法的接口 ZoneAwareLoadBalancer
ServerListUpdater 为 DynamicServerListLoadBalancer 定义动静更新服务列表的接口 PollingServerListUpdater

Ribbon 齐全是基于这些接口上建设起来的,是 Ribbon 的外围。理解这些外围的类的性能对于了解 Ribbon 的原理和扩大很无利。

在之前的例子中,应用 Ribbon 负载平衡都是通过在 RestTemplate 的 Bean 上增加注解@LoadBalanced,使得 RestTemplate 领有了负载平衡的能力。

LoadBalanced 源码:


/**
 * Annotation to mark a RestTemplate or WebClient 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 {}

在正文中能够看到:该注解标记在 RestTemplate 或者其余的 WebClient 的 Bean 上,来应用 LoadBalancerClient

LoadBalancerClient:该接口扩大自 ServiceInstanceChooser

public interface LoadBalancerClient extends ServiceInstanceChooser {

    /**
     * Executes request using a ServiceInstance from the LoadBalancer for the specified
     * service.
     * @param serviceId The service ID to look up the LoadBalancer.
     * @param request Allows implementations to execute pre and post actions, such as
     * incrementing metrics.
     * @param <T> type of the response
     * @throws IOException in case of IO issues.
     * @return The result of the LoadBalancerRequest callback on the selected
     * ServiceInstance.
     */
    <T> T execute(String serviceId, LoadBalancerRequest<T> request) throws IOException;

    /**
     * Executes request using a ServiceInstance from the LoadBalancer for the specified
     * service.
     * @param serviceId The service ID to look up the LoadBalancer.
     * @param serviceInstance The service to execute the request to.
     * @param request Allows implementations to execute pre and post actions, such as
     * incrementing metrics.
     * @param <T> type of the response
     * @throws IOException in case of IO issues.
     * @return The result of the LoadBalancerRequest callback on the selected
     * ServiceInstance.
     */
    <T> T execute(String serviceId, ServiceInstance serviceInstance,
            LoadBalancerRequest<T> request) throws IOException;

    /**
     * Creates a proper URI with a real host and port for systems to utilize. Some systems
     * use a URI with the logical service name as the host, such as
     * http://myservice/path/to/service. This will replace the service name with the
     * host:port from the ServiceInstance.
     * @param instance service instance to reconstruct the URI
     * @param original A URI with the host as a logical service name.
     * @return A reconstructed URI.
     */
    URI reconstructURI(ServiceInstance instance, URI original);
}

ServiceInstanceChooser:

public interface ServiceInstanceChooser {

    /**
     * Chooses a ServiceInstance from the LoadBalancer for the specified service.
     * @param serviceId The service ID to look up the LoadBalancer.
     * @return A ServiceInstance that matches the serviceId.
     */
    ServiceInstance choose(String serviceId);
}
  • ServiceInstance choose(String serviceId):依据 ServiceId,联合负载均衡器抉择一个服务实例
  • <T> T execute(String serviceId, LoadBalancerRequest<T> request):应用来自 LoadBalancer 的 ServiceInstance 为指定的服务执行申请
  • <T> T execute(String serviceId, ServiceInstance serviceInstance,

            LoadBalancerRequest<T> request):应用来自 LoadBalancer 的 ServiceInstance 为指定的服务执行申请,是上一个办法的重载,在实现类中能够看到它们的关系,就是前一个办法的细节实现、
  • URI reconstructURI(ServiceInstance instance, URI original):应用注解 ip 和 port 构建特定的 URL 以供 Ribbon 外部应用。Ribbon 应用具备逻辑服务名称的 URL 作为 host,例如:http://service-b/order/add。

从这些办法的性能能够晓得这两个接口的重要性了。这两个接口的同一包下有一个类 LoadBalancerAutoConfigurationLoadBalancerAutoConfigurationorg.springframework.cloud.client.loadbalancer包下,在 spring-cloud-commons 外面。该主动配置类正式 Ribbon 的外围配置类。

@Configuration(proxyBeanMethods = false)
@ConditionalOnClass(RestTemplate.class)
@ConditionalOnBean(LoadBalancerClient.class)
@EnableConfigurationProperties(LoadBalancerRetryProperties.class)
public class LoadBalancerAutoConfiguration {

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

    @Autowired(required = false)
    private List<LoadBalancerRequestTransformer> transformers = 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);
                }
            }
        });
    }

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

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

        @Bean
        public LoadBalancerInterceptor ribbonInterceptor(
                LoadBalancerClient loadBalancerClient,
                LoadBalancerRequestFactory requestFactory) {return new LoadBalancerInterceptor(loadBalancerClient, requestFactory);
        }

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

    }

    /**
     * Auto configuration for retry mechanism.
     */
    @Configuration(proxyBeanMethods = false)
    @ConditionalOnClass(RetryTemplate.class)
    public static class RetryAutoConfiguration {

        @Bean
        @ConditionalOnMissingBean
        public LoadBalancedRetryFactory loadBalancedRetryFactory() {return new LoadBalancedRetryFactory() {};}

    }

    /**
     * Auto configuration for retry intercepting mechanism.
     */
    @Configuration(proxyBeanMethods = false)
    @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);
            };
        }

    }

}

这就清晰起来了,他的配置加载机会是以后工程环境必须有 RestTemplate 的实例和必须初始化了 LoadBalancerClient 的实现类。

@ConditionalOnClass(RestTemplate.class)
@ConditionalOnBean(LoadBalancerClient.class)

LoadBalancerRequestFactory:用于创立 LoadBalancerRequest 给 LoadBalancerInterceptor 应用。

LoadBalancerInterceptorConfig:保护了 LoadBalancerInterceptor 与 RestTemplateCustomizer 的实例。

  • LoadBalancerInterceptor:拦挡每一次的 HTTP 申请,将申请绑定金 Ribbon 的负载平衡的生命周期。
  • RestTemplateCustomizer:为每一个 Restemplate 绑定 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,
                this.requestFactory.createRequest(request, body, execution));
    }
}

在源码能够看到它是通过 ClientHttpRequestInterceptor 实现每次对 HTTP 申请的拦挡,ClientHttpRequestInterceptor 类是 Spring 中保护的申请拦截器,实现它的 intercept 办法就能够使得申请进入办法内,从而 Ribbon 就能够做一些本人的解决了。

在应用 RestTemplate 申请服务时应用的 URI:http://serviceName/path/to/se…

LoadBalancerInterceptor 中的 intercept 办法,最终调用的是 RibbonLoadBalancerClient 的 execute 办法:

    public <T> T execute(String serviceId, LoadBalancerRequest<T> request, Object hint) throws IOException {
        // 拿到负载均衡器的实现
        ILoadBalancer loadBalancer = this.getLoadBalancer(serviceId);
        // 拿到具体的 Server
        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);
        }
    }
    protected Server getServer(ILoadBalancer loadBalancer, Object hint) {return loadBalancer == null ? null : loadBalancer.chooseServer(hint != null ? hint : "default");
    }

对于 chooseServer 是接口 ILoadBalancer 的办法,这里就先看一下其中的一个实现 BaseLoadBalancer

    public Server chooseServer(Object key) {if (this.counter == null) {this.counter = this.createCounter();
        }

        this.counter.increment();
        if (this.rule == null) {return null;} else {
            try {return this.rule.choose(key);
            } catch (Exception var3) {logger.warn("LoadBalancer [{}]:  Error choosing server for key {}", new Object[]{this.name, key, var3});
                return null;
            }
        }
    }

最初是通过:rule.choose(key)拿到 Server,而 rule 就是 IRule。

RibbonClientConfiguration 中初始化了下面表格提到几个外围类

  1. 初始化 ribbonRule: ZoneAvoidanceRule
  2. 初始化 ribbonPing:DummyPing
  3. 初始化 ribbonServerList:ConfigurationBasedServerList
  4. 初始化 ServerListUpdater:new PollingServerListUpdater(config)
  5. 初始化 ILoadBalancer:ZoneAwareLoadBalancer
  6. 初始化 ribbonServerListFilter:ZonePreferenceServerListFilter
  7. 初始化 ribbonLoadBalancerContext:RibbonLoadBalancerContext
  8. 初始化 serverIntrospector:DefaultServerIntrospector

对于 BlockingLoadBalancerClient:

Spring Cloud Hoxton.RELEASE 版本公布之后,新增了一个新的负载均衡器实现 BlockingLoadBalancerClient。它是第一个蕴含 阻塞式 非阻塞式 负载均衡器客户端实现的版本,作为已进入保护模式的 Netflix Ribbon 的代替计划。

如果想在 RestTemplate应用新的 BlockingLoadBalancerClient, 须要减少 spring-cloud-loadbalancer 的依赖,否则默认应用RibbonLoadBalancerClient

正文完
 0