【Hoxton.SR1版本】Spring Cloud Ribbon负载均衡服务调用

目录

一、简介

二、Ribbon架构说明

三、负载均衡演示

四、Ribbon负载均衡策略

五、如何替换默认的轮训策略?

六、自定义轮询算法

七、总结


一、简介

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

Ribbon相关官网可以参考如下地址:https://github.com/Netflix/ribbon

由上面的官网,我们可以看到Ribbon已经进入了维护模式:

【Hoxton.SR1版本】Spring Cloud Ribbon负载均衡服务调用_第1张图片

但是由于Ribbon功能太强大,并且目前在生产环境大量使用,Spring Cloud想推出Load Balancer去替代Ribbon,但是目前为止,还没完全替代Ribbon。

  • Ribbon能干嘛?

Ribbon主要提供服务负载均衡调用的功能,简单的说,就是将用户的请求平摊的分配到多个服务上,从而达到系统的HA(高可用)

,常见的负载均衡有软件Nginx,LVS,硬件F5等。主要分为下面两种方式:

  • 集中式负载均衡

例如Nginx就是集中式负载均衡,Nginx是服务器负载均衡,客户端所有请求都会交给Nginx,然后由nginx实现请求转发,即负载均衡是由服务器实现的。

  • 进程内负载均衡

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

二、Ribbon架构说明

Ribbon大体架构如下图所示:

说明:服务消费者自身集成Ribbon客户端负载均衡软件,它会去服务注册中心查询当前可用的服务注册列表,然后根据用户指定的负载均衡策略,去请求目前空闲的一个服务提供者进行调用。

三、负载均衡演示

其实在前面几节文章中我们都实现了Ribbon负载均衡的功能,我们可以启动:

【Hoxton.SR1版本】Spring Cloud Ribbon负载均衡服务调用_第2张图片

五个微服务,这几个微服务在前面章节中都已经详细实现过,这里不再过多说明。浏览器多次访问:http://localhost/consumer/payment/get/3,观察运行结果:

可见,我们默认是按照轮训的负载均衡策略进行远程调用,实质最主要的配置就是RestTemplate + @LoadBalanced注解实现。

/**
 * @Description 全局配置文件类
 * @Date 2020/7/25 15:45
 * @Author weishihuai
 * 说明: 此处配置也可以放在主启动类中
 */
@Configuration
public class CustomConfig {

    //注册到spring ioc容器中
    @Bean
    @LoadBalanced  //开启负载均衡功能
    public RestTemplate restTemplate() {
        return new RestTemplate();
    }

}

细心的小伙伴可能会发现,查看我们服务消费方的maven依赖,我们看到其实我们并没有引入Ribbon的相关依赖包,但是为啥它也能实现服务的负载均衡呢?

原因其实是:在新版本的SpringCloud中,当引入了spring-cloud-starter-netflix-eureka-client Eureka客户端依赖包之后,其实就自动帮我们引入Ribbon负载均衡的依赖。下面我们查看spring-cloud-starter-netflix-eureka-client这个包依赖的其他包信息:

【Hoxton.SR1版本】Spring Cloud Ribbon负载均衡服务调用_第3张图片

四、Ribbon负载均衡策略

Ribbon负载均衡策略主要的组件就是IRule组件,根据特定算法从服务列表中选取一个要访问的服务。IRule是一个接口,源码如下:

package com.netflix.loadbalancer;

public interface IRule {
    Server choose(Object var1);

    void setLoadBalancer(ILoadBalancer var1);

    ILoadBalancer getLoadBalancer();
}

主要的策略实现类有下面几种,UML类图如下所示:

【Hoxton.SR1版本】Spring Cloud Ribbon负载均衡服务调用_第4张图片

  • RoundRobinRule:轮训算法,也是默认算法
  • RandomRule : 随机策略算法;
  • RetryRule:先按照RoundRobinRule的策略获取服务,如果获取服务失败则在指定时间内进行重试;
  • BestAvailableRule:会先过滤掉由于多次访问故障而处于断路器跳闸状态的服务,然后选择一个并发量最小的服务;
  • AvailabilityFilteringRule:先过滤掉故障实例,再选择并发较小的实例;  
  • WeightedResponseTimeRule:对RoundRobinRule的扩展,响应速度越快的实例选择权重越大,越容易被选择;

五、如何替换默认的轮训策略?

其实并不是所有业务场合都适合使用默认的轮训算法进行负载均衡,有时候我们确实有需要去替换掉默认的轮训算法,接下来我们讲解如何替换默认的轮训策略,指定其他策略算法或者自定义自己的策略规则。

这里需要注意一点:

【Hoxton.SR1版本】Spring Cloud Ribbon负载均衡服务调用_第5张图片

官方文档明确给出了警告:自定义配置类不能放在@ComponentScan所扫描的当前包以及子包下,否则我们自定义的这个配置类就会被所有的Ribbon客户端所共享,达不到特殊化定制的目的。简单的说,自定义Ribbon算法的时候,自定义类不能够放在@ComponentScan下扫描的包以及子包下,因此MyRule.java不能放在SpringBoot启动类的包以及子包下。例如下图这种结构:

