SpringCloud-Ribbon

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/belovehejian/article/details/86084099

什么是Ribbon

Ribbon 是 Netflix 公司开发的一个用于负载均衡的组件.

什么是负载均衡

负载均衡就是利用特定的方式,将流量(简单理解为客户端请求)分摊到多个操作单元上的一种手段.负载均衡对于系统吞吐量与系统处理能力有着质的提升.比如 Nginx 就是负载均衡组件.负载均衡可以简单的理解为有以下一些

集中式负载均衡负载均衡在组件位于客户端与服务端之间,通过一些手段,把收到的客户端的网络请求转到各个服务端之间,比如 Nginx

进程内负载均衡是指从实例库选取一个实例进行流量导入,我个人理解就是从很多的服务提供实例当中选取一个进行调用

示例

1:创建Eureka Server 注册中心 (无特殊要求,按照正常配置即可)

2:创建Eureka Client 服务提供者 (无特殊要求,按照正常配置即可,提供若干接口给消费者进行调用,实例需要注册到 Eureka Server 注册中心)


@RestController
public class DemoController {

    @RequestMapping(value = "/add", method = RequestMethod.GET)
    public String add(Integer a, Integer b, HttpServletRequest request) {
        return " From Port: " + request.getServerPort() + ", Result: " + (a + b);
    }
}

3:创建 Eureka Client Ribbon 工程,作为服务消费者,对外提供服务 (实例需要注册到 Eureka Server 注册中心)

添加依赖

<dependencies>
        <!-- ribbon jar-->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-ribbon</artifactId>
        </dependency>

        <!-- eureka client jar-->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
        </dependency>

    </dependencies>

在程序主入口处实例化一个 RestTemplate, 并且添加注解 @loadBalanced 声明该 RestTemplate 用于负载均衡

@SpringBootApplication
@EnableDiscoveryClient
public class Chapter5RibbonLoadBalancerApplication {
    public static void main(String[] args) {
        SpringApplication.run(Chapter5RibbonLoadBalancerApplication.class, args);
    }
    @Bean
    @LoadBalanced
    public RestTemplate restTemplate() {
        return new RestTemplate();
    }
}

编写对外提供的接口并注入 RestTemplate

@RestController
public class DemoController {

    @Autowired
    private RestTemplate restTemplate;

    @RequestMapping("/add")
    public String add(Integer a, Integer b) {
        String result = restTemplate.getForObject("http://eureka-client-A/add?a=" + a + "&b=" + b, String.class);
        System.out.println(result);
        return result;
    }
}

然后依次将 3 个工程打成 jar 包,并执行jar, 在执行 服务提供者的 jar 的时候, 应用不同的端口,多启动几个实例,然后通过服务消费者提供的接口进行调用. 如:http://localhost:7777/add?a=10&b=20 可以看到 效果如下

可以看出,Ribbon 默认使用轮巡的方式访问服务源,这是一种负载均衡策略.

Ribbon 负载均衡策略与自定义配置

Ribbon总共有7中负载均衡策略

  1. RandomRule: 随机策略,随机选择 server
  2. RoundRobinRule: 轮询策略,按顺序循环选择 server (这是默认策略, BaseLoadBalancer 中默认指定的)
  3. RetryRule: 重试策略, 在一个配置时间段内选择 server不成功,则一直尝试选择一个可用的 server
  4. BestAvailableRule: 最低并发策略, 逐个考察server,如果server 断路器打开则忽略. 再选择其中一个并发连接最低的 server
  5. AvailabilityFilteringRule: 可用过滤策略, 过滤掉一直连接失败并且标记为 circuit tripped 的 server.过滤掉那些高并发连接的 server (active connections 超过配置的阀值)
  6. ResponseTimeWeightenRule: 响应时间加权策略.根据 server 的响应时间分配权重,响应时间越长, 权重越低, 被选择到的几率就越小. 响应时间越短,权重越高,被选择到的几率越高. 这个策略综合了很多因素,如: 网络,磁盘, IO, 等等.这些因素都会影响到响应时间.
  7. ZoneAvoidanceRule: 区域权衡策略.综合判断 server 所在区域的性能和 server 的可用性.轮询选择 server. 并且判断一个 AWS Zone 的运行性能是否可用,剔除那些不可用的 Zone 中的所有 server.

全局策略配置

使用 Ribbon 的时候想要全局更改负载均衡策略,只需要加一个配置类,返回一个 负载均衡策略 bean 便可以 如:修改上例为 随机策略

/**
 * 修改Ribbon 负载均衡策略的配置类.
 */
@Configuration
public class RibbonRuleConfig {

    //返回一个 随机策略.
    @Bean
    public RandomRule randomRule() {
        return new RandomRule();
    }
}

然后在启动上例项目的时候, 负载均衡策略就已经变更成了随机策略了. 只要加上这个, 后续的所有 Ribbon 请求都将使用指定的策略.

基于注解的策略配置

