一篇学习Ribbon

导读:本篇文章讲解 一篇学习Ribbon,希望对大家有帮助,欢迎收藏,转发!站点地址:www.bmabk.com

1.Ribbon负载均衡简介

1.1Ribbon概述

1.1.1.Ribbon是什么

SpringCloud Ribbon是基于Netflix Ribbon实现的一套==客户端负载均衡==的工具。

简单的说,Ribbon是Netflix发布的开源项目,主要功能是提供客户端的软件负载均衡算法,将Netflix的中间层服务连接在一起。Ribbon客户端组件提供一系列完善的配置项如连接超时重试等。简单的说,就是在配置文件中列出LoadBalanCer(简称LB)后面所有的机器,Ribbon会自动的帮助你基于某种规则(如简单轮询,随机连接等)去连接这些机器。我们也很容易使用Ribbon实现自定义的负载均衡算法。

1.1.2.Ribbon主要职责

LB(负载均衡)

LB,即负载均衡( Load Balance ),在微服务或分布式集群中经常用的一种应用。

负载均衡简单的说就是将用户的请求平摊的分配到多个服务上,从而达到系统的HA。

常见的负载均衡有软件nginx , LVS,硬件F5等。

相应的在中间件,例如:dubbo和 SpringCloud中均给我们提供了负载均衡,SpringCloud的负载均衡算法可以自定义。

LB又分为两种,集中式LB和进程内LB

集中式LB(偏硬件)

即在服务的消费方和提供方之间使用独立的LB设施(可以是硬件,如F5,也可以是软件,如nginx ) ,由该设施负责把访问请求通过某种策略转发至服务的提供方;

进程内LB(偏软件)

将LB逻辑集成到消费方,消费方从服务注册中心获知有哪些地址可用,然后自己再从这些地址中选择出一个合适的服务器。
Ribbon就属于进程内LB,它只是一个类库,集成于消费方进程,消费方通过它来获取到服各提供方的地址。

在这里插入图片描述

1.1.3 官方资料

https://github.com/Netflix/ribbon/wiki

2.Ribbon实例

上一篇的案例中,我们启动了一个springcloud-demo,然后通过DiscoveryClient来获取服务实例信息,然后获取ip和端口来访问。

但是实际环境中,我们往往会开启很多个user-service的集群。此时我们获取的服务列表中就会有多个,到底该访问哪一个呢?

一般这种情况下我们就需要编写负载均衡算法,在多个实例列表中进行选择。不过Eureka中已经帮我们集成了负载均衡组件:Ribbon,简单修改代码即可使用。

在这里插入图片描述
接下来,我们就来使用Ribbon实现负载均衡。

2.1.Ribbon架构说明

在这里插入图片描述
Ribbon 在工作时分成两步:

第一步先选择 EurekaServer,它优先选择在同一个区域内负载较少的server。
第二步再根据用户指定的策略,在从server取到的服务注册列表中选择一个地址。

其中Ribbon 提供了多种策略:比如轮询、随机和根据响应时间加权。

2.2.启动两个服务实例

首先我们启动两个springcloud-demo实例,一个80,一个81。
在这里插入图片描述
Eureka监控面板:
在这里插入图片描述

2.3.开启负载均衡

因为Eureka中已经集成了Ribbon,所以我们无需引入新的依赖

加入Ribbon的配置

# EurekaServer地址
eureka.client.service-url.defaultZone=http://eureka7001.com:7001/eureka/,http://eureka7002.com:7002/eureka/,http://eureka7003.com:7003/eureka/

在RestTemplate的配置方法上添加@LoadBalanced注解

@Bean
@LoadBalanced
public RestTemplate restTemplate() {
    return new RestTemplate(new OkHttp3ClientHttpRequestFactory());
}

修改调用方式,不再手动获取ip和端口,而是直接通过服务名称调用

/**
 * @author bruceliu
 * @create 2019-05-02 15:52
 * @description
 */
@RestController
@RequestMapping("consumer")
public class ConsumerController {

    //改成下面这行
    private static final String REST_URL_PREFIX = "http://SPRINGCLOUD-DEMO-SERVICE";

    @Autowired
    private RestTemplate restTemplate;

    @RequestMapping("/test")
    public List<User> consumerTest(){

        return this.restTemplate.getForObject(REST_URL_PREFIX+"/all",List.class);
    }
}

测试
在这里插入图片描述
在这里插入图片描述
Ribbon整合后可以直接调用微服务而不再关心地址和端口!!!

2.4.源码跟踪

为什么我们只输入了service名称就可以访问了呢?之前还要获取ip和端口。显然有人帮我们根据service名称,获取到了服务实例的ip和端口。它就是LoadBalancerInterceptor
我们进行源码跟踪:
在这里插入图片描述
继续跟入execute方法:发现获取了8001端口的服务

