文章首发: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: 8761eureka: instance: hostname: localhost client: registerWithEureka: false fetchRegistry: false serviceUrl: defaultZone: http://${eureka.instance.hostname}:${server.port}/eureka/
启动类
@SpringBootApplication@EnableEurekaServerpublic 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-bserver: port: 7777eureka: client: serviceUrl: defaultZone: http://localhost:8761/eureka/ instance: prefer-ip-address: true
b2
spring: application: name: ribbon-service-bserver: port: 7778eureka: client: serviceUrl: defaultZone: http://localhost:8761/eureka/ instance: prefer-ip-address: true
b3
spring: application: name: ribbon-service-bserver: port: 7779eureka: client: serviceUrl: defaultZone: http://localhost:8761/eureka/ instance: prefer-ip-address: true
b1、b2、b3的controller
@RestControllerpublic 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@EnableDiscoveryClientpublic class ServiceB1Application { public static void main(String[] args) { SpringApplication.run(ServiceB1Application.class, args); }}@SpringBootApplication@EnableDiscoveryClientpublic class ServiceB2Application { public static void main(String[] args) { SpringApplication.run(ServiceB2Application.class, args); }}@SpringBootApplication@EnableDiscoveryClientpublic 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-aserver: port: 7776eureka: client: serviceUrl: defaultZone: http://localhost:8761/eureka/ instance: prefer-ip-address: true
启动类,RestTemplate应用了@LoadBalanced
,这样RestTemplate就开启了ribbon的负载平衡了。
@SpringBootApplication@EnableDiscoveryClientpublic 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超过配置的阈值) |
响应工夫加权策略 | 曾经被弃用,作用同WeightedResponseTimeRule | |
WeightedResponseTimeRule | 响应工夫加权策略 | 依据Server的响应工夫调配权重,响应工夫越长,权重越低,被选中的概率就越低。响应工夫越短,权重越高,被抉择到的概率越高 |
ZoneAvoidanceRule | 区域衡量策略 | 综合判断Server所在区域的性能和Server的可用性轮询抉择Server,并且判断一个AWS Zone的运行性能是否可用,剔除不可用的Zone中的所有Server |
在下面的入门案例中Ribbon的默认负载平衡策略是轮询策略,
Ribbon自定义配置负载平衡策略
全局配置
应用Ribbon时配置全局的负载平衡策略,须要加一个配置类。改配置类须要被@ComponentScan
扫描到能力全局失效。
@Configurationpublic class GlobalRuleConfig { @Bean public IRule ribbonRule() { return new RandomRule(); }}
下面配置了随机的策略,屡次拜访http://localhost:7776/ribbon/...
基于@RibbonClient
或@RibbonClients
注解的配置
配置类,留神:编写自定义配置类,须要特地留神的是官网文档明确给出了正告:这个自定义配置类不能放在@ComponentScan所扫描的包以及其子包下(即不能放在主启动类所在的包及其子包下,因而咱们须要新建一个包来放该配置类),否则咱们自定义的这个配置类就会被所有的Ribbon客户端所共享,也就达不到特殊化定制的目标了
@Configurationpublic 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@Qualifierpublic @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。
从这些办法的性能能够晓得这两个接口的重要性了。这两个接口的同一包下有一个类LoadBalancerAutoConfiguration
。LoadBalancerAutoConfiguration
在org.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
中初始化了下面表格提到几个外围类
- 初始化ribbonRule: ZoneAvoidanceRule
- 初始化ribbonPing:DummyPing
- 初始化ribbonServerList:ConfigurationBasedServerList
- 初始化ServerListUpdater:new PollingServerListUpdater(config)
- 初始化ILoadBalancer:ZoneAwareLoadBalancer
- 初始化ribbonServerListFilter:ZonePreferenceServerListFilter
- 初始化ribbonLoadBalancerContext:RibbonLoadBalancerContext
- 初始化serverIntrospector:DefaultServerIntrospector
对于BlockingLoadBalancerClient:
Spring Cloud Hoxton.RELEASE 版本公布之后,新增了一个新的负载均衡器实现BlockingLoadBalancerClient
。它是第一个蕴含阻塞式
和非阻塞式
负载均衡器客户端实现的版本,作为已进入保护模式的Netflix Ribbon的代替计划。
如果想在 RestTemplate
应用新的 BlockingLoadBalancerClient
, 须要减少 spring-cloud-loadbalancer
的依赖,否则默认应用RibbonLoadBalancerClient
。