掌握了Ribbon初始化的底层原理和Eureka整合使用的流程。从本章开始,我就要开始讲解Ribbon的工作流程了。本章,我会先通过一个图讲解Ribbon与Eureka结合使用时的整体流程,后面再深入源码,讲解Ribbon的各个核心组件的底层实现。
一、概述
与Eureka结合使用时,我们通常会这样写程序:
@RestController
public class ServiceBController {
@Bean
@LoadBalanced
public RestTemplate getRestTemplate() {
return new RestTemplate();
}
@GetMapping(value = "/greeting/{name}")
public String greeting(@PathVariable("name") String name) {
RestTemplate restTemplate = getRestTemplate();
// ServiceA是服务提供方向Eureka注册的应用名
return restTemplate.getForObject("http://ServiceA/sayHello/" + name, String.class);
}
}
RestTemplate就是一个Spring提供了Http RESTFUL请求工具类而已,但是@LoadBalanced
是什么呢?为什么引入它就可以获得Ribbon的负载均衡功能了?
先来看下面这两张图,这是Ribbon结合Eureka使用时的整体工作流程:
- 首先,Spring Boot应用启动后,会触发Eureka/Ribbon的自动装配机制,创建Ribbon客户端,并将Ribbon的核心组件注入到容器,然后组装到客户端。
- Spring Boot扫描到
@LoadBalanced
注解后,会创建一个请求拦截器,并将Ribbon客户端和拦截器注入到RestTemplate中; - 与此同时,Spring Boot应用本身会作为Eureka-Client从Eureka-Server拉取注册表并缓存在本地;
- 当应用通过RestTemplacte发送请求时,拦截器会拦截所有请求,然后依赖Ribbon的负载均衡机制从本地缓存的注册表中选择一个合适的地址,最终生成一个实际可调用的URL;
- 最后,RestTemplate内嵌的Http网络通信组件会根据该URL发起调用。
Ribbon客户端配置的原理我已经在前面章节讲过了,本章我重点是将这个图涉及到的整体功能梳理一遍,后续章节再讲其中每一部分涉及的原理。
二、RestTemplate装配
RestTemplate装配,其实就是给RestTemplate对象注入一个拦截器和Ribbon客户端对象。
2.1 @LoadBalanced注解
@LoadBalanced
注解,其实就是一个标记注解而已,用来标记将Ribbon客户端对象,即LoadBalancerClient
,注入到RestTemplate中:
package org.springframework.cloud.client.loadbalancer;
/**
* 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
@Qualifier
public @interface LoadBalanced {
}
前面章节,我分析过Ribbon客户端的初始化流程,Spring应用启动时会通过RibbonAutoConfiguration
自动装配一个默认的Ribbon客户端——RibbonLoadBalancerClient:
@Configuration
@Conditional(RibbonAutoConfiguration.RibbonClassesConditions.class)
@RibbonClients
@AutoConfigureAfter(
name = "org.springframework.cloud.netflix.eureka.EurekaClientAutoConfiguration")
@AutoConfigureBefore({ LoadBalancerAutoConfiguration.class, AsyncLoadBalancerAutoConfiguration.class })
@EnableConfigurationProperties({ RibbonEagerLoadProperties.class, ServerIntrospectorProperties.class })
public class RibbonAutoConfiguration {
@Bean
@ConditionalOnMissingBean
public SpringClientFactory springClientFactory() {
SpringClientFactory factory = new SpringClientFactory();
factory.setConfigurations(this.configurations);
return factory;
}
// Ribbon客户端
@Bean
@ConditionalOnMissingBean(LoadBalancerClient.class)
public LoadBalancerClient loadBalancerClient() {
return new RibbonLoadBalancerClient(springClientFactory());
}
}
可以看到,RibbonAutoConfiguration装配完成后,会触发LoadBalancerAutoConfiguration
的自动装配。
LoadBalancerAutoConfiguration,核心功能就是给RestTemplate装配Ribbon客户端,同时设置一个Ribbon拦截器LoadBalancerInterceptor
:
@Configuration(proxyBeanMethods = false)
@ConditionalOnClass(RestTemplate.class)
@ConditionalOnBean(LoadBalancerClient.class)
@EnableConfigurationProperties(LoadBalancerRetryProperties.class)
public class LoadBalancerAutoConfiguration {
// 注意这里的LoadBalanced注解
@LoadBalanced
@Autowired(required = false)
private List<RestTemplate> restTemplates = Collections.emptyList();
@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);
}
}
});
}
@Bean
@ConditionalOnMissingBean
public LoadBalancerRequestFactory loadBalancerRequestFactory(
LoadBalancerClient loadBalancerClient) {
return new LoadBalancerRequestFactory(loadBalancerClient, this.transformers);
}
// 拦截器配置
@Configuration(proxyBeanMethods = false)
@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 restTemplate -> {
List<ClientHttpRequestInterceptor> list = new ArrayList<>(
restTemplate.getInterceptors());
list.add(loadBalancerInterceptor);
// 装配拦截器,拦截器里已经组装了Ribbon客户端
restTemplate.setInterceptors(list);
};
}
}
//...
}
可以看到,最终组装出来的RestTemplate包含了一个LoadBalancerInterceptor
拦截器,而拦截器里面又注入了默认的Ribbon客户端RibbonLoadBalancerClient
。这样,当通过RestTemplate发送请求的时候,请求就会被拦截,然后就可以利用Ribbon来做负载均衡了。
2.2 请求拦截
当我们利用RestTemplate发起一个http请求时(比如restTemplate.getForObject()
),这个请求首先会被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) {
this(loadBalancer, new LoadBalancerRequestFactory(loadBalancer));
}
// 拦截请求
@Override
public ClientHttpResponse intercept(final HttpRequest request, final byte[] body,
final ClientHttpRequestExecution execution) throws IOException {
// 1.获取请求URL,比如http://ServiceA/sayHello
final URI originalUri = request.getURI();
// 2.解析出服务名,比如ServiceA
String serviceName = originalUri.getHost();
Assert.state(serviceName != null, "Request URI does not contain a valid hostname: " + originalUri);
// 3.将请求封装成一个LoadBalancerRequest对象,然后根据服务名发起请求
return this.loadBalancer.execute(serviceName, requestFactory.createRequest(request, body, execution));
}
}
其实内部逻辑非常简单:
- 解析URL,主要就是解析出要调用的服务名;
- 将原生的HttpRequest封装成一个LoadBalancerRequest对象,并交给Ribbon客户端(LoadBalancerClient)进行处理。
三、请求处理流程
从RestTemplate装配的流程可以知道,拦截器LoadBalancerInterceptor只是很薄的一层,它只是将请求交给LoadBalancerClient
这个Ribbon客户端来处理。而Spring Cloud默认创建的Ribbon客户端是RibbonLoadBalancerClient
。我们来看看它是如何处理请求的。
3.1 Riibbon客户端
RibbonLoadBalancerClient,是在Ribbon自动装配类RibbonAutoConfiguration
中装配并注入到Spring容器的,它是默认Ribbon客户端:
// RibbonAutoConfiguration.java
@Bean
@ConditionalOnMissingBean(LoadBalancerClient.class)
public LoadBalancerClient loadBalancerClient() {
return new RibbonLoadBalancerClient(springClientFactory());
}
我们看下它的具体实现,里面包含了请求的整个处理流程:
public class RibbonLoadBalancerClient implements LoadBalancerClient {
private SpringClientFactory clientFactory;
public RibbonLoadBalancerClient(SpringClientFactory clientFactory) {
this.clientFactory = clientFactory;
}
// 构造真实请求URL,也就是替换服务名称为真正的host地址
@Override
public URI reconstructURI(ServiceInstance instance, URI original) {
Assert.notNull(instance, "instance can not be null");
// 获取应用(服务) 名称
String serviceId = instance.getServiceId();
RibbonLoadBalancerContext context = this.clientFactory.getLoadBalancerContext(serviceId);
Server server = new Server(instance.getHost(), instance.getPort());
IClientConfig clientConfig = clientFactory.getClientConfig(serviceId);
ServerIntrospector serverIntrospector = serverIntrospector(serviceId);
URI uri = RibbonUtils.updateToHttpsIfNeeded(original, clientConfig,
serverIntrospector, server);
return context.reconstructURIWithServer(server, uri);
}
// 根据服务ID,选择一个Server
@Override
public ServiceInstance choose(String serviceId) {
Server server = getServer(serviceId);
if (server == null) {
return null;
}
return new RibbonServer(serviceId, server, isSecure(server, serviceId),
serverIntrospector(serviceId).getMetadata(server));
}
@Override
public <T> T execute(String serviceId, LoadBalancerRequest<T> request) throws IOException {
// 1.获取负载均衡调度器,默认是ZoneAwareLoadBalancer,在RibbonClientConfiguration中配置
ILoadBalancer loadBalancer = getLoadBalancer(serviceId)
// 2.根据负载均衡策略,获取一个Server
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);
}
protected ILoadBalancer getLoadBalancer(String serviceId) {
// 这里通过SpringClientFactory获取,内部其实会给serviceId创建一个独立的子ApplicationContext
return this.clientFactory.getLoadBalancer(serviceId);
}
@Override
public <T> T execute(String serviceId, ServiceInstance serviceInstance, LoadBalancerRequest<T> request) throws IOException {
Server server = null;
if(serviceInstance instanceof RibbonServer) {
server = ((RibbonServer)serviceInstance).getServer();
}
if (server == null) {
throw new IllegalStateException("No instances available for " + serviceId);
}
RibbonLoadBalancerContext context = this.clientFactory.getLoadBalancerContext(serviceId);
RibbonStatsRecorder statsRecorder = new RibbonStatsRecorder(context, server);
try {
// 3.发起调用
T returnVal = request.apply(serviceInstance);
statsRecorder.recordStats(returnVal);
return returnVal;
}
catch (IOException ex) {
statsRecorder.recordStats(ex);
throw ex;
}
catch (Exception ex) {
statsRecorder.recordStats(ex);
ReflectionUtils.rethrowRuntimeException(ex);
}
return null;
}
private ServerIntrospector serverIntrospector(String serviceId) {
ServerIntrospector serverIntrospector = this.clientFactory.getInstance(serviceId,
ServerIntrospector.class);
if (serverIntrospector == null) {
serverIntrospector = new DefaultServerIntrospector();
}
return serverIntrospector;
}
protected Server getServer(String serviceId) {
return getServer(getLoadBalancer(serviceId));
}
protected Server getServer(ILoadBalancer loadBalancer) {
if (loadBalancer == null) {
return null;
}
// 根据负载均衡策略选择一个Server
return loadBalancer.chooseServer("default");
}
public static class RibbonServer implements ServiceInstance {
private final String serviceId;
private final Server server;
private final boolean secure;
private Map<String, String> metadata;
//...
}
}
上述代码,处理流程如下:
- 根据应用(服务) 名称,获取一个负载均衡调度器,其实这里内部是通过SpringClientFactory获取的,默认为ZoneAwareLoadBalancer,并且每个serviceId对应一个独立的子ApplicationContext;
- 根据负载均衡策略选择一个Server;
- 将请求URL中的应用(服务) 名称替换成真实的host地址,并利用底层的HTTP通信组件发起调用。
四、总结
本章,我对Ribbon的整体工作流程进行了分析,上述流程中的核心是一个叫做ZoneAwareLoadBalancer
的Ribbon调度管理器组件,我会在后续章节详细介绍它。
Java 面试宝典是大明哥全力打造的 Java 精品面试题,它是一份靠谱、强大、详细、经典的 Java 后端面试宝典。它不仅仅只是一道道面试题,而是一套完整的 Java 知识体系,一套你 Java 知识点的扫盲贴。
它的内容包括:
- 大厂真题:Java 面试宝典里面的题目都是最近几年的高频的大厂面试真题。
- 原创内容:Java 面试宝典内容全部都是大明哥原创,内容全面且通俗易懂,回答部分可以直接作为面试回答内容。
- 持续更新:一次购买,永久有效。大明哥会持续更新 3+ 年,累计更新 1000+,宝典会不断迭代更新,保证最新、最全面。
- 覆盖全面:本宝典累计更新 1000+,从 Java 入门到 Java 架构的高频面试题,实现 360° 全覆盖。
- 不止面试:内容包含面试题解析、内容详解、知识扩展,它不仅仅只是一份面试题,更是一套完整的 Java 知识体系。
- 宝典详情:https://www.yuque.com/chenssy/sike-java/xvlo920axlp7sf4k
- 宝典总览:https://www.yuque.com/chenssy/sike-java/yogsehzntzgp4ly1
- 宝典进展:https://www.yuque.com/chenssy/sike-java/en9ned7loo47z5aw
目前 Java 面试宝典累计更新 400+ 道,总字数 42w+。大明哥还在持续更新中,下图是大明哥在 2024-12 月份的更新情况:
想了解详情的小伙伴,扫描下面二维码加大明哥微信【daming091】咨询
同时,大明哥也整理一套目前市面最常见的热点面试题。微信搜[大明哥聊 Java]或扫描下方二维码关注大明哥的原创公众号[大明哥聊 Java] ,回复【面试题】 即可免费领取。