⭐️ SpringCloud 入门实战系列不迷路:

  • SpringCloud 入门实战(一)什么是SpringCloud?
  • SpringCloud 入门实战(二)-SpringCloud项目搭建
  • SpringCloud 入门实战(三)-Eureka注册中心集成
  • SpringCloud入门 实战(四)-Zookeeper、Consul注册中心集成
  • SpringCloud入门实战(五)-Ribbon负载均衡集成
  • SpringCloud入门实战(六)-OpenFeign服务调用集成


文章目录

  • 一、Ribbon简介
  • 二、Ribbon和Nginx负载均衡区别
    • 1、Ribbon和Nginx负载均衡区别:
    • 2、Ribbon负载均衡
  • 三、RestTemplate
  • 四、Ribbon核心组件IRule
    • 1、Ribbon与Eureka
    • 2、Ribbon核心组件IRule
    • 3、7种负载均衡算法如何切换?
  • 五、默认负载均衡算法原理
  • 六、RoundRobinRule轮询算法源码分析

一、Ribbon简介

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

由官网https://github/Netflix/ribbon可知,Ribbon也进入维护中了,但是生产中大量使用中,还是比较优秀,不是立刻退出技术舞台的时刻。

Tip:未来可替换方案趋势:Spring 的LoadBalancer
Ribbon=负载均衡+RestTemplate

二、Ribbon和Nginx负载均衡区别

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

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

1、Ribbon和Nginx负载均衡区别:

  • ​Nginx是服务器负载均衡,客户端所有请求都会交给Nginx,然后由Nginx实现转发请求。即负载均衡是由服务端实现的。

  • Ribbon本地负载均衡,在调用微服务接口时候,会在注册中心上获取注册信息服务列表之后缓存到JVM本地,从而在本地实现RPC远程服务调用技术

2、Ribbon负载均衡

  • 进程内LB将LB逻辑集成到消费方,消费方从服务注册中心获知有哪些地址可用,然后再从这些地址中选择出一台合适的服务器。它只是一个类库,集成于消费方进程。
  • 集中式LB:即在服务的消费方和提供方之间使用独立的LB设施(可以是硬件,如F5,也可以是软件,如Nginx),由该设施负责把访问请求通过某种策略转发至服务提供方。

eg:我们的order调用payment工程,ribbon应该在我们的order服务消费方

三、RestTemplate

RestTemplate的一些方法 :
获取数据结果可通过 RestTemplate 的getForObjectgetForEntity 发送数据postForObject,getForEntity 中可以获取返回的状态码、请求头等信息,通过 getBody 获取响应的内容。其余的和 getForObject 一样,也是有 3 个重载的实现:

@RestController
@RequestMapping("/consumer")
@Slf4j
public class OrderController {
    public static final String PAYMENT_URL = "http://CLOULD-PAYMENT";
    @Resource
    private RestTemplate restTemplate;

    @PostMapping("/order")
    public CommonResult<Payment> createOrder(@RequestBody Payment payment) {
        return restTemplate.postForObject(PAYMENT_URL + "/payment", payment, CommonResult.class);
    }

    @GetMapping("/order/{id}")
    public CommonResult<Payment> getOrderPayment(@PathVariable("id") Long id) {
        return restTemplate.getForObject(PAYMENT_URL + "/payment/" + id, CommonResult.class);
    }

    @GetMapping("/order/getForEntity/{id}")
    public CommonResult<Payment> getOrderPayment2(@PathVariable("id") Long id) {
        ResponseEntity<CommonResult> entity = restTemplate.getForEntity(PAYMENT_URL + "/payment/" + id, CommonResult.class);
        if (entity.getStatusCode().is2xxSuccessful()) {
            return entity.getBody();
        } else {
            return new CommonResult<>(444, "操作失败");
        }
    }
}

四、Ribbon核心组件IRule

Ribbon在工作时分为两步
第一步:先选择 EurekaServer,它优先选择在同一个区域内负载较少的Server;
第二步:再根据用户指定的策略,在从Server取到的服务注册列表中选择一个地址;
其中Ribbon提供了多种策略,比如轮询、随机、根据响应时间加权。

1、Ribbon与Eureka

spring-cloud-starter-netflix-eureka-client3.0.x版本以前是包含对ribbon的依赖的,官方内置了随机和轮询负载均衡策略,所以不用额外引入ribbon依赖也可以使用,2020.0版本后移除了ribbon依赖。

 <dependency>
     <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
            <version>不同版本</version>
       </dependency>

eg:spring-cloud-starter-netflix-eureka-client2.0.x版本是包含对ribbon的依赖的,不需特殊引入

但是Spring Cloud在2020.0版本后,如3.1.3版本就移除了Ribbon,用的是 loadbalancer:

由于我们之前搭建的工程都是基于最新版本的springcloud 2021.0.3版本,导入Eureka依赖spring-cloud-starter-netflix-eureka-client后检查不到Ribbon的依赖,无法查看IRule接口代码,是使用LoadBalancer代替的Ribbon。

所以我们新搭建一个springcloud001版本以下的工程学习Ribbon,注意引入的依赖:
父pom:

 <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-dependencies</artifactId>
        <version>2.0.2.RELEASE</version>
        <type>pom</type>
        <scope>import</scope>
      </dependency>
      <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-dependencies</artifactId>
        <version>Finchley.RELEASE</version>
        <type>pom</type>
        <scope>import</scope>
      </dependency>

