负载均衡调用——Ribbon

一、简介

1.开源代码库

https://github.com/Netflix/ribbon

2、Ribbon是什么

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

3、Ribbon能干什么

Ribbon是部署在客户端的IPC库,提供以下功能:

  • 客户端负载均衡
  • 容错处理
  • 支持多协议的异步通信。支持HTTP、TCP、UDP协议。
  • 支持缓存和批量处理

Ribbon模块介绍:

Ribbon由多个组件组成,其中一些组件在内部用于生产,随着时间的推移,其中的一些组件已被非OSS解决方案所取代。这是因为Netflix开始着手于针对单一职责模块的RPC更加组件化的体系结构。因此,此时每个Ribbon功能区组件都会受到不同程度的关注。

  • ribbon: Ribbon功能应用入口。使用Ribbon的功能可以通过初始化应用入口,调用接口实现。该模块依赖其他模版实现所需功能,比如容错处理ribbon依赖Histrix。
  • ribbon-loadbalancer:负载均衡功能入口。如果仅需要负载均衡功能,可以使用单独使用该模块。
  • ribbon-eureka:基于Eureka客户端实现可用服务列表管理
  • ribbon-transport: 具备客户端负载均衡能力的,基于RxNetty框架能够支持HTTP、TCP、UDP协议的通信客户端。
  • ribbon-httpclient: 具备客户端负载均衡能力的,基于Apache HttpClient,支持REST风格的HTTP请求客户端。
  • ribbon-core: 客户端配置APIs和其他共享APIs。
  • ribbon-example:使用例子。

