转载

【一起学源码-微服务】Ribbon 源码一:Ribbon概念理解及Demo调试

本文章首发自本人公众号: 壹枝花算不算浪漫 ,如若转载请标明来源!

感兴趣的小伙伴可关注个人公众号:壹枝花算不算浪漫

【一起学源码-微服务】Ribbon 源码一:Ribbon概念理解及Demo调试

前言

前情回顾

前面文章已经梳理清楚了Eureka相关的概念及源码,接下来开始研究下Ribbon的实现原理。

我们都知道Ribbon在spring cloud中担当 负载均衡 的角色, 当两个Eureka Client互相调用的时候,Ribbon能够做到调用时的负载,保证多节点的客户端均匀接收请求。(这个有点类似于前端调用后端时Nginx做的负载均衡)

本讲目录

本讲主通过一个简单的demo来了解ribbon内部实现,这里主要是对ribbon有个宏观的认识,后续篇章会一步步通过debug的方式对ribbon的细节做一个全面的讲解。

目录如下:

  1. 一个demo来看看ribbon是做什么的
  2. @LoadBalanced初探
  3. LoadBalancerAutoConfiguration初探
  4. RibbonLoadBalancerClient初探

说明

原创不易,如若转载 请标明来源!

博客地址: 一枝花算不算浪漫 微信公众号:壹枝花算不算浪漫

Ribbon正文

一个demo来看看ribbon是做什么的

首先看下我们这里的demo,目录结构如下:

【一起学源码-微服务】Ribbon 源码一:Ribbon概念理解及Demo调试

这里有3个模块,eurekaServer作为注册中心,serviceA和serviceB分别作为EurekaClient。

代码地址上传到了自己的git: github.com/barrywangme…

ribbon相关的类结构信息

  1. 启动了eureka client如下: 服务A 2个 : 一个端口号为8087,另一个为8088 服务B 1个

    【一起学源码-微服务】Ribbon 源码一:Ribbon概念理解及Demo调试
  2. 查看注册中心Dashboard

    【一起学源码-微服务】Ribbon 源码一:Ribbon概念理解及Demo调试
  3. 服务B调用服务A中的接口

    【一起学源码-微服务】Ribbon 源码一:Ribbon概念理解及Demo调试
  4. 查看负载均衡情况 第一次调用服务B的greeting方法:

    【一起学源码-微服务】Ribbon 源码一:Ribbon概念理解及Demo调试
  5. 第二次调用服务A的greeting方法:

    【一起学源码-微服务】Ribbon 源码一:Ribbon概念理解及Demo调试

这里可以看到服务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 是干嘛的:

【一起学源码-微服务】Ribbon 源码一:Ribbon概念理解及Demo调试

我们通过debug可以看到 获取的 ILoadBalancer 已经获取到服务A所有的节点信息了,这一章就先不延伸下去了,后面会详细来说 ILoadBalancer 处理的细节。

原文  https://juejin.im/post/5e8e53c86fb9a03c4a4970a5
正文到此结束
Loading...