如何使用Spring Cloud Ribbon进行负载均衡调用?
前面写的Eureka Client,服务消费者调用服务消费者的时候,是通过负载均衡方式调用的。这里再回顾下当时的用法:
1.在启动类中,声明一个RestTemplate的bean,用@LoadBalanced注解修饰:
@Bean
@LoadBalanced
public RestTemplate restTemplate() {
return new RestTemplate();
}
2.在进行http调用的地方,通过@Autowired方式,注入RestTemplate,再通过restTemplate调用。调用时,写服务名就可以,比如按照下面这种方式调用。
return this.restTemplate.getForEntity("http://MICROSERVICE-PROVIDER/provider", String.class).getBody();
通过这两步操作,进行调用时,就可以通过服务名,得到多个服务实例的信息,通过负载均衡的方式进行调用。
从上面的步骤看来,使用负载均衡的开发量还是挺简单的,不过为什么在restTemplate上加个注解,通过restTemplate进行调用,就可以做到负载均衡呢,这里就需要研究下源码才能知道原因了。
Spring Cloud Ribbon源码分析
既然是在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 {
}
从注释中可以知道,这个注解是用来给RestTemplate做标记,以使用负载均衡客户端(LoadBalancerClient)来配置它。所以,我们在生成的RestTemplate的bean上添加这么一个注解,这个bean就会配置LoadBalancerClient。
那么,就再看下LoadBalancerClient的代码:
/**
* Represents a client side load balancer
* @author Spencer Gibb
*/
public interface LoadBalancerClient {
/**
* Choose 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);
/**
* execute 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
* @return the result of the LoadBalancerRequest callback on the selected
* ServiceInstance
*/
<T> T execute(String serviceId, LoadBalancerRequest<T> request) throws IOException;
/**
* Create a proper URI with a real host and port for systems to utilize.
* Some systems use a URI with the logical serivce 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
* @param original a URI with the host as a logical service name
* @return a reconstructed URI
*/
URI reconstructURI(ServiceInstance instance, URI original);
}
LoadBalancerClient是一个接口,里面有三个方法。
第一个,ServiceInstance choose(String serviceId);从方法名上就可以看出,是根据传入的serviceId(服务名),从负载均衡器中选择一个服务实例,服务实例通过ServiceInstance类来表示。
第二个,execute方法,使用从负载均衡器中选择的服务实例来执行请求内容。
第三个,URI reconstructURI(ServiceInstance instance, URI original);方法,是重新构建一个URI的,还记得我们在代码中,通过RestTemplate请求服务时,写的是服务名吧,这个方法就会把这个请求的URI进行转换,返回host+port,通过host+port的形式去请求服务。
从工程中搜索LoadBalancerClient接口的实现类,可以找到RibbonLoadBalancerClient类实现了这个接口,并且实现了接口中定义的方法。
再梳理一下逻辑,我们在RestTemplate上添加了@LoadBalanced注解,RibbonLoadBalancerClient就会配置到这个RestTemplate实例上。
在LoadBalancerClient接口的同一个包路径下,还会看到另一个LoadBalancerAutoConfiguration类,看名字就感觉这是一个自动配置LoadBalancer的,进去这个类看一下。
/**
* Auto configuration for Ribbon (client side load balancing).
*
* @author Spencer Gibb
* @author Dave Syer
*/
@Configuration
@ConditionalOnClass(RestTemplate.class)
@ConditionalOnBean(LoadBalancerClient.class)
public class LoadBalancerAutoConfiguration {
@LoadBalanced
@Autowired(required = false)
private List<RestTemplate> restTemplates = Collections.emptyList();
@Bean
public SmartInitializingSingleton loadBalancedRestTemplateInitializer(
final List<RestTemplateCustomizer> customizers) {
return new SmartInitializingSingleton() {
@Override
public void afterSingletonsInstantiated() {
for (RestTemplate restTemplate : LoadBalancerAutoConfiguration.this.restTemplates) {
for (RestTemplateCustomizer customizer : customizers) {
customizer.customize(restTemplate);
}
}
}
};
}
@Bean
@ConditionalOnMi