从官网源码仓库得知,当前项目已进入维护状态,但有些功能还是在生产中大规模部署的,在将来会被 Spring Cloud Loadbalancer替代(参考:https://spring.io/blog/2018/12/12/spring-cloud-greenwich-rc1-available-now

3、架构说明

在这里插入图片描述

Ribbon其实就是一个软负载均衡的客户端组件,他可以和其他所需请求的客户端结合使用,和eureka结合只是其中的一个实例。Ribbon在工作的时候分两步:

  1. 先选择EurekaServer,优选选择同一个区域内负载较少的Server;
  2. 根据用户指定的策略,从Server取到的服务注册列表中选择一个地址。

其中Ribbon提供了多种的负载均衡策略,如轮询、随机和根据响应时间加强等。

4、ribbon与nginx对比

Ribbon客户端负载均衡和Nginx服务端负载均衡的区别:

  • Nginx是服务器负载均衡,客户端所有请求都会交给Nginx,然后由Nginx实现转发请求,即负载均衡是由服务端实现的。
  • Ribbon是本地负载均衡,在调用微服务接口时候,会在注册中心上获取注册信息服务列表之后缓存到JVM本地,从而在本地实现RPC远程服务调用技术。

负载均衡又分为两类,分别可以对应于Nginx和Ribbon:

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

二、springboot中使用ribbon

1、pom依赖

在服务消费端使用ribbon

ribbon的依赖为:

<dependency>
	<groupId>org.springframework.cloud</groupId>
	<artifactId>spring-cloud-starter-netflix-ribbon</artifactId>
    <version>2.2.1.RELEASE</version>
    <scope>compile</scope>
</dependency>

而eureka的依赖为:

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

通过分析我们可以发现后者包含前者,所以通常引入后者即可

在这里插入图片描述

2、ribbon的负载均衡算法

在这里插入图片描述

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

这些负载均衡算法均是抽象类com.netflix.loadbalancer.AbstractLoadBalancerRule的实现,而抽象类实现了IRule接口。

3、ribbon算法的实现

官方文档明确给出了警告:自己自定义的轮询算法配置类不能放在@ComponentScan注解所扫描的当前包下以及子包下,否则自定义的这个配置类就会被所有Ribbon客户端所共享,就打不到特殊化定制的目的了,换句话说,如果这个配置类我们能够被@ComponentScan注解扫描到,那么访问所有的微服务提供方的具体实例时,我们都会采取配置类中的算法,如果要特殊化定制——即指定访问某些微服务提供方时采用配置的轮询算法,那么我们就应该使这个配置类让@ComponentScan注解扫描不到,我们知道,在主启动类的@SpringBootApplication注解中,其实这个注解包含了@SpringBootConfiguration@EnableAutoConfiguration@ComponentScan这三个注解,所以我们写的轮询算法配置类不能和主启动类在同一个包下,所以我们需要建新的包,实现定制轮询算法的配置类

  • 项目结构

在这里插入图片描述

  • 主启动类
package org.jun;

import org.Rule.myRule;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.EnableEurekaClient;
import org.springframework.cloud.netflix.ribbon.RibbonClient;

/**
 * @author junfeng.lin
 * @date 2021/2/10 11:45
 */
@SpringBootApplication
@EnableEurekaClient
//访问的微服务名称,采用配置文件中的算法
@RibbonClient(name = "eureka-order" ,configuration = myRule.class)
public class RibbonClient80 {
    public static void main(String[] args) {
        SpringApplication.run(RibbonClient.class ,args);
    }
}

4、负载均衡算法原理(轮询算法)

  • 轮询负载均衡算法原理

    实际调用的服务实例下标 = Rest接口第几次请求数 % 服务器集群总数量

    //获取服务列表的信息(所有服务名)
    List<String> services = discoveryClient.getServices();
          
    //根据微服务名称获取具体服务实例
    List<ServiceInstance> instances = discoveryClient.getInstances("service-nae");
    
  • 轮询负载均衡算法源码分析——choose()方法获取要选择的服务

private AtomicInteger nextServerCyclicCounter;

public RoundRobinRule() {
    //初始化下标为0,是原子类
    this.nextServerCyclicCounter = new AtomicInteger(0);
}

public Server choose(ILoadBalancer lb, Object key) {
    //如果没有开启负载均衡则返回空
    if (lb == null) {
        log.warn("no load balancer");
        return null;
    } else {
        Server server = null;
        int count = 0;

        while(true) {
            if (server == null && count++ < 10) {
                //获取所有存活的服务
                List<Server> reachableServers = lb.getReachableServers();
                //获取所有服务
                List<Server> allServers = lb.getAllServers();
                int upCount = reachableServers.size();
                int serverCount = allServers.size();
                if (upCount != 0 && serverCount != 0) {
                    //获取选中的服务列表的下标
                    int nextServerIndex = this.incrementAndGetModulo(serverCount);
                    //根据下标获取对应的服务
                    server = (Server)allServers.get(nextServerIndex);
                    if (server == null) {
                        Thread.yield();
                    } else {
                        if (server.isAlive() && server.isReadyToServe()) {
                            return server;
                        }

                        server = null;
                    }
                    continue;
                }

                log.warn("No up servers available from load balancer: " + lb);
                return null;
            }
            //如果超过10次获取不到服务则返回空
            if (count >= 10) {
                log.warn("No available alive servers after 10 tries from load balancer: " + lb);
            }

            return server;
        }
    }
}

private int incrementAndGetModulo(int modulo) {
    int current;
    int next;
    do {
        current = this.nextServerCyclicCounter.get();
        next = (current + 1) % modulo;
    } while(!this.nextServerCyclicCounter.compareAndSet(current, next));  //通过自旋锁获取下标值

    return next;
}

5、自定义负载均衡算法

  • 由于使用了自定义的负载均衡算法,则RestTemplcate的负载均衡注解可以去掉
@Configuration
public class ApplicationContextConfig {
    @Bean
//    @LoadBalanced//使用该注解赋予RestTemplate负载均衡的能力
    public RestTemplate getRestTemplate() {
        return new RestTemplate();
    }
}
  • 自定义负载均衡算法接口与实现类
import org.springframework.cloud.client.ServiceInstance;

import java.util.List;

/**
 * @author junfeng.lin
 * @date 2021/2/10 11:52
 */
public interface LoadBalancer
{
    ServiceInstance instances(List<ServiceInstance> serviceInstances);
}

import org.springframework.cloud.client.ServiceInstance;
import org.springframework.stereotype.Component;

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

/**
 * @author junfeng.lin
 * @date 2021/2/10 11:52
 */
@Component
public class MyLoadBalancer implements LoadBalancer
{

    private AtomicInteger atomicInteger = new AtomicInteger(0);

    public final int getAndIncrement()
    {
        int current;
        int next;

       do {
            current = this.atomicInteger.get();
            next = current >= 2147483647 ? 0 : current + 1;
        }while(!this.atomicInteger.compareAndSet(current,next));
        System.out.println("服务列表下标为: "+next);
        return current;
    }

    //负载均衡算法:rest接口第几次请求数 % 服务器集群总数量 = 实际调用服务器位置下标  ,每次服务重启动后rest接口计数从0开始。
    @Override
    public ServiceInstance instances(List<ServiceInstance> serviceInstances)
    {
        int index = getAndIncrement() % serviceInstances.size();

        return serviceInstances.get(index);
    }
}
  • 在Controller中使用
@Autowired
private RestTemplate restTemplate;
@Autowired
private DiscoveryClient discoveryClient;
@Autowired
private MyLoadBalancer myLoadBalancer;
@GetMapping("/myLoadBalancerGetPort")
public String myLoadBalancerGetPort() {
    //根据微服务名称获取具体服务实例
    List<ServiceInstance> instances = discoveryClient.getInstances("eureka-order");
    if (instances == null || instances.size() <= 0) {
        return null;
    }
    //调用自定义负载均衡类获取服务对象
    ServiceInstance instance = myLoadBalancer.instances(instances);
    //获取服务对象的统一资源标志符
    URI uri = instance.getUri();
    String port = restTemplate.getForObject(uri + "/getPort", String.class);
    return port;
}
  • 效果:通过debug模式查看,每次都是轮询的方式选择服务列表的服务

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

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值