子pom:

 <!--Eureka的依赖版本不要填,因为dependencyManagement中指定了版本,交给maven自动匹配就好-->
 <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
    </dependency>

IRule接口在ribbon-loadbalancer包中,下载源码,可以查看IRule类图

2、Ribbon核心组件IRule

IRule 是一个接口。其作用是:根据特定算法从服务列表中选取一个要访问的服务,Ribbon默认的算法为轮询算法

IRule类图

Ribbon中的7种负载均衡算法:

  • comflix.loadbalancer.RoundRobinRule:轮询(默认)
  • comflix.loadbalancer.RandomRule:随机
  • comflix.loadbalancer.RetryRule:先按照RoundRobinRule的策略获取服务,如果获取服务失败则在指定时间内会进行重试
  • WeightedResponseTimeRule :对RoundRobinRule的扩展,响应速度越快的实例选择权重越大,越容易被选择
  • BestAvailableRule :会先过滤掉由于多次访问故障而处于断路器跳闸状态的服务,然后选择一个并发量最小的服务
  • AvailabilityFilteringRule :先过滤掉故障实例,再选择并发较小的实例
  • ZoneAvoidanceRule:默认规则,复合判断server所在区域的性能和server的可用性选择服务器

3、7种负载均衡算法如何切换?

前提:自定义配置类不能放在@ComponentScan所扫描的当前包及子包下,否则,我们的自定义配置配就会被所有的Ribbon客户端所共享,达不到特殊定制化目的。

1)首先定义自己的规则类(注意包的层级,不能放在@ComponentScan所扫描的当前包及子包下,也就是与启动类所在包区分开)

前边有提到Ribbon负载均衡是进程内LB,将LB逻辑集成到消费方,所以在order工程里定义规则类

2)主启动类配置上自己的规则

@RibbonClient(name="CLOUD-PROVIDER-PAYMENT",configuration = MySelfRule.class)

测试:http://localhost/consumer/order/1 发现8001、8002随机访问我们的payment 工程的,达到自定义的RandomRule规则效果。

五、默认负载均衡算法原理

负载均衡算法:rest接口第几次请求数%服务器集群总数=实际调用服务器下标,每次重启服务后rest接口技术从1开始

eg:本例中 8001+8002组合为2台的payment集群,up(2)可看到健康的可达的机器为2.
List[0] instances=120.0.0.1:8081
List[1] instances=120.0.0.1:8082
比如第一次请求进来,1%2=1,返回去下标是1,第二次请求,2%2=0,所以8001,8002来回切换,即默认的轮询算法的原理

六、RoundRobinRule轮询算法源码分析

IRule.java

RoundRobinRule.java


public class RoundRobinRule extends AbstractLoadBalancerRule {

    private AtomicInteger nextServerCyclicCounter;
    private static final boolean AVAILABLE_ONLY_SERVERS = true;
    private static final boolean ALL_SERVERS = false;

    private static Logger log = LoggerFactory.getLogger(RoundRobinRule.class);

    public RoundRobinRule() {
        nextServerCyclicCounter = new AtomicInteger(0);
    }

    public RoundRobinRule(ILoadBalancer lb) {
        this();
        setLoadBalancer(lb);
    }

    //实现接口的方法choose,选择哪一个负载均衡算法
    public Server choose(ILoadBalancer lb, Object key) {
         //没有负载均衡算法,报错
        if (lb == null) {
            log.warn("no load balancer");
            return null;
        }

        Server server = null;
        int count = 0;
        while (server == null && count++ < 10) {
             //选择可达的健康的机器(活着的)
            List<Server> reachableServers = lb.getReachableServers();
            List<Server> allServers = lb.getAllServers();
            int upCount = reachableServers.size();
            int serverCount = allServers.size();
			//upCount健康机器数
            if ((upCount == 0) || (serverCount == 0)) {
                log.warn("No up servers available from load balancer: " + lb);
                return null;
            }
            //nextServerIndex 计算返回的机器下标,用到了CAS自旋锁知识
            //(上面提到的机器总数2台,轮询返回下标0、1)
            int nextServerIndex = incrementAndGetModulo(serverCount);
            server = allServers.get(nextServerIndex);

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

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

            // Next.
            server = null;
        }

        if (count >= 10) {
            log.warn("No available alive servers after 10 tries from load balancer: "
                    + lb);
        }
        return server;
    }

    /**
     * Inspired by the implementation of {@link AtomicInteger#incrementAndGet()}.
     *
     * @param modulo The modulo to bound the value of the counter.
     * @return The next value.
     */
  //计算返回的机器下标,用到了CAS自旋锁
    private int incrementAndGetModulo(int modulo) {
        for (;;) {
            int current = nextServerCyclicCounter.get();
            int next = (current + 1) % modulo;
            if (nextServerCyclicCounter.compareAndSet(current, next))
                return next;
        }
    }

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

    @Override
    public void initWithNiwsConfig(IClientConfig clientConfig) {
    }
}

小结:RoundRobinRule轮询算法用到了CAS自旋锁知识

项目demo源码地址:https://gitlab/springcloud5521407

更多推荐

SpringCloud入门实战(五)-Ribbon负载均衡集成