前提
在閱讀這篇博客之前,希望你對SpringCloud套件熟悉和理解,更希望關注下微服務開發平台
概述
在使用springcloud ribbon客戶端負載均衡的時候,可以給RestTemplate bean 加一個@LoadBalanced注解,就能讓這個RestTemplate在請求時擁有客戶端負載均衡的能力,先前有細嚼過但是沒有做過筆記,剛好處理此類問題記錄下
@LoadBalanced
/**
* 注釋將RestTemplate bean標記為配置為使用LoadBalancerClient。
*/
@Target({ ElementType.FIELD, ElementType.PARAMETER, ElementType.METHOD })
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@Qualifier
public @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();
@Bean
public 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
@ConditionalOnMissingBean
public 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 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));
}
}
重點看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 {
@Autowired
Environment environment;
/**
* DiscoveryHeaderHelper默認bean
* @return
*/
@Bean
public DiscoveryHeaderHelper discoveryHeaderHelper() {
DiscoveryHeaderHelper discoveryHeaderHelper = new DiscoveryHeaderHelper(environment);
DiscoveryHeaderHelper.INSTANCE = discoveryHeaderHelper;
return discoveryHeaderHelper;
}
/**
* resttemplate構建
*/
@Resource
private RestTemplateBuilder restTemplateBuilder;
/**
* resttemplate請求bean,更改系統本身的builder
* @return
*/
@Bean
@LoadBalanced
public 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;
}
@Bean
public DiscoveryClient.DiscoveryClientOptionalArgs discoveryClientOptionalArgs() {
DiscoveryClient.DiscoveryClientOptionalArgs discoveryClientOptionalArgs = new DiscoveryClient.DiscoveryClientOptionalArgs();
discoveryClientOptionalArgs.setAdditionalFilters(Collections.singletonList(new DiscoveryHeaderClientFilter()));
discoveryClientOptionalArgs.setEventListeners(Collections.singleton(new EurekaClientEventListener()));
return discoveryClientOptionalArgs;
}
}
源碼地址 > DiscoveryConfig