这是我参与更文挑战的第15天,活动详情查看:更文挑战
一、前言
先以案例(demo
)入手,再来分析 ribbon
流程,最后怼源码。
(1)Ribbon + RestTemplate
案例
简单介绍下,实现原理:
- 通过在
RestTemplate
上增加@LoadBalanced
添加拦截器 - 拦截器中通过
Ribbon
选取服务实例 - 然后将请求地址中的服务名称替换成
Ribbon
选取服务实例的IP
和 端口
pom.xml
配置文件
<!-- `SpringCloud`中`eureka-client` -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
<version>2.1.2.RELEASE</version>
</dependency>
<!-- 直接导入`ribbon` -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-ribbon</artifactId>
</dependency>
复制代码
RestTemplate
中使用
@Configuration
public class RestTemplateConfig {
@Bean
@LoadBalanced
public RestTemplate restTemplate() {
return new RestTemplate();
}
}
复制代码
(2)图解 Ribbon
流程
大致流程,如图:
大致流程可分为:
- 注解:自动装配
LoadBalancerAutoConfiguration
- 在自动配置类中,为
RestTemplate
添加拦截器LoadBalancerInterceptor
- 调用请求后,拦截器中获取
host
,并在LoadBalancerClient
中对host
信息进行转换,得到真正的服务器地址。 LoadBalancerClient
中从Eureka client
得到服务实例列表,然后通过包含了负载均衡规则IRule
,选出要发起调用的server
。- 交给负责
HTTP
通讯的组件LoadBalancerRequest
执行真正的HTTP
请求。
二、直接怼源码 – 初始化
主要分为:
- 初始化:注解、自动装配、添加拦截器
- 请求初始化
spring-cloud
与ribbon
整合时的默认ILoadBalancer
1)初始化:注解、自动装配、添加拦截器
这过程主要分为三部分:
- 从
@LoadBalanced
注解入手 LoadBalancerAutoConfiguration
自动装配RestTemplate
设置拦截器
流程图如下:
详细过程源码如下:
- 先从
@LoadBalanced
注解入手:
在
RestTemplate
上,加上@LoadBalance
注解。
// 定位:org.springframework.cloud.client.loadbalancer
@Target({ ElementType.FIELD, ElementType.PARAMETER, ElementType.METHOD })
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@Qualifier
public @interface LoadBalanced {
}
复制代码
LoadBalancerAutoConfiguration
自动装配
那么,与 Spring Boot
和 Spring Cloud
相关的类,直接找 XXXAutoConfiguration
的类:
// 定位:org.springframework.cloud.client.loadbalancer
@Configuration
@ConditionalOnClass(RestTemplate.class) // 存在这个类时候加载
@ConditionalOnBean(LoadBalancerClient.class)
@EnableConfigurationProperties(LoadBalancerRetryProperties.class)
public class LoadBalancerAutoConfiguration {
// 把用户创建的 `RestTemplate` 放在这里
@LoadBalanced
@Autowired(required = false)
private List<RestTemplate> restTemplates = Collections.emptyList();
... ...
// 用 `RestTemplateCustomizer` 对每个 `RestTemplate` 进行定制化
// 给每个 `RestTemplate` 设置了 `Interceptor`
@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);
}
}
}
};
}
... ...
// 3. 给 RestTemplate 添加拦截器
@Configuration
@ConditionalOnMissingClass("org.springframework.retry.support.RetryTemplate")
static class LoadBalancerInterceptorConfig {
... ...
// 创建 RestTemplateCustomizer
@Bean
@ConditionalOnMissingBean
public RestTemplateCustomizer restTemplateCustomizer(
final LoadBalancerInterceptor loadBalancerInterceptor) {
return new RestTemplateCustomizer() {
@Override
public void customize(RestTemplate restTemplate) {
// 给每个 RestTemplate 添加拦截器
List<ClientHttpRequestInterceptor> list = new ArrayList<>(
restTemplate.getInterceptors());
list.add(loadBalancerInterceptor);
// 用 RestTemplateCustomizer 定制化了拦截器
restTemplate.setInterceptors(list);
}
};
}
}
}
复制代码
小小提下 LoadBalancerInterceptor
:
-
通过调用
RestTemplateCustomizer.customize()
方法来给RestTemplate
增加拦截器LoadBalancerInterceptor
。 -
LoadBalancerInterceptor
中实现了负载均衡的方法。
2)请求初始化
主要过程分为:
-
拦截器处理:地址转换
-
LoadBalancerClient
处理:实现负载均衡 -
拦截器处理:地址转换
大致流程,如图:
restTemplate.getForObject("http://serviceA/hello")
转换为:restTemplate.getForObject("http://172.18.1.24:8080/hello")
的转换。
// 定位:org.springframework.cloud.client.loadbalancer
public class LoadBalancerInterceptor implements ClientHttpRequestInterceptor {
private LoadBalancerClient loadBalancer;
private LoadBalancerRequestFactory requestFactory;
... ...
// 在这里替换 host,转换成实际的 ip:port
@Override
public ClientHttpResponse intercept(final HttpRequest request, final byte[] body,
final ClientHttpRequestExecution execution) throws IOException {
final URI originalUri = request.getURI();
String serviceName = originalUri.getHost();
// 交给 LoadBalancerClient 去处理真正的 HTTP 请求
// 会去执行真正的IRule实现逻辑,利用负载均衡规则筛选出适合的服务实例
return this.loadBalancer
.execute(serviceName, requestFactory.createRequest(request, body, execution));
}
}
复制代码
-
LoadBalancerClient
处理,实现负载均衡拦截器实际上只是简单的封装,把请求直接交给
LoadBalancerClient
去执行事项的请求。
public class RibbonLoadBalancerClient implements LoadBalancerClient {
private SpringClientFactory clientFactory;
... ...
@Override
public <T> T execute(String serviceId, LoadBalancerRequest<T> request)
throws IOException {
// 查找服务对应的负载均衡器
ILoadBalancer loadBalancer = getLoadBalancer(serviceId);
// 会调用 loadBalancer.chooseServer 方法
// 这个 server 就已经包含了具体的 ip 和 port
Server server = getServer(loadBalancer);
... ...
// 执行真正的 HTTP 请求
return execute(serviceId, ribbonServer, request);
}
... ...
}
复制代码
那么这个 RibbonLoadBalancerClient
什么时候初始化的?
找到
RibbonAutoConfiguration
// 定位:org.springframework.cloud.netflix.ribbon
@Configuration
@ConditionalOnClass({ IClient.class,
RestTemplate.class, AsyncRestTemplate.class, Ribbon.class})
@RibbonClients
// 在EurekaClientAutoConfiguration 之后来执行,即 eureka-client 初始化完成之后执行。
@AutoConfigureAfter(
name = "org.springframework.cloud.netflix.eureka.EurekaClientAutoConfiguration")
// 在 LoadBalancerAutoConfiguration 之前执行
@AutoConfigureBefore({LoadBalancerAutoConfiguration.class,
AsyncLoadBalancerAutoConfiguration.class})
@EnableConfigurationProperties({RibbonEagerLoadProperties.class, ServerIntrospectorProperties.class})
public class RibbonAutoConfiguration {
... ...
}
复制代码
另外,ribbon
包结构,如下:
ribbon-2.2.5.jar
:ribbon
一些比较核心组件ribbon-transport-2.2.5.jar
:基于netty
封装的特别底层的进行http
、tcp
、udp
各种协议的网络通信的组件ribbon-core-2.2.5.jar
:ribbon
基础性的一些通用的代码组件ribbon-httpclient-2.2.5.jar
:是ribbon
底层的http
网络通信的一些组件ribbon-loadbalancer-2.2.5.jar
:都是ribbon
最核心的原生的API
© 版权声明
文章版权归作者所有,未经允许请勿转载。
THE END