编写一个空注解

public @interface AvoidScan {
}

编写应用于特定服务实例的的策略配置类

@Configuration
@AvoidScan
public class RoundRobinRuleConfig {

    @Resource
    private IClientConfig config;

    @Bean
    public IRule roundRobinRule(IClientConfig clientConfig) {
        return new RoundRobinRule();
    }
}

在主启动类上加上注解

/**
 * @RibbonClient(name = "eureka-client-A", configuration = RoundRobinRuleConfig.class) 表示:
 *                          对 eureka-client-A 服务使用的策略是经过 RoundRobinRuleConfig 类配置的.
 * @ComponentScan(excludeFilters = {@ComponentScan.Filter(type = FilterType.ANNOTATION, value = {AvoidScan.class})}) 表示:
 *                  让spring 不去扫描被 @AvoidScan 注解注释的类,因为不能应用于全局,所以只对 eureka-client-A 服务实例生效 .
 *
 * 也可以通过 @RibbonClients 注解来对多个服务员进行策略指定 如:
 * @RibbonClients(value = {
 * @RibbonClient(name = "client-a, configuration = TestA.class),
 * @RibbonClient(name = "client-b, configuration = TestB.class)
 *  })
 */
@SpringBootApplication
@EnableDiscoveryClient
@RibbonClient(name = "eureka-client-A", configuration = RoundRobinRuleConfig.class)
@ComponentScan(excludeFilters = {@ComponentScan.Filter(type = FilterType.ANNOTATION, value = {AvoidScan.class})})
public class Chapter5RibbonLoadBalancerApplication {
    public static void main(String[] args) {
        SpringApplication.run(Chapter5RibbonLoadBalancerApplication.class, args);
    }

    //使用负载均衡,(使用默认的策略 轮巡策略)
    @Bean
    @LoadBalanced
    public RestTemplate restTemplate() {
        return new RestTemplate();
    }
}

个人感觉这种方式比较繁琐,要编写很多只针对一个服务实例的代码

基于配置文件的策略自定义

语法是: <client name>.ribbon.*  使用这种方式基本上不需要任何注解形式的配置代码 (我还是比较喜欢这种方式,很方便).比如下面对 服务使用 随机策略. 首先把之前写的配置类全部注释掉.确认项目使用的是默认的策略. 然后在配置文件当中添加一下内容:

#针对 eureka-client-A 服务实例 使用 指定的随机策略./
eureka-client-A:
  ribbon:
    NFLoadBalancerRuleClassName: com.netflix.loadbalancer.RandomRule

然后启动项目 进行访问 http://localhost:7777/add?a=10&b=20  可以看到控制台输出

可以看出使用的是 随机策略. 个人感觉这种方式很方便,因为一般来说我们的配置文件都是远程放在 git 上面的, 如果要更改策略的话, 直接修改配置文件的内容就好了, 不需要像使用注解的方式那样还要修改源代码.然后重新编译等等一系列的事情. 还是文件配置比较方便

Ribbon 超时与重试

使用 HTTP 发起请求,请求超时好像是比较常见的问题,此时对调用进行时限控制和超时之后的重试还是比较重要的. Ribbon 的重试机制默认是开启的.有需要的话可以添加对超时时间与重试机制策略的配置. 如: 

#针对 eureka-client-A 服务实例 使用 指定的随机策略./
eureka-client-A:
  ribbon:
    NFLoadBalancerRuleClassName: com.netflix.loadbalancer.RandomRule
    ConnectTimeout: 30000   #连接超时时间
    ReadTimeout: 30000
    MaxAutoRetries: 1  #对第一次请求的服务的重试次数 比如第一次请求 A 服务, 失败了, 然后会在重试 1 次
    MaxAutoRetriesNextServer: 1 #要重试的下一个服务的最大数量 (不包括第一个服务)
    OkToRetryOnAllOperations: true

Ribbon 的饥饿加载

Ribbon 在进行客户端负载均衡的时候并不是在启动的时候就加载上下文,而是在实际请求到达的时候才回去创建,因此这个特性往往会让第一次调用用时比较久,有可能会超时. 因此我们可以通过指定 Ribbon 具体的客户端来开启饥饿加载.意思就是,除了指定的 Ribbon 客户端是饥饿加载之外,其他的所有配置项的应用上下文都是在启动的时候就加载.

在配置文件当中添加:

#配置指定使用饥饿加载的上下文,所有不在配置项的上下文都会在启动的时候加载.
ribbon:
  eager-load:
    enabled: true
    clients: eureka-client-A, eureka-client-B, eureka-client-C

在默认情况下 Ribbon 客户端会从 Eureka 注册中心读取服务注册信息列表,来达到动态负载均衡的功能.当然也可以脱离 Eureka 使用,只需要在配置文件当中配置禁止使用 Eureka 功能,然后手动指定源服务地址列表就可以.

ribbon:
  eureka:
    enable: false #禁用 Eureka 功能
