概述
在使用springcloud ribbon客户端负载均衡的时候,可以给RestTemplate bean 加一个@LoadBalanced注解,就能让这个RestTemplate在请求时拥有客户端负载均衡的能力,先前有细嚼过但是没有做过笔记,刚好处理此类问题记录下
@LoadBalanced
/*** 注释将RestTemplate bean标记为配置为使用LoadBalancerClient。*/@Target({ ElementType.FIELD, ElementType.PARAMETER, ElementType.METHOD })@Retention(RetentionPolicy.RUNTIME)@Documented@Inherited@Qualifierpublic @interface LoadBalanced {}
通过源码可以发现这是一个LoadBalanced标记注解并且标记了@Qualifier(基于Spring Boot的自动配置机制),我们可以溯源到LoadBalancerAutoConfiguration
LoadBalancerAutoConfiguration
/*** 功能区的自动配置(客户端负载平衡)*/@Configuration@ConditionalOnClass(RestTemplate.class)@ConditionalOnBean(LoadBalancerClient.class)@EnableConfigurationProperties(LoadBalancerRetryProperties.class)public class LoadBalancerAutoConfiguration {@LoadBalanced@Autowired(required = false)private List<RestTemplate> restTemplates = Collections.emptyList(); //这里持有@LoadBalanced标记的RestTemplate实例@Autowired(required = false)private List<LoadBalancerRequestTransformer> transformers = Collections.emptyList();@Beanpublic SmartInitializingSingleton loadBalancedRestTemplateInitializerDeprecated(final ObjectProvider<List<RestTemplateCustomizer>> restTemplateCustomizers) {return () -> restTemplateCustomizers.ifAvailable(customizers -> {for (RestTemplate restTemplate : LoadBalancerAutoConfiguration.this.restTemplates) {for (RestTemplateCustomizer customizer : customizers) {//为restTemplate添加定制customizer.customize(restTemplate);}}});}// .../*** 以下针对classpath存在RetryTemplate.class的情况配置,先忽略*/@Configuration@ConditionalOnClass(RetryTemplate.class)public static class RetryAutoConfiguration {@Bean@ConditionalOnMissingBeanpublic LoadBalancedRetryFactory loadBalancedRetryFactory() {return new LoadBalancedRetryFactory() {};}}// ...}
@LoadBalanced和@Autowried结合使用,意思就是这里注入的RestTempate Bean是所有加有@LoadBalanced注解标记的(持有@LoadBalanced标记的RestTemplate实例)
这段自动装配的代码的含义不难理解,就是利用了RestTempllate的拦截器,使用RestTemplateCustomizer对所有标注了@LoadBalanced的RestTemplate Bean添加了一个LoadBalancerInterceptor拦截器,而这个拦截器的作用就是对请求的URI进行转换获取到具体应该请求哪个服务实例ServiceInstance。
关键问下自己:为什么?
- RestTemplate实例是怎么被收集的?
- 怎样通过负载均衡规则获取具体的具体的server?
继续扒看源码>
上面可以看出,会LoadBalancerAutoConfiguration类对我们加上@LoadBalanced注解的bean 添加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 compatibilitythis(loadBalancer, new LoadBalancerRequestFactory(loadBalancer));}@Overridepublic 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));}}
重点看intercept方法 当我们restTemplate执行请求操作时,就会被拦截器拦截进入intercept方法,而loadBalancer是LoadBalancerClient的具体实现
RibbonLoadBalancerClient
public <T> T execute(String serviceId, LoadBalancerRequest<T> request, Object hint)throws IOException {ILoadBalancer loadBalancer = getLoadBalancer(serviceId);Server server = getServer(loadBalancer, hint);if (server == null) {throw new IllegalStateException("No instances available for " + serviceId);}RibbonServer ribbonServer = new RibbonServer(serviceId, server,isSecure(server, serviceId),serverIntrospector(serviceId).getMetadata(server));return execute(serviceId, ribbonServer, request);}
看到这里相信都遇到过类似的错误,恍然大悟
No instances available for xxxxx
总结
- 1.根据serviceId 获取对应的loadBalancer
- 2.根据loadBalancer获取具体的server(这里根据负载均衡规则,获取到具体的服务实例)
- 3.创建RibbonServer
- 4.执行具体请求
这里
注意: @LoadBalanced 标记注解获取到最后通过负载均衡规则获取具体的具体的server来发起请求
案例
/*** 服务注册中心配置** @author <a href="mailto:shangzhi.ibyte@gmail.com">iByte</a>* @since 1.0.1*/@Configuration@EnableConfigurationProperties(ModuleMappingHelper.class)public class DiscoveryConfig {@AutowiredEnvironment environment;/*** DiscoveryHeaderHelper默认bean* @return*/@Beanpublic DiscoveryHeaderHelper discoveryHeaderHelper() {DiscoveryHeaderHelper discoveryHeaderHelper = new DiscoveryHeaderHelper(environment);DiscoveryHeaderHelper.INSTANCE = discoveryHeaderHelper;return discoveryHeaderHelper;}/*** resttemplate构建*/@Resourceprivate RestTemplateBuilder restTemplateBuilder;/*** resttemplate请求bean,更改系统本身的builder* @return*/@Bean@LoadBalancedpublic RestTemplate restTemplate() {RestTemplate restTemplate = restTemplateBuilder.configure(new RestTemplate());//RestTemplate interceptors 远程调用请求增加头部信息处理restTemplate.getInterceptors().add(new RestApiHeaderInterceptor());//RestTemplate Set the error handler 错误处理restTemplate.setErrorHandler(new RestResponseErrorHandler());return restTemplate;}@Beanpublic DiscoveryClient.DiscoveryClientOptionalArgs discoveryClientOptionalArgs() {DiscoveryClient.DiscoveryClientOptionalArgs discoveryClientOptionalArgs = new DiscoveryClient.DiscoveryClientOptionalArgs();discoveryClientOptionalArgs.setAdditionalFilters(Collections.singletonList(new DiscoveryHeaderClientFilter()));discoveryClientOptionalArgs.setEventListeners(Collections.singleton(new EurekaClientEventListener()));return discoveryClientOptionalArgs;}}
源码地址 > DiscoveryConfig
