5
头图

文章首发: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


MaiSR
27 声望5 粉丝

不积跬步,无以至千里