zoukankan      html  css  js  c++  java
  • 【一起学源码-微服务】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,目录结构如下:

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

    代码地址上传到了自己的git:
    https://github.com/barrywangmeng/spring-cloud-learn

    ribbon相关的类结构信息

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

    2. 查看注册中心Dashboard
      image.png

    3. 服务B调用服务A中的接口
      调用

    4. 查看负载均衡情况
      第一次调用服务B的greeting方法:
      image.png

    5. 第二次调用服务A的greeting方法:
      image.png

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

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

    总结

    这一篇主要讲解了一个RestTemplate 加上@LoadBalanced 注解后是如何获取到请求服务的多个节点信息的,通过debug 我们可以很清晰的看到请求流程,最后画一个图来总结一下:

    申明

    本文章首发自本人博客:https://www.cnblogs.com/wang-meng 和公众号:壹枝花算不算浪漫,如若转载请标明来源!

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

    22.jpg

  • 相关阅读:
    一个用于录制用户输入操作并实时回放的小工具
    Ubuntu 14.04 下安装wiznote客户端
    lombok @EqualsAndHashCode 注解的影响
    初始化数据库和导入数据
    com.mysql.jdbc.Driver 和 com.mysql.cj.jdbc.Driver的区别 serverTimezone设定
    fastjson如何指定字段不序列化
    Mybatis 查询tinyint(1)的数据库字段时会自动转换成boolean类型
    Maven中settings.xml的配置项说明
    logback的使用和logback.xml详解
    解决Eureka Server不踢出已关停的节点的问题
  • 原文地址:https://www.cnblogs.com/wang-meng/p/12151486.html
Copyright © 2011-2022 走看看