【Ribbon】源码解析(上)

301 阅读3分钟

这是我参与更文挑战的第15天,活动详情查看:更文挑战

一、前言

先以案例(demo)入手,再来分析 ribbon 流程,最后怼源码。


(1)Ribbon + RestTemplate 案例

简单介绍下,实现原理:

  • 通过在 RestTemplate 上增加 @LoadBalanced 添加拦截器
  • 拦截器中通过 Ribbon 选取服务实例
  • 然后将请求地址中的服务名称替换成 Ribbon 选取服务实例的 IP 和 端口
  1. 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>
  1. RestTemplate中使用
@Configuration
public class RestTemplateConfig {

    @Bean
    @LoadBalanced
    public RestTemplate restTemplate() {

        return new RestTemplate();
    }
}

(2)图解 Ribbon 流程

大致流程,如图:

ribbon-流程.png

大致流程可分为:

  1. 注解:自动装配 LoadBalancerAutoConfiguration
  2. 在自动配置类中,为 RestTemplate 添加拦截器 LoadBalancerInterceptor
  3. 调用请求后,拦截器中获取 host,并在 LoadBalancerClient 中对 host 信息进行转换,得到真正的服务器地址。
  4. LoadBalancerClient 中从 Eureka client 得到服务实例列表,然后通过包含了负载均衡规则 IRule,选出要发起调用的 server
  5. 交给负责 HTTP 通讯的组件 LoadBalancerRequest 执行真正的 HTTP 请求。


二、直接怼源码 - 初始化

主要分为:

  1. 初始化:注解、自动装配、添加拦截器
  2. 请求初始化
  3. spring-cloudribbon 整合时的默认 ILoadBalancer

1)初始化:注解、自动装配、添加拦截器

这过程主要分为三部分:

  1. @LoadBalanced 注解入手
  2. LoadBalancerAutoConfiguration 自动装配
  3. RestTemplate 设置拦截器

流程图如下:

初始化1.png


详细过程源码如下:

  1. 先从 @LoadBalanced 注解入手:

RestTemplate 上,加上 @LoadBalance 注解。

// 定位:org.springframework.cloud.client.loadbalancer
@Target({ ElementType.FIELD, ElementType.PARAMETER, ElementType.METHOD })
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@Qualifier
public @interface LoadBalanced {
}
  1. LoadBalancerAutoConfiguration 自动装配

那么,与 Spring BootSpring 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)请求初始化

主要过程分为:

  1. 拦截器处理:地址转换

  2. LoadBalancerClient 处理:实现负载均衡

  3. 拦截器处理:地址转换

大致流程,如图:

请求初始化1.png

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));
	}
}

  1. 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 包结构,如下:

  1. ribbon-2.2.5.jarribbon 一些比较核心组件
  2. ribbon-transport-2.2.5.jar:基于 netty 封装的特别底层的进行 httptcpudp 各种协议的网络通信的组件
  3. ribbon-core-2.2.5.jarribbon 基础性的一些通用的代码组件
  4. ribbon-httpclient-2.2.5.jar:是 ribbon 底层的 http网络通信的一些组件
  5. ribbon-loadbalancer-2.2.5.jar:都是 ribbon 最核心的原生的 API