Ribbon的作用

之前eureka是注册中心,用于服务注册和发现。每个服务注册过去,eureka里就包含了每个服务部署在哪些机器上,每台机器上有哪个端口可以接受请求。
如果我要调用某个服务,我就需要就从eureka那边去拉取服务注册表,看看我要调用的那个服务部署在哪些机器上,监听的是哪个端口号,然后给我具体的分配到某个机器的某个端口。那么springcloud中谁来实现这个功能呢?答: ribbon
Spring cloud ribbon在spring cloud微服务体系中充当着负载均衡的角色。这个负载均衡指的是客户端的负载均衡。

负载均衡分类

为了保证服务的可用性,我们会给每个服务部署多个实例,避免因为单个实例挂掉之后,导致整个服务不可用。并且,因为每个服务部署了多个实例,也提升了服务的承载能力,可以同时处理更多的请求。
不过此时需要考虑到需要将请求均衡合理的分配,保证每个服务实例的负载。一般来说,我们有两种方式实现服务的负载均衡,分别是客户端级别服务端级别

i、服务端级别的负载均衡

  1. 客户端通过外部的代理服务器,将请求转发到后端的多个服务。比较常见的有 Nginx 服务器,如下图所示:<br />![客户端级别负载均衡.png](https://cdn.nlark.com/yuque/0/2021/png/1642324/1612151231752-b32e09a7-8561-49a5-94f5-8200c6a86770.png#align=left&display=inline&height=591&margin=%5Bobject%20Object%5D&name=%E5%AE%A2%E6%88%B7%E7%AB%AF%E7%BA%A7%E5%88%AB%E8%B4%9F%E8%BD%BD%E5%9D%87%E8%A1%A1.png&originHeight=591&originWidth=611&size=65720&status=done&style=none&width=611)

ii、客户端级别的负载均衡

客户端通过内嵌的“代理”,将请求转发到后端的多个服务。比较常见的有 Dubbo、Ribbon 框架提供的负载均衡功能,如下图所示:
客户端级别负载均衡2.png

iii、总结

客户端负载均衡和服务端负载均衡最大的区别在于服务清单所存储的位置。在客户端负载均衡中,所有的客户端节点都有一份自己要访问的服务端清单,这些清单统统都是从服务注册中心获取的。
相比来说,客户端级别的负载均衡可以有更好的性能,因为不需要多经过一层代理服务器。并且,服务端级别的负载均衡需要额外考虑代理服务的高可用,以及请求量较大时的负载压力。因此,在微服务场景下,一般采用客户端级别的负载均衡为主。
对于客户端的负载均衡来说,最好搭配注册中心一起使用。这样,服务实例的启动和关闭,可以向注册中心发起注册和取消注册,保证能够动态的通知到客户端。

Netflix Ribbon的核心组件

ILoadBalancer

Ribbon的入口 ,也是真正的业务核心类。主要工作的职责。

  • 维护了存储服务实例Server对象的二个列表。一个用于存储所有服务实例的清单,一个用于存储正常服务的实例清单。
  • 初始化得到可用的服务列表,启动定时任务去实时的检测服务列表中的服务的可用性,并且间断性的去更新服务列表,结合注册中心。
  • 选择可用的服务进行调用(这个一般交给IRule去实现,不同的轮询策略)

    IRule

    选择一个最终的服务地址作为负载均衡的结果。
    选择策略有:轮询、根据响应时间加权、断路器(当Hystrix可用时)、随机、hash等。也可以根据自己的实际的业务场景进行自定义的负载均衡算法。

    IPing

    定时检查server是否健康。

    ServerList

    用于获取地址列表。它既可以是静态的(提供一组固定的地址),也可以是动态的(从注册中心中定期查询地址列表)。

    Ribbon-demo

  • 码云gitee:https://gitee.com/ZIB/ribbon-demo.git

    源码分析

    1. ribbon-deom代码入门

    ServiceB内ServiceBController.java

// @LoadBalanced这个注解,是属于spring cloud的哪个项目的?哪个包的?
// org.springframework.cloud.client.loadbalancer包
// spring-cloud-commons这个项目
@Bean
@LoadBalanced
public RestTemplate getRestTemplate() {
    return new RestTemplate();
}

//http://localhost:9090/ServiceB/user/lily
@RequestMapping("/ServiceB/user/{name}")
public String sayHello(@PathVariable("name") String name) {
    RestTemplate restTemplate = getRestTemplate();
    // http://192.168.31.107:8080/sayHello/leo
    return restTemplate.getForObject("http://ServiceA/sayHello/" + name, String.class);
}

我们从上面的demo中可以知道,加入注解@LoadBalanced 就会让RestTemplate具有了客户端负载均衡的能力。那么我们就来从@LoadBalanced 注解入手

2. @LoadBalanced Ribbon注入拦截器,一切的开始

点进注解可知,当前@LoadBalancedspring-cloud-commons-3.0.1.RELEASE包中,去github下载相同版本的源码。

2.1 获取@LoadBalanced注解标记的RestTemplate

当我们点进@LoadBalanced时,看源码解析如下,该注解用于将RestTemplate 在底层采用 LoadBalancerClient 来执行http请求,支持负载均衡

/**
 * 用于将RestTemplate或WebClient bean标记为底层采用LoadBalancerClient来执行http请求,
 * 支持负载均衡。
 * @author Spencer Gibb
 */
@Target({ ElementType.FIELD, ElementType.PARAMETER, ElementType.METHOD })
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@Qualifier
public @interface LoadBalanced {
}

2.2 启动配置类LoadBalancerAutoConfiguration 分析

由于在 serviceB 项目启动时候,由于使用@LoadBalanced注解,所以项目启动时候,会加载这个类去配置相关处理

/**
 * 默认@LoadBalanced加载的默认配置
 */
@Configuration(proxyBeanMethods = false)
@ConditionalOnClass(RestTemplate.class)
@ConditionalOnBean(LoadBalancerClient.class)
@EnableConfigurationProperties(LoadBalancerProperties.class)
public class LoadBalancerAutoConfiguration {
    @LoadBalanced
    @Autowired(required = false)
    private List<RestTemplate> restTemplates = Collections.emptyList();

    /**
     * 2. @LoadBalanced下的new RestTemplate()通过loadBalancedRestTemplateInitializerDeprecated(..)
     * 方法,构建构建一个单例的SmartInitializingSingleton对象,并且把restTemplate注入到其customize属性中
     * @param restTemplateCustomizers
     * @return
     */
    @Bean
    public SmartInitializingSingleton loadBalancedRestTemplateInitializerDeprecated(
            final ObjectProvider<List<RestTemplateCustomizer>> restTemplateCustomizers) {
        return () -> restTemplateCustomizers.ifAvailable(customizers -> {
            for (RestTemplate restTemplate : LoadBalancerAutoConfiguration.this.restTemplates) {
                for (RestTemplateCustomizer customizer : customizers) {
                    customizer.customize(restTemplate);
                }
            }
        });
    }

    @Configuration(proxyBeanMethods = false)
    @Conditional(RetryMissingOrDisabledCondition.class)
    static class LoadBalancerInterceptorConfig {
        /**
         * 2.1.1 在2.1当中的loadBalancerInterceptor由这里生成
         * @param loadBalancerClient
         * @param requestFactory
         * @return
         */
        @Bean
        public LoadBalancerInterceptor loadBalancerInterceptor(LoadBalancerClient loadBalancerClient,
                LoadBalancerRequestFactory requestFactory) {
            return new LoadBalancerInterceptor(loadBalancerClient, requestFactory);
        }

        /**
         * 2.1在2当中会触发生成RestTemplateCustomizer对象,对象生成中,会把loadBalancerInterceptor拦截器
         * 加载到restTemplate中
         * @param loadBalancerInterceptor
         * @return
         */
        @Bean
        @ConditionalOnMissingBean
        public RestTemplateCustomizer restTemplateCustomizer(final LoadBalancerInterceptor loadBalancerInterceptor) {
            return restTemplate -> {
                List<ClientHttpRequestInterceptor> list = new ArrayList<>(restTemplate.getInterceptors());
                //2.1.1 这里添加了拦截器list
                list.add(loadBalancerInterceptor);
                //并把拦截器添加到每隔restTemplate中
                restTemplate.setInterceptors(list);
            };
        }

    }

    //代码省略...
    }
  • 当1代码 @LoadBalanced 下的 new RestTemplate() 通过 loadBalancedRestTemplateInitializerDeprecated(..) 方法,构建构建一个单例的 SmartInitializingSingleton 对象,并且把 restTemplate 注入到其 customize 属性中
  • 此时 RestTemplateCustomizer 的对象由上面代码 2.1 的构造方法 restTemplateCustomizer(final LoadBalancerInterceptor loadBalancerInterceptor) 构成 ,当中得知了 restTemplate 放入了一个 LoadBalancerInterceptor 拦截器
  • LoadBalancerInterceptor 拦截器的生成主要由上面 2.1.1 构造方法由 loadBalancerInterceptor(..) 生成

    3. LoadBalancerInterceptor拦截器