前言
前情回顧
前面文章已經梳理清楚了Eureka相關的概念及源碼,接下來開始研究下Ribbon的實現原理。
我們都知道Ribbon在spring cloud中擔當負載均衡的角色, 當兩個Eureka Client互相調用的時候,Ribbon能夠做到調用時的負載,保證多節點的客戶端均勻接收請求。(這個有點類似於前端調用后端時Nginx做的負載均衡)
本講目錄
本講主通過一個簡單的demo來了解ribbon內部實現,這里主要是對ribbon有個宏觀的認識,后續篇章會一步步通過debug的方式對ribbon的細節做一個全面的講解。
目錄如下:
- 一個demo來看看ribbon是做什么的
- @LoadBalanced初探
- LoadBalancerAutoConfiguration初探
- RibbonLoadBalancerClient初探
說明
原創不易,如若轉載 請標明來源!
博客地址:一枝花算不算浪漫
微信公眾號:壹枝花算不算浪漫 (文章底部有公眾號二維碼)
Ribbon正文
一個demo來看看ribbon是做什么的
首先看下我們這里的demo,目錄結構如下:
這里有3個模塊,eurekaServer作為注冊中心,serviceA和serviceB分別作為EurekaClient。
代碼地址上傳到了自己的git:
https://github.com/barrywangmeng/spring-cloud-learn
ribbon相關的類結構信息
-
啟動了eureka client如下:
服務A 2個: 一個端口號為8087,另一個為8088
服務B 1個
-
查看注冊中心Dashboard
-
服務B調用服務A中的接口
-
查看負載均衡情況
第一次調用服務B的greeting方法:
-
第二次調用服務A的greeting方法:
這里可以看到服務A調用的時候加了一個注解: @LoadBalanced
服務B第一次調用到了服務A的8088那個節點
服務B第二次調用到了服務A的8087那個節點
這里就可以證明使用@LoadBalanced
自動對我們的http請求加了負載均衡,接下來我們就用@LoadBalanced
來一步步往下看。
@LoadBalanced初探
接下來看下@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@Qualifierpublic @interface LoadBalanced {}
這里主要看注釋,這里意思是使用這個注解后可以將普通的RestTemplate
使用 LoadBalanceClient
這個類去處理。
接着我們看下LoadBalanced
相關的配置。
LoadBalancerAutoConfiguration初探
我們知道,springboot + springcloud 對應的組件都會有相應的XXXAutoConfigure配置類,同理,我們在LoadBalanced
同級包下可以找到對應的AutoConfigure類:LoadBalancerAutoConfiguration
, 先看下類的定義:
@Configuration
@ConditionalOnClass(RestTemplate.class)
@ConditionalOnBean(LoadBalancerClient.class)
@EnableConfigurationProperties(LoadBalancerRetryProperties.class)
public class LoadBalancerAutoConfiguration {
}
看到這里有個 @ConditionalOnClass(RestTemplate.class)
,這個含義是 只有存在RestTemplate
這個類的時該配置才會生效。
接着看LoadBalancerAutoConfiguration
中的一些方法:
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);
}
}
}
};
}
}
我們可以看下loadBalancedRestTemplateInitializer
方法,這個里面會遍歷restTemplates
然后調用customize()
方法進行特殊處理。
public class LoadBalancerAutoConfiguration {
@LoadBalanced
@Autowired(required = false)
private List<RestTemplate> restTemplates = Collections.emptyList();
@Configuration
@ConditionalOnMissingClass("org.springframework.retry.support.RetryTemplate")
static class LoadBalancerInterceptorConfig {
@Bean
public LoadBalancerInterceptor ribbonInterceptor(
LoadBalancerClient loadBalancerClient,
LoadBalancerRequestFactory requestFactory) {
return new LoadBalancerInterceptor(loadBalancerClient, requestFactory);
}
@Bean
@ConditionalOnMissingBean
public RestTemplateCustomizer restTemplateCustomizer(
final LoadBalancerInterceptor loadBalancerInterceptor) {
return new RestTemplateCustomizer() {
@Override
public void customize(RestTemplate restTemplate) {
List<ClientHttpRequestInterceptor> list = new ArrayList<>(
restTemplate.getInterceptors());
list.add(loadBalancerInterceptor);
restTemplate.setInterceptors(list);
}
};
}
}
}
這里面是為每一個restTemplate
添加一個loadBalancerInterceptor
攔截器,緊接着看一下LoadBalancerInterceptor.java
public class LoadBalancerInterceptor implements ClientHttpRequestInterceptor {
private LoadBalancerClient loadBalancer;
private LoadBalancerRequestFactory requestFactory;
@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, requestFactory.createRequest(request, body, execution));
}
}
這里面就很簡單了,將serviceName(這里就是對應我們demo中的:ServiceA)和request、body、excution等組成的新的request傳遞給LoadBalancerClient
,然后調用其中的execute
,這個方法的實現繼續往下看RibbonLoadBalancerClient
RibbonLoadBalancerClient初探
接下來再看一下 LoadBalanceClient
:
public interface LoadBalancerClient extends ServiceInstanceChooser {
<T> T execute(String serviceId, LoadBalancerRequest<T> request) throws IOException;
<T> T execute(String serviceId, ServiceInstance serviceInstance, LoadBalancerRequest<T> request) throws IOException;
URI reconstructURI(ServiceInstance instance, URI original);
}
這個接口只有一個實現類:RibbonLoadBalancerClient
, 那么我們繼續看實現類中的execute方法:
@Override
public <T> T execute(String serviceId, LoadBalancerRequest<T> request) throws IOException {
ILoadBalancer loadBalancer = getLoadBalancer(serviceId);
Server server = getServer(loadBalancer);
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);
}
接着就可以在這個方法上愉快的debug了,我們先看看ILoadBalancer
是干嘛的:
我們通過debug可以看到 獲取的ILoadBalancer
已經獲取到服務A所有的節點信息了,這一章就先不延伸下去了,后面會詳細來說ILoadBalancer
處理的細節。
總結
這一篇主要講解了一個RestTemplate
加上@LoadBalanced
注解后是如何獲取到請求服務的多個節點信息的,通過debug 我們可以很清晰的看到請求流程,最后畫一個圖來總結一下:
申明
本文章首發自本人博客:https://www.cnblogs.com/wang-meng 和公眾號:壹枝花算不算浪漫,如若轉載請標明來源!
感興趣的小伙伴可關注個人公眾號:壹枝花算不算浪漫