RestTemplate与LoadBalanced
前言
在SpringCloud的微服务场景中,服务之间调用,我们通常使用Feign来实现。不过通过RestTemplate也能实现对服务请求的负载均衡,本文对RestTemplate的负载均很实现方式做一下简单的分析说明。
关于RestTemplate的原理 RestTemplate源码分析一文中已做说明,本文不再细说。
RestTemplate实现负载均
- 定义微服务注册中心
本文以consul作为注册中心,注册中心的配置启动,这里不做细说。 - 服务提供方多个实例,并且都注册到了注册中心
如上图所示,服务提供方service-provider注册了两个实例,都已注册到注册中心,端口分别为9090,9099 - 调用方使用RestTemplate的条件
I、服务发现: 客户端在使用之前需要知道服务提供方的接口请求地址。
<--项目中需要引入ribbon-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-ribbon</artifactId>
</dependency>
II、声明RestTemplate
@LoadBalanced
@Bean
public RestTemplate restTemplate(){
return new RestTemplate();
}
III、在代码中使用
@Autowired
private RestTemplate restTemplate;
@GetMapping("rest/test")
public Response<UserDto> getUserInfoByRestTemplate(@RequestParam("userId") String userId) {
ParameterizedTypeReference<Response<UserDto>> responseBodyType = new ParameterizedTypeReference<Response<UserDto>>() {};
ResponseEntity<Response<UserDto>> result = restTemplate.exchange("http://service-provider/inner/entities?userId={userId}", HttpMethod.GET,
new HttpEntity<>(null),responseBodyType,userId);
log.info("get service provider result:{}",result.getBody());
return result.getBody();
}
//执行两次请求后,打印日志如下。
2021-10-30 21:00:47.050 INFO 77602 --- [nio-8080-exec-1] c.z.d.interfaces.SomeController : get service provider result:Response{code=200, message='null', data=UserDto(id=2, userName=name2,port:9090)}
2021-10-30 21:00:58.806 INFO 77602 --- [nio-8080-exec-5] c.z.d.interfaces.SomeController : get service provider result:Response{code=200, message='null', data=UserDto(id=2, userName=name2,port:9099)}
可以发现,通过以上步骤,RestTemplate已经实现了负载均很
RestTemplate实现负载均原理
LoadBalanced注解
LoadBalanced是一个注解,在其申明中包含了@Qualifier注解。我们可以简单理解LoadBalanced注解是一个标记,在我们需要使用必需带有这个标记注解的时候,只需要再次引用LoadBalanced注解即可。
@LoadBalanced
@Bean
public RestTemplate restTemplate1(){
return new RestTemplate();
}
@LoadBalanced
@Bean
public RestTemplate restTemplate2(){
return new RestTemplate();
}
@Bean
public RestTemplate restTemplate3(){
return new RestTemplate();
}
.......
@LoadBalanced
@Autowired(required = false)
private List<RestTemplate> restTemplates = Collections.emptyList();
//这里的restTemplates包含了restTemplate1,restTemplate2,不包含restTemplate3
RestTemplateCustomizer
RestTemplateCustomizer是一个接口,定义了对RestTemplate的扩展。RestTemplate源码分析一文中介绍了RestTemplate各个组件,都是可以被我们自定义扩展的。
我们可以在RestTemplateCustomizer的实现类中修改RestTemplate的HttpMessageConverter、ClientHttpRequestFactory、ClientHttpRequestInterceptor等等。
LoadBalancerAutoConfiguration
LoadBalancerAutoConfiguration这是一个自动配置类。下图中红色1部分,表示收集了所有被LoadBalanced标记过的RestTemplate对象,红色2部分,表示如果RestTemplateCustomizer对象有具体的实现bean,则用他们对所有收集到的RestTemplate对象执行了自定义修改RestTemplate的操作。
通过以上两部分我们基本可以猜测,ribbon中必然实现了自定义的RestTemplateCustomizer对象,这个对象会扩展RestTemplate的拦截器接口ClientHttpRequestInterceptor,只有这样我们才可以在请求前将服务名替换为具体的ip地址和端口号。
如上图,在LoadBalancerInterceptorConfig中(它是LoadBalancerAutoConfiguration的内部类),声明了LoadBalancerInterceptor的bean对象,LoadBalancerInterceptor实现了接口ClientHttpRequestInterceptor。并且LoadBalancerInterceptor通过一个匿名RestTemplateCustomizer对象,最终添加到RestTemplate的interceptors中。
LoadBalancerInterceptor
通过以上分析,我们已经知道RestTemplate的最终负载均很扩展需要靠LoadBalancerInterceptor的实现。
在LoadBalancerInterceptor的intercept的拦截器方法中,具体的服务名如何与ip:port进行替换,委托给了LoadBalancerClient对象。LoadBalancerClient是Ribbon的核心类,本文不做特别分析,后续文章会对次做详细讲解。
总结
至此,我们已经明白RestTemplate是如何通过@LoadBalanced注解实现负载均很的。
- 声明一个带@LoadBalanced注解的RestTemplate对象
- Ribbon的自动配置类LoadBalancerAutoConfiguration会收集所有被@LoadBalanced标记过的RestTemplate
- Ribbon中的类LoadBalancerInterceptor实现RestTemplate的扩展接口ClientHttpRequestInterceptor,LoadBalancerInterceptor类将负载均衡委托给Ribbon的底层类。
- Ribbon将LoadBalancerInterceptor类通过RestTemplateCustomizer最终注册到收集的RestTemplate的intercepts中。
- 这样,RestTemplate通过加载ClientHttpRequestInterceptor的扩展,就实现了负载均衡