org.springframework.cloud.netflix.ribbon.RibbonLoadBalancerClient

在这里插入图片描述
如果再跟下一次,发现获取的是8002

2.5.负载均衡策略

Ribbon默认的负载均衡策略是简单的轮询,我们可以测试一下:
编写测试类,在刚才的源码中我们看到拦截中是使用RibbonLoadBalanceClient来进行负载均衡的,其中有一个choose方法,是这样介绍的:
在这里插入图片描述
现在这个就是负载均衡获取实例的方法。我们对注入这个类的对象,然后对其测试:

package com.bruceliu.test;

import com.bruceliu.SpringcloudDemoConsumerApplication;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.cloud.client.ServiceInstance;
import org.springframework.cloud.netflix.ribbon.RibbonLoadBalancerClient;
import org.springframework.test.context.junit4.SpringRunner;

/**
 * @author bruceliu
 * @create 2019-05-04 11:33
 * @description 负载均衡算法测试
 */
@RunWith(SpringRunner.class)
@SpringBootTest(classes = SpringcloudDemoConsumerApplication.class)
public class LoadBalanceTest {

    @Autowired
    RibbonLoadBalancerClient client;

    @Test
    public void test1(){
        for (int i = 0; i <10 ; i++) {
            ServiceInstance instance = this.client.choose("SPRINGCLOUD-DEMO-SERVICE");
            System.out.println(instance.getHost() + ":" + instance.getPort());
        }
    }
}

运行结果:
在这里插入图片描述
符合了我们的预期推测,确实是轮询方式

我们是否可以修改负载均衡的策略呢?继续跟踪源码,发现这么一段代码:
在这里插入图片描述
我们看看这个rule是谁:
在这里插入图片描述
这里的rule默认值是一个RoundRobinRule,看类的介绍:
在这里插入图片描述
这不就是轮询的意思嘛。

我们注意到,这个类其实是实现了接口IRule的,查看一下:
在这里插入图片描述
定义负载均衡的规则接口。
它有以下实现:
在这里插入图片描述
SpringBoot也帮我们提供了修改负载均衡规则的配置入口:

SPRINGCLOUD-DEMO-SERVICE:
  ribbon:
    NFLoadBalancerRuleClassName: com.netflix.loadbalancer.RandomRule

格式是:{服务名称}.ribbon.NFLoadBalancerRuleClassName,值就是IRule的实现类。
再次测试,发现结果变成了随机:
在这里插入图片描述

2.6.重试机制

我们现在关闭一个springcloud-demo-8001实例在这里插入图片描述
因为服务剔除的延迟,consumer并不会立即得到最新的服务列表,此时再次访问你会得到错误提示:
在这里插入图片描述
但是此时,8002服务其实是正常的。

因此Spring Cloud 整合了Spring Retry 来增强RestTemplate的重试能力,当一次服务调用失败后,不会立即抛出一次,而是再次重试另一个服务。
只需要简单配置即可实现Ribbon的重试:

spring:
  cloud:
    loadbalancer:
      retry:
        enabled: true # 开启Spring Cloud的重试功能
SPRINGCLOUD-DEMO-SERVICE:
  ribbon:
    ConnectTimeout: 250 # Ribbon的连接超时时间
    ReadTimeout: 1000 # Ribbon的数据读取超时时间
    OkToRetryOnAllOperations: true # 是否对所有操作都进行重试
    MaxAutoRetriesNextServer: 1 # 切换实例的重试次数
    MaxAutoRetries: 1 # 对当前实例的重试次数

根据如上配置,当访问到某个服务超时后,它会再次尝试访问下一个服务实例,如果不行就再换一个实例,如果不行,则返回失败。切换次数取决于MaxAutoRetriesNextServer参数的值

引入spring-retry依赖

<dependency>
    <groupId>org.springframework.retry</groupId>
    <artifactId>spring-retry</artifactId>
</dependency>

我们重启springcloud-demo-consumer,测试,发现即使springcloud-demo-8001宕机,也能通过另一台服务实例获取到结果!

2.7 Ribbon核心组件IRule(面试题)

