在上篇文章Ribbon架構剖析中,我們已經介紹了Ribbon的架構組成以及很多重要的對象,相信你已經對Ribbon已經有一個清晰的認識了。本篇文章則研究一下Ribbon的原理
首先我們知道,在普通項目中Ribbon的使用是這樣的
@SpringBootApplication
@RibbonClient(name = "provider-demo", configuration = cn.org.config.LoadBalanced.class)
public class CloudDemoConsumerApplication {
@Bean
@LoadBalanced
public RestTemplate restTemplate(){
return new RestTemplate();
}
public static void main(String[] args) {
SpringApplication.run(CloudDemoConsumerApplication.class, args);
}
}
這里面最引人矚目的就是注解@RibbonClient
了,看一下這個注解都是做了什么吧
@RibbonClient
觀察@RibbonClient
的源碼可知,這個注解使用@Import
注解引入了配置類RibbonClientConfigurationRegistrar
,看一下這個類的registerBeanDefinitions
方法
public void registerBeanDefinitions(AnnotationMetadata metadata,
BeanDefinitionRegistry registry) {
Map<String, Object> attrs = metadata.getAnnotationAttributes(
RibbonClients.class.getName(), true);
if (attrs != null && attrs.containsKey("value")) {
AnnotationAttributes[] clients = (AnnotationAttributes[]) attrs.get("value");
for (AnnotationAttributes client : clients) {
registerClientConfiguration(registry, getClientName(client),
client.get("configuration"));
}
}
if (attrs != null && attrs.containsKey("defaultConfiguration")) {
String name;
if (metadata.hasEnclosingClass()) {
name = "default." + metadata.getEnclosingClassName();
} else {
name = "default." + metadata.getClassName();
}
registerClientConfiguration(registry, name,
attrs.get("defaultConfiguration"));
}
Map<String, Object> client = metadata.getAnnotationAttributes(
RibbonClient.class.getName(), true);
String name = getClientName(client);
if (name != null) {
registerClientConfiguration(registry, name, client.get("configuration"));
}
}
- 首先會判斷是否存在注解
@RibbonClients
,注意,這里可是多了一個s的 - 然后判斷
@RibbonClients
注解上是否存在屬性value
和defaultConfiguration
,如果存在的話分別注冊他們 - 接着最后才是處理
@RibbonClient
注解 - 這里我們就可以猜測
RibbonClientConfigurationRegistrar
這個類應該是可以同時處理這兩個注解的,觀察一下@RibbonClients
注解的源碼發現它確實是引入的也是這個類 - 這兩個注解的區別應該也可以猜測出來,單數和雙數
- 觀察最后注冊的代碼,可以看到最后注冊bean的類型都是
RibbonClientSpecification
,這里留意一下
private void registerClientConfiguration(BeanDefinitionRegistry registry,
Object name, Object configuration) {
BeanDefinitionBuilder builder = BeanDefinitionBuilder
.genericBeanDefinition(RibbonClientSpecification.class);
builder.addConstructorArgValue(name);
builder.addConstructorArgValue(configuration);
registry.registerBeanDefinition(name + ".RibbonClientSpecification",
builder.getBeanDefinition());
}
自動裝配
上方看完這些代碼之后,我們了解了@RibbonClients
和@RibbonClient
兩個注解,可以對整體的流程還是有些疑惑。那么接下來就看看自動裝配都是做了什么吧
查看Ribbon包下的spring.factories
文件,發現引入了一個配置類RibbonAutoConfiguration
,那么從這個類開始看起吧
先決條件
@ConditionalOnClass
,當前環境必須存在這幾個類:IClient
,RestTemplate
,AsyncRestTemplate
,Ribbon
@RibbonClients
,這個注解剛才已經講過了,暫且不提@AutoConfigureAfter
,負載均衡肯定是要基於注冊中心來做的,所以自動裝配是在Eureka初始化完畢之后初始化的@AutoConfigureBefore
,這里的兩個類先不說,保持神秘@EnableConfigurationProperties
,兩個配置類,其中:RibbonEagerLoadProperties
類中是關於Ribbon的飢餓加載模式的屬性ServerIntrospectorProperties
類中是關於安全端口的屬性
裝配bean
這個配置類加載的類挺多的,但是比較重要的有這幾個:
SpringClientFactory
,我們知道每一個微服務在都會調用多個微服務,而調用各個微服務的配置可能是不一樣的,所以就需要這個創建客戶端負載均衡器的工廠類,它可以為每一個ribbon客戶端生成不同的Spring上下文,而觀察這個類的configurations
屬性也驗證了這一點
@Autowired(required = false)
private List<RibbonClientSpecification> configurations = new ArrayList<>();
@Bean
public SpringClientFactory springClientFactory() {
SpringClientFactory factory = new SpringClientFactory();
factory.setConfigurations(this.configurations);
return factory;
}
RibbonLoadBalancerClient
,持有SpringClientFactory
對象,當然,它還有其他的功能,這里暫且不提
負載均衡
上方雖然看了Ribbon的自動裝配功能,但是好像離真相還有一些距離,這是因為雖然Ribbon准備好了,但是負載均衡還沒看呢。SpringCloud把負載均衡相關的自動配置放在了spring-cloud-commons包下
負載均衡的配置類是LoadBalancerAutoConfiguration
這個類里注冊的幾個bean就比較核心了
LoadBalancerInterceptor
客戶端請求攔截器
RestTemplateCustomizer
用於給所有的RestTemplate
增加攔截器
@Bean
@ConditionalOnMissingBean
public RestTemplateCustomizer restTemplateCustomizer(
final LoadBalancerInterceptor loadBalancerInterceptor) {
return restTemplate -> {
List<ClientHttpRequestInterceptor> list = new ArrayList<>(
restTemplate.getInterceptors());
list.add(loadBalancerInterceptor);
restTemplate.setInterceptors(list);
};
}
負載均衡核心實現
現在我們就可以猜測,整個核心應該就是在這個攔截器上了,看一看攔截器的核心方法:
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));
}
其中requestFactory.createRequest(request, body, execution)
方法是為了把請求參數封裝為request
重點關注execute
方法
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);
}
創建負載均衡器
我們知道,每個Ribbon客戶端的負載均衡器都是唯一的,第一行getLoadBalancer
就會去創建這個負載均衡器
protected ILoadBalancer getLoadBalancer(String serviceId) {
return this.clientFactory.getLoadBalancer(serviceId);
}
public ILoadBalancer getLoadBalancer(String name) {
return getInstance(name, ILoadBalancer.class);
}
public <C> C getInstance(String name, Class<C> type) {
C instance = super.getInstance(name, type);
if (instance != null) {
return instance;
}
IClientConfig config = getInstance(name, IClientConfig.class);
return instantiateWithConfig(getContext(name), type, config);
}
最后的邏輯是如果存在緩存則從緩存中獲取,如果不存在創建
static <C> C instantiateWithConfig(AnnotationConfigApplicationContext context,
Class<C> clazz, IClientConfig config) {
C result = null;
try {
Constructor<C> constructor = clazz.getConstructor(IClientConfig.class);
result = constructor.newInstance(config);
} catch (Throwable e) {
// Ignored
}
if (result == null) {
result = BeanUtils.instantiate(clazz);
if (result instanceof IClientConfigAware) {
((IClientConfigAware) result).initWithNiwsConfig(config);
}
if (context != null) {
context.getAutowireCapableBeanFactory().autowireBean(result);
}
}
return result;
}
創建的大題流程則就是通過文章開始提到的兩個注解注冊的幾個RibbonClientSpecification
類型的配置來創建
獲取服務
getServer
方法的實現應該可以猜出來,使用具體的負載均衡器結合相應的負載均衡算法再加上服務列表過濾、服務健康檢測等操作最后會獲取的一個可用服務
調用服務
這里在調用之前把服務封裝成了RibbonServer
private final String serviceId;
private final Server server;
private final boolean secure;
private Map<String, String> metadata;
除了這幾個屬性外,RibbonServer
還有一個方法
public URI getUri() {
return DefaultServiceInstance.getUri(this);
}
這個方法就把服務從實例id轉化為一個可調用的url了
public static URI getUri(ServiceInstance instance) {
String scheme = (instance.isSecure()) ? "https" : "http";
String uri = String.format("%s://%s:%s", scheme, instance.getHost(),
instance.getPort());
return URI.create(uri);
}
然后就是發送http請求