【Hoxton.SR1版本】Spring Cloud Ribbon负载均衡服务调用_第6张图片

 主要步骤有以下两步:

【a】自定义ribbon配置类

package com.wsh.myrule;

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

/**
 * @Description 自定义ribbon配置类不能够放在@ComponentScan下扫描的包以及子包下.
 * @Date 2020/8/4 15:16
 * @Author weishihuai
 * 说明: 
 */
@Configuration
public class CustomRibbonConfig {

    @Bean
    public IRule customRule() {
        //定义为随机策略
        return new RandomRule();
    }

}

【b】启动类指定我们自己的负载均衡算法

@SpringBootApplication
@EnableEurekaClient
//指定我们自己配置的负载均衡策略
@RibbonClient(name = "SPRINGCLOUD-PAYMENT-SERVICE", configuration = CustomRibbonConfig.class)
public class OrderServiceApplication80 {
    public static void main(String[] args) {
        SpringApplication.run(OrderServiceApplication80.class, args);
    }
}

【c】启动项目,通过浏览器访问三次:http://localhost/consumer/payment/get/3,运行结果如下图所示:

可见,我们替换了随机策略后,ribbon客户端在挑选服务的时候就采用的随机性去选择空闲的服务进行调用了。

六、自定义轮询算法

接下来,我们仿照着RoundRobinRule轮询策略的源码,自己手写一个简单的轮训负载均衡算法。

【a】服务提供者8001、8002微服务的controller增加对应的方法

@GetMapping(value = "/payment/customLoadBalancer")
    public String customLoadBalancer() {
        return "当前服务实例端口号:" + serverPort;
    }

【b】服务消费者order80注释掉@LoadBalanced,关闭自带的负载均衡功能

@Configuration
public class CustomConfig {

    //注册到spring ioc容器中
    @Bean
//    @LoadBalanced  //开启负载均衡功能
    public RestTemplate restTemplate() {
        return new RestTemplate();
    }

}

【c】自定义LoadBalancer接口

package com.wsh.springcloud.loadbalance;

import org.springframework.cloud.client.ServiceInstance;

import java.util.List;

/**
 * @Description 自定义LoadBalancer
 * @Date 2020/8/4 16:13
 * @Author weishihuai
 * 说明:
 */
public interface CustomLoadBalancer {
    /**
     * 从Eureka服务注册列表中获取某个服务实例
     *
     * @param serviceInstanceList 服务注册列表
     * @return
     */
    ServiceInstance getInstances(List serviceInstanceList);

}

【d】自定义LoadBalancer实现类

package com.wsh.springcloud.loadbalance;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.cloud.client.ServiceInstance;
import org.springframework.stereotype.Component;

import java.util.List;
import java.util.concurrent.atomic.AtomicInteger;

/**
 * @Description 自定义LoadBalancer实现类
 * @Date 2020/8/4 16:13
 * @Author weishihuai
 * 说明:
 */
@Component
public class CustomLoadBalancerImpl implements CustomLoadBalancer {
    private static final Logger logger = LoggerFactory.getLogger(CustomLoadBalancerImpl.class);

    //原子整形类
    private AtomicInteger atomicInteger = new AtomicInteger(0);

    /**
     *
     * @return
     */
    public final int getAndIncrement() {
        int current;
        //记录第几次访问
        int visitIndex;
        //自旋锁比较
        do {
            //获取当前值
            current = this.atomicInteger.get();
            visitIndex = current >= Integer.MAX_VALUE ? 0 : (current + 1);
        } while (!this.atomicInteger.compareAndSet(current, visitIndex)); //采用CAS算法保证原子操作
        logger.info("第几次访问次数 : {}", visitIndex);
        return visitIndex;
    }

    @Override
    public ServiceInstance getInstances(List serviceInstanceList) {
        int index = getAndIncrement() % serviceInstanceList.size();
        return serviceInstanceList.get(index);
    }

}

【e】服务消费者order80增加如下方法:

 @GetMapping("/consumer/payment/customLoadBalancer")
    public String customLoadBalancer() {
        List instances = discoveryClient.getInstances("SPRINGCLOUD-PAYMENT-SERVICE");
        if (instances == null || instances.size() <= 0) {
            return null;
        }
        ServiceInstance serviceInstance = customLoadBalancer.getInstances(instances);
        //获取服务提供者的URI
        URI serviceInstanceUri = serviceInstance.getUri();
        return restTemplate.getForObject(serviceInstanceUri + "/payment/customLoadBalancer", String.class);
    }

【f】启动项目,浏览器访问多次:http://localhost/consumer/payment/customLoadBalancer,运行结果如下图:

 

 

可见,如上四次访问,ribbon客户端采用了轮询策略进行了访问。 

七、总结

以上就是Ribbon服务负载均衡搭建的详细过程,相关项目的代码我已经放在Gitee上,有需要的小伙伴可以去拉取进行学习:https://gitee.com/weixiaohuai/springcloud_Hoxton,由于笔者水平有限,如有不对之处,还请小伙伴们指正,相互学习,一起进步。

你可能感兴趣的:(SpringCloud,Ribbon,负载均衡,Spring,Cloud)