策略名称 策略对应的类名 实现原理
轮询策略(默认) RoundRobinRule 轮询策略表示每次都顺序取下一个 provider,比如一共有 5 个provider,第 1 次取第 1 个,第 2次取第 2 个,第 3 次取第 3 个,以此类推
权重轮询策略 WeightedResponseTimeRule 1.根据每个 provider 的响应时间分配一个权重,响应时间越长,权重越小,被选中的可能性越低。2.原理:一开始为轮询策略,并开启一个计时器,每 30 秒收集一次每个 provider 的平均响应时间,当信息足够时,每个 provider附上一个权重,并按权重随机选择provider,高权越重的 provider会被高概率选中。
随机策略 RandomRule 从 provider 列表中随机选择一个provider
最少并发数策略 BestAvailableRule 选择正在请求中的并发数最小的 provider,除非这个provider 在熔断中。
在“选定的负载均衡策略”基础上进行重试机制 RetryRule 1.“选定的负载均衡策略”这个策略是轮询策略RoundRobinRule 2.该重试策略先设定一个阈值时间段,如果在这个阈值时间段内当选择 provider 不成功,则一直尝试采用“选定的负载均衡策略:轮询策略”最后选择一个可用的provider
可用性敏感策略 AvailabilityFilteringRule 过滤性能差的 provider,有 2种:第一种:过滤掉在 eureka 中处于一直连接失败 provider 第二种:过滤掉高并发的 provider
区域敏感性策略 ZoneAvoidanceRule 1.以一个区域为单位考察可用性,对于不可用的区域整个丢弃,从剩下区域中选可用的provider2.如果这个 ip 区域内有一个或多个实例不可达或响应变慢,都会降低该 ip 区域内其他 ip 被选中的权重。机架!
Ribbon的负载均衡算法内置了7种,如果还不能满足实际的要求,那么你需要自定义负载均衡算法~~~~~
写一个类实现IRUle接口,重写其中的方法
然后把自己的规则类,注入到系统中.....

2.8 修改访问服务的算法方式

方式一:修改代码更换负载均衡策略

在启动类中添加实例负载均衡的实例,则默认的轮询策略就会失效,具体如下:

import com.netflix.loadbalancer.IRule;
import com.netflix.loadbalancer.RandomRule;
import com.netflix.loadbalancer.RoundRobinRule;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class RibbonConfig {

    /**
     * 显示实例化 负载均衡的策略对象,那么默认的轮询策略就会失效
     * @return
     */
    @Bean
    public IRule createRule(){
        //修改为随机算法!
        //return new RandomRule();
        return new RoundRobinRule();
    }
}
@SpringBootApplication
@EnableEurekaClient//开启客户端
@RibbonClient(name = "USER-SERVICE",configuration = RibbonConfig.class)
public class UserApp9090 {

    public static void main(String[] args) {
        SpringApplication.run(UserApp9090.class,args);
    }
}

方式二:修改配置文件更换负载均衡策略
第二种方式在application.properties中设置分配的策略

#设置负载均衡策略 eureka-ribbon-provider 为调用的服务的名称
eureka-ribbon-provider.ribbon.NFLoadBalancerRuleClassName=com.netflix.loadbalancer.RandomRule

‘eureka-ribbon-provider’是调用的服务的名称,后面的组成部分是固定的。
同时注释掉方式一中的内容

3.Ribbon源码分析

1.LoadBalancerInterceptor

为什么我们只输入了service名称就可以访问了呢?之前还要获取ip和端口。显然有人帮我们根据service名称,获取到了服务实例的ip和端口。它就是LoadBalancerInterceptor
在这里插入图片描述

org.springframework.cloud.netflix.ribbon.RibbonLoadBalancerClient

2.RibbonLoadBalancerClient

在这里插入图片描述
在这里插入图片描述

3.ZoneAwareLoadBalancer

在这里插入图片描述
调用父类的chooseServer

4.BaseLoadBalancer

在这里插入图片描述
内部规则是ZoneAvoidanceRule,就是前面说的把默认的RoundRobinRule给覆盖了,但是复合策略里面默认的又是RoundRobinRule,只是加了一些过滤器,根据一些条件过滤服务器。
在这里插入图片描述

5.默认的RoundRobinRule何时被覆盖?

5.1.RibbonClientConfiguration

在这里插入图片描述

5.2.何时替换了RoundRobinRule

在这里插入图片描述

6.PredicateBasedRule的choose

在这里插入图片描述

最终到父类的choose方法,先获取负载均衡器lb,然后获取所有的服务器,再获取预测器getPredicate,里面是个复合的AbstractServerPredicate,可以当做过滤器,满足一定条件过滤服务器的,最后进行chooseRoundRobinAfterFiltering轮询:

7.AbstractServerPredicate的chooseRoundRobinAfterFiltering

必须先走过滤器,过滤一遍,然后再轮询出服务器。
在这里插入图片描述

8.进行过滤器过滤

在这里插入图片描述
内部最终会进行ZoneAvoidancePredicateAvailabilityPredicate两个过滤器的过滤,只要有一个不满足就被过滤掉了。

9.Predicates

在这里插入图片描述

10.ZoneAvoidancePredicate区域过滤器

我们一般默认就一个区域defaultzone,多个区域就需要有判断了,比如断电情况,负载这些,具体可以看ZoneAvoidancePredicate的apply方法。一般情况只有一个区域,就返回true了:
在这里插入图片描述

11.AvailabilityPredicate可用性过滤器