client:
  ribbon:
    listOfServers: http://localhost:7070  #指定源服务地址列表

Ribbon解读

介绍几个 Ribbon 中的核心接口

IClientConfig:定义 Ribbon 中管理配置的接口, 默认实现 DefaultClientConfigImpl

IRule: 定义 Ribbon 中负载均衡策略的接口, 默认实现 ZoneAvoidanceRule

IPing: 定义定期 ping 服务检查可用性的接口, 默认实现 DummyPing

ServerList<Server>: 定义获取服务列表方法的接口, 默认实现 ConfigurationBasedServerList

ServerListFilter<Server>: 定义特定期望获取服务列表方法的接口, 默认实现 ZonePreferenceServerListFilter

ILoadBalancer: 定义负载均衡选择服务器的核心方法的接口, 默认实现 ZoneAwareLoadBalancer

ServerListUpdater: 为DynamicServerListLoadBalancer 定义动态更新服务器列表的接口. 默认实现 PollingServerListUpdater

如下所示

//使用负载均衡,(使用默认的策略 轮巡策略)
    @Bean
    @LoadBalanced
    public RestTemplate restTemplate() {
        return new RestTemplate();
    }

使用 @LoadBalanced 注解就可以使用负载均衡了,. 查看该注解可以看到,

注释中写的很清楚, , 然后接着看 LoadBalancerClient 类,这个来表示一个 客户端负载均衡器,该类里面有四个很重要的方法,具体可以查看方法上面的注释. 

不过根据书上说的,我倒是没有追踪到自动配置类(也就是为该类的实现做初始化自动配置) LoadBalancerAutoConfiguration 类中去(能力有限,正在学习当中). 所以直接看 LoadBalancerAutoConfiguration  的代码吧.

类上的注释表名, 只有在类中包含 RestTemplate 类的实例,并且包含LOadBalancerClient 实例,才会初始化这个 LoadBalancerAutoConfiguration, 我个人理解的是,正因为这个条件, 所以我们在主配置程序当中要手动初始化一个 RestTemplate 实例, 扫描到 Spring 当中.

在这个类中还有一个方法 loadBalancerRequestFactory, 这个方法用于创建 LoadBalancerRequest 实例, 供 LoadBalancerInterceptor 使用.

而在  LosdBalancerInterceptorConfig 静态类中则是维护了 2 个实例, LoadBalancerInterceptor 与 RestTemplateCustomizer 实例.

RestTemplateCustomizer: 为每个 RestTemplate 绑定 LoadBalancerInterceptor 拦截器.可以看到在方法的最后面为 restTemplate 设置了一个拦截器进去.

LoadBalancerInterceptor: 拦截每一次 HTTP 请求, 将请求绑定到 Ribbon 负载均衡的声明周期.然后继续查看该实例所在的类的实现,

发现这个类是实现了 Spring 维护的请求拦截器 ClientHttpRequestInterceptor, 而实现该 ClientHttpRequestInterceptor 拦截器, 并重写其中的 interceptor 方法, 这个时候收到的所有请求就会进入 interceptor 方法体. 而在 interceptor 方法体当中,确实用传入的LoadBalancerClient 的实例 loadBalancer 执行 execute 方法的. 而 LoadBalancerClient 只有一个实现类 RibbonLoadBalancerClient .然后追进去, 查看 execute 方法.

首先获取一个 ILoadBalancer 实例.,然后获取 server.前面说过 ILoadBalancer 接口是定义负载均衡选择服务器的核心方法接口.所以 getServer() 方法应该就是负载均衡选择具体服务实例的方法了.接着往里面看

方法中调用了 chooseServer 方法, 该方法的作用是 从负载均衡器当中选择一个服务器, 使用了 key 值来获取具体的服务实例.如果不指定 key, 则返回的是 null. 每个负载均衡器选择接口(ILoadBalancer )的实现类的方法实现都不一样, 具体可以查看源码.这样就将拦截到的 HTTP 请求 与负载均衡策略关联起来了. 

现在回想一下, 好像这里的这个负载均衡就是利用 RestTemplate 发起请求,  然后在用一个静态内部类去实现 Spring 维护的 ClientHttpRequestInterceptor 请求拦截器, 利用该拦截器拦截每一次的请求, 然后强行将 请求 与负载均衡关联起来. 然后发起请求调用,拿到结果. 比起直接发起请求来说, 这里多了一个负载均衡,相当于是 多了一个选择服务器的操作.

IRule 接口是定义 Ribbon 负载均衡策略的父接口,所有策略都是基于它实现的. 主要逻辑是 choose() 方法. Ribbon 是通过 ILoadBalancer 来关联 IRule 的. ILoadBanancer 的chooseServer 会转换成 IRule 的 choose() 方法.

参考 <重新定义 Spring Cloud>  一书

猜你喜欢

转载自blog.csdn.net/belovehejian/article/details/86084099
今日推荐