Ribbon是Netflix公司提供的一个客户端负载工具,Spring Cloud也对其进行了集成支持。使用Ribbon需要在pom.xml中添加如下依赖。
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-ribbon</artifactId>
</dependency>
为了使用LoadBalancerClient,还需要在Classpath下存在RestTemplate,为此引入如下依赖。
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-web</artifactId>
</dependency>
然后在@Configuration
类上使用@org.springframework.cloud.netflix.ribbon.RibbonClient
声明一个Ribbon Client,指定Client的名称。如下代码声明了一个名称为hello
的Ribbon Client。
@SpringBootApplication
@RibbonClient("hello")
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
然后可以通过下面的方式指定服务端的地址,其中hello
是上面声明的Ribbon Client的名称。
hello.ribbon.listOfServers=http://localhost:8900,http://localhost:8901
然后可以在应用中注入org.springframework.cloud.client.loadbalancer.LoadBalancerClient
,并通过它来获取服务端地址了。
@SpringBootTest(classes=Application.class)
@RunWith(SpringRunner.class)
public class RibbonTest {
@Autowired
private LoadBalancerClient loadBalancerClient;
@Test
public void test() {
String serviceId = "hello";
for (int i=0; i<5; i++) {
ServiceInstance instance = this.loadBalancerClient.choose(serviceId);
System.out.println(i + ". " + instance.getUri());
}
}
}
上面的代码就通过LoadBalancerClient连续获取了5次服务端地址,运行代码你会看到如下这样的输出。
0. http://localhost:8900
1. http://localhost:8901
2. http://localhost:8900
3. http://localhost:8901
4. http://localhost:8900
Spring Cloud会注册Ribbon Client需要的IRule、IPing和ILoadBalancer等bean,它们由org.springframework.cloud.netflix.ribbon.RibbonClientConfiguration
负责注册。我们可以通过注册自己的相关类型的bean来覆盖默认的bean定义,比如下面的代码就指定了使用的IRule是基于轮询的RoundRobinRule实现。
@Configuration
public class RibbonConfiguration {
@Bean
public IRule rule() {
return new RoundRobinRule();
}
}
也可以通过@RibbonClients
的defaultConfiguration指定通用的配置信息。
@SpringBootApplication
@RibbonClient(value="hello")
@RibbonClients(defaultConfiguration=RibbonConfiguration.class)
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
也可以专门为某个Ribbon Client定义特殊配置信息,此时可以通过@RibbonClient
的configuration属性指定需要应用的@Configuration
配置类。
@SpringBootApplication
@RibbonClient(value="hello", configuration=RibbonConfiguration.class)
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
Ribbon Client的配置参数也可以通过application.properties进行配置,属性名格式是clientName.ribbon.property
。比如下面的配置指定了名称为hello的Ribbon Client的服务地址和使用的IRule。更多的配置参数可以参考com.netflix.client.config.CommonClientConfigKey
的API文档,这些参数的默认值可以参考com.netflix.client.config.DefaultClientConfigImpl
。
hello.ribbon.listOfServers=http://localhost:8900,http://localhost:8901
hello.ribbon.NFLoadBalancerRuleClassName=com.netflix.loadbalancer.RoundRobinRule
使用配置文件指定的参数比使用
@RibbonClient(value="hello", configuration=RibbonConfiguration.class)
指定的配置拥有更高的优先级。
Ribbon和Eureka一起使用
当应用中存在Eureka Client时,可以不通过hello.ribbon.listOfServers=http://localhost:8900,http://localhost:8901
写死hello客户端对应的服务地址。此时可以通过Eureka Client从Eureka Server获取服务地址,从而达到动态获取服务地址的目的。此时如果声明了名为hello的Ribbon Client,则会从Eureka Server获取serviceId为hello的服务对应的地址。
eureka.client.registerWithEureka=false
eureka.client.serviceUrl.defaultZone=http://localhost:8089/eureka/
如果应用中存在Eureka Client,但不希望通过Eureka Client来获取Ribbon Client对应的服务地址,可以指定ribbon.eureka.enabled=false
。
hello.ribbon.listOfServers=http://localhost:8900,http://localhost:8901
ribbon.eureka.enabled=false
eureka.client.registerWithEureka=false
eureka.client.serviceUrl.defaultZone=http://localhost:8089/eureka/
RestTemplate负载
RestTemplate可以和Ribbon一起使用,使其具备负载能力。在对应的RestTemplate对应的bean上加上@LoadBalanced
可以使其拥有负载能力。
@Configuration
public class RibbonConfiguration {
@Bean
public IRule rule() {
return new RoundRobinRule();
}
@Bean
@LoadBalanced
public RestTemplate restTemplate(RestTemplateBuilder builder) {
return builder.build();
}
}
在使用的时候需要把host改为对应的serviceId,比如有一个服务hello,我们想访问它提供的/api/abc
服务,即可以访问http://hello/api/abc
。内部在访问的时候会自动把hello替换为服务hello的一个具体地址(它可能是通过ribbon.listOfServers
指定的,也可能是直接从Eureka获取的)。
@Autowired
private RestTemplate restTemplate;
@GetMapping("hello")
public String hello() {
String result = this.restTemplate.getForObject("http://hello/api/hello/abc", String.class);
return result;
}
如果你的应用中需要同时有多个RestTemplate,有的需要有负载均衡功能,有的不需要有,则可以在应用中创建多个RestTemplate类型的bean,然后根据需要用@Primary
指定一个为自动注入时默认使用的。比如下面我们定义了默认注入的是拥有负载均衡功能的RestTemplate。
@Bean
@LoadBalanced
@Primary
public RestTemplate restTemplate(RestTemplateBuilder builder) {
return builder.build();
}
@Bean
public RestTemplate commonRestTemplate(RestTemplateBuilder builder) {
return builder.build();
}
WebClient负载
方式一
WebClient也是可以负载的,与RestTemplate类似,它需要定义一个WebClient.Builder
类型的bean,并使用@LoadBalanced
标注。
@Bean
@LoadBalanced
public WebClient.Builder webClientBuilder() {
return WebClient.builder();
}
然后使用的时候就注入WebClient.Builder
,通过它创建一个WebClient对象。WebClient进行访问时URI中的host也是需要使用服务名。
@Autowired
private WebClient.Builder webClientBuilder;
@GetMapping("webClient/hello")
public Mono<String> hello() {
return this.webClientBuilder.build().get().uri("http://hello/api/hello/abc").retrieve().bodyToMono(String.class);
}
方式二
方式二是注入一个LoadBalancerExchangeFilterFunction类型的bean,该bean将由Spring Cloud自动创建。然后在通过WebClient.Builder创建WebClient时指定该ExchangeFilterFunction,这样创建出来的WebClient也是具有负载功能的。
@Autowired
private LoadBalancerExchangeFilterFunction lbFunction;
@GetMapping("webClient/lbFunction")
public Mono<String> lbFunction() {
return WebClient.builder().baseUrl("http://hello")
.filter(lbFunction)
.build()
.get()
.uri("/api/hello/abc")
.retrieve()
.bodyToMono(String.class);
}
自动重试
Ribbon和Spring Retry一起使用的时候可以在调用远程服务失败时发起重试。需要先加入Spring retry依赖。
<dependency>
<groupId>org.springframework.retry</groupId>
<artifactId>spring-retry</artifactId>
</dependency>
然后可以通过MaxAutoRetries指定最多重试次数,比如ribbon.MaxAutoRetries=2
指定所有的Ribbon客户端在发起请求时最多重试两次,第一次调用不算在重试次数中。可以通过MaxAutoRetriesNextServer配置最多重试的服务器数量,第一个服务器是不算的。比如MaxAutoRetries=2,MaxAutoRetriesNextServer=1,那么会在第一台服务器上最多调用3次,在第二台服务器上也最多调用3次,如果还有第三台服务器,则第三台服务器不会再调用了。Ribbon默认只对GET请求进行重试,如果需要对POST请求也进行重试,则可以配置ribbon.OkToRetryOnAllOperations=true
。还可以通过retryableStatusCodes来指定需要进行重试的Http状态码,比如只希望在状态码为500或502时进行重试,则配置ribbon.retryableStatusCodes=500,502
。默认情况只要服务器通讯正常都不会重试,即状态码不管是404还是502等都不会发起重试,只有建立连接失败或者请求超时会重试。所以如果我们需要在状态码为502的时候也能发起重试则需要指定retryableStatusCodes。
ribbon.MaxAutoRetries=2
ribbon.MaxAutoRetriesNextServer=2
ribbon.OkToRetryOnAllOperations=true
ribbon.retryableStatusCodes=404,502
示例代码如下。
@GetMapping("retry/{sub}")
public String retryAny(@PathVariable("sub") String sub) {
String result = this.restTemplate.getForObject("http://hello/{sub}", String.class, sub);
return result;
}
如果Classpath下存在Spring Retry的相关jar包,但是又不希望使用它,则可以指定spring.cloud.loadbalancer.retry.enabled=false
。