这个是用来过滤掉不可用的服务器,不如连接不上,还有并发太多的
在这里插入图片描述
如果检测出断路器问题或者是服务器连接数超过限制了,就要过滤掉了。
在这里插入图片描述

12.执行过滤之后

最后过滤出来的服务器集合再进行轮询算法,选出一个。
在这里插入图片描述
核心的incrementAndGetModulo(int)方法

老版本源码

private int incrementAndGetModulo(int modulo) {
        int current;
        int next;
        do {
            current = this.nextServerCyclicCounter.get(); //nextServerCyclicCounter是AtomicInteger对象,默认值0,可保证线程安全性 
            next = (current + 1) % modulo; //每次往后移一位,取集合中的下一个server。这里要注意的是从1开始,即数组中的第二个server会被第一个调用。
        } while(!this.nextServerCyclicCounter.compareAndSet(current, next)); //操作完成后用CAS操作将next赋值给nextServerCyclicCounter

        return next;
    }

新版本源码在这里插入图片描述

13.常见的负载均衡算法

IRule是以下七种负载均衡算法的父接口
在这里插入图片描述
说明:
在这里插入图片描述

4.自定义Ribbon负载均衡策略

官方文档指出:自定义的负载均衡配置类不能放在 @componentScan 所扫描的当前包下及其子包下,否则我们自定义的这个配置类就会被所有的Ribbon客户端所共享,也就是说我们达不到特殊化定制的目的了;

要求自定义的算法:依旧是轮询策略,但是每个服务器被调用5次后轮到下一个服务,即以前是每个服务被调用1次,现在是每个被调用5次。

1、自定义算法类必须继承 AbstractLoadBalanceRule 类

启动类在com.bruce.springcloud 包下,所以我们新建一个包: con.bruce.myrule,并在该包下新建一个类:CustomeRule

public class CustomeRule extends AbstractLoadBalancerRule
{
     /*
     total = 0 // 当total==5以后,我们指针才能往下走,
     index = 0 // 当前对外提供服务的服务器地址,
     total需要重新置为零,但是已经达到过一个5次,我们的index = 1
     */

    private int total = 0;             // 总共被调用的次数,目前要求每台被调用5次
    private int currentIndex = 0;    // 当前提供服务的机器号

    public Server choose(ILoadBalancer lb, Object key) {
        if (lb == null) {
            return null;
        }
        Server server = null;

        while (server == null) {
            if (Thread.interrupted()) {
                return null;
            }
            List<Server> upList = lb.getReachableServers(); //当前存活的服务
            List<Server> allList = lb.getAllServers();  //获取全部的服务

            int serverCount = allList.size();
            if (serverCount == 0) {
                return null;
            }

            //int index = rand.nextInt(serverCount);
            //server = upList.get(index);
            if(total < 5)
            {
                server = upList.get(currentIndex);
                total++;
            }else {
                total = 0;
                currentIndex++;
                if(currentIndex >= upList.size())
                {
                    currentIndex = 0;
                }
            }

            if (server == null) {
                Thread.yield();
                continue;
            }

            if (server.isAlive()) {
                return (server);
            }

            // Shouldn't actually happen.. but must be transient or a bug.
            server = null;
            Thread.yield();
        }
        return server;
    }

    @Override
    public Server choose(Object key) {
        return choose(getLoadBalancer(), key);
    }

    @Override
    public void initWithNiwsConfig(IClientConfig clientConfig) {
    }
}
2、配置类中增加自定义规则
@Configuration
public class ConfigBean
{
    @Bean
    @LoadBalanced //Ribbon 是客户端负载均衡的工具;
    public RestTemplate getRestTemplate()
    {
        return new RestTemplate();
    }

    @Bean
    public IRule myRule()
    {
        return new CustomeRule(); //自定义负载均衡规则
    }
}
3、主启动类添加 @RibbonClient 注解,name和configuration参数很重要;

在启动该微服务的时候就能去加载我们自定义的Ribbon配置类,从而使配置生效:

@RibbonClient(name=“microservicecloud-dept”, configuration=ConfigBean.class)

name指定针对哪个服务 进行负载均衡,而configuration指定负载均衡的算法具体实现类。

4、测试

启动该消费者服务,然后访问:http://localhost/consumer/dept/get/1,可以看到先访问生产者1服务5次,然后访问生产者2服务5次…

4.总结

openfeign封装了Ribbon

在这里插入图片描述

Ribbon的负载均衡策略

在这里插入图片描述

Ribbon负责均衡策略图解

在这里插入图片描述

Ribbon常见负载均衡算法

在这里插入图片描述

Ribbon的实现类

在这里插入图片描述

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。

文章由极客之音整理,本文链接:https://www.bmabk.com/index.php/post/94144.html

(0)
小半的头像小半

相关推荐

极客之音——专业性很强的中文编程技术网站,欢迎收藏到浏览器,订阅我们!