Ribbon负载均衡

Ribbon负载均衡

1、什么是Ribbon

Ribbon是一个基于HTTP和TCP的客户端负载均衡工具,它是基于Netflix Ribbon实现的。它不像Spring Cloud服务注册中心、配置中心、API网关那样独立部署,但是它几乎存在于每个Spring Cloud微服务中,包括Feign提供的声明式服务调用也是基于该Ribbon实现的。

Ribbon默认提供多种负载均衡算法,例如:轮训、随机等等。也可以自定义负载均衡算法。

2、负载均衡不同方案的区别

目前业界主流的负载均衡方案可分成两类:

  • 集中式负载均衡(服务器负载均衡),即在consumer和provider之间使用独立的负载均衡设施(可以是硬件,如F5,也可以说是软件,如nginx)由该设施负载把访问请求通过某种策略转发。
  • 进程内负载均衡(客户端负载均衡),将负载均衡逻辑集成到consumer,consumer从服务注册中心获知有哪些地址可用,然后自己再从这些地址中选择出一个合适的provider。

3、Ribbon负载均衡策略

image-20220414184443857

3.1、轮询策略(默认)

com.netflix.loadbalancer.RoundRobinRule

实现原理:接口第几次请求数%服务器集群总数量=实际调用服务器位置下标,每次服务器重启后接口计数从1开始。

比如一共有5个provider,第一次取第一个,第二次取第二个,第三次取第三个,以此类推。

3.2、权重轮询策略

com.netflix.loadbalancer.WeightedResponseTimeRule

对RoundRobinRule的扩张,根据每个provider的响应时间分配一个权重,响应时间越长,权重越小,被选中的可能性就越低。

实现原理:一开始为轮询策略,并开启一个计时器,每30秒收集一次每个provider的平均响应时间,当信息足够时,给每个provider附上一个权重值,并按权重随机选择provider,高权重的provider会被高概率选中。

3.3、随机策略

com.netflix.loadbalancer.RandomRule

实现原理:随机从provider列表中选择一个。

3.4、最少并发数策略

com.netflix.loadbalancer.BestAvailableRule

实现原理:会先过滤掉由于多次访问故障而处于熔断的服务,然后选择一个并发最小的服务

3.5、重试策略

com.netflix.loadbalancer.RetryRule

实现原理:其实就是轮询策略的增强版,轮询策略服务不可用时不做处理,重试策略服务不可用时会重新尝试集群中的其他节点。

3.6、可用性敏感策略

com.netflix.loadbalancer.AvailabilityFilteringRule

实现原理:先过滤故障实例,再选择并发较小的实例

  • 第一种:过滤掉在Eureka中一直处于连接失败的provider
  • 第二种:过滤掉高并发(繁忙)的provider

3.7、区域敏感性策略

com.netflix.loadbalancer.ZoneAvoidanceRule

实现原理:

  • 以一个区域为单位考察可用性,对于不可用的区域整个丢弃,从剩下的区域中选择可用的provider
  • 如果这个ip区域内有一个或者多个实例不可达成或响应变慢,都会降低该ip区域内其他ip被选中的权重

4、Ribbon入门案例

Ribbon默认采用轮询的负载策略。

实例代码:https://gitee.com/junweihu/eureka-demo

    private List<Product> selectProductListByLoadBalancerClient() {
        StringBuffer sb = null;

        // 根据服务名称获取服务
        ServiceInstance providerInstances = loadBalancerClient.choose("SERVICE-PROVIDER");
        if (providerInstances == null) {
            return null;
        }

        sb = new StringBuffer();
        String host = providerInstances.getHost();
        int port = providerInstances.getPort();
        sb.append("http://" + host + ":" + port + "/product/list");
        log.info("call address:{}", sb.toString());

        ResponseEntity<List<Product>> exchange = restTemplate.exchange(
                sb.toString(),
                HttpMethod.GET,
                null,
                new ParameterizedTypeReference<List<Product>>() {
                });

        return exchange.getBody();
    }

image-20220414161818796

5、Ribbon负载均衡策略设置

5.1、全局

在启动类或配置类中注入负载均衡策略对象。所有服务请求均使用该策略。

    /**
     * 随机负载均衡
     * @return
     */
    @Bean
    public RandomRule randomRule() {
        return new RandomRule();
    }

多次访问结果如下

image-20220414162456342

5.2、局部

修改配置文件,指定服务的负载均衡策略。格式:服务应用名.ribbon.NFLoadBalancerRuleClassName

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

image-20220414170526563

6、Ribbon点对点直连

点对点直连是指绕过注册中心,直连服务提供者获取服务,一般用在测试。

6.1添加依赖

在pom中引入Ribbon的依赖,注意如果pom中有Eureka的依赖,需要剔除Eureka的依赖。

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

6.2配置文件

配置文件中关闭Eureka,添加直连的服务地址。如果不设置负载均衡策略默认使用轮询策略。

# 负载均衡策略
# service-provider为调用的服务名称
service-provider:
  ribbon:
    NFLoadBalancerRuleClassName: com.netflix.loadbalancer.RandomRule
    # 指定具体的服务列表,多个用逗号间隔
    listOfServers: http://localhost:7070,http://localhost:7071

# 关闭Eureka实现点对点
ribbon:
  eureka:
    enabled: false

6.3访问

关闭Eureka注册中心,服务提供者由于无法注册到注册中心所以连接异常。但是服务是可以正常访问的。

7、Ribbon负载均衡算法

Ribbon核心组件IRule:根据特定算法从服务列表中选取一个要访问的服务

image-20220414182902885

7.1、源码

RoundRobinRule源码分析,负载均衡的核心在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();

            //没有可用的服务
            if ((upCount == 0) || (serverCount == 0)) {
                log.warn("No up servers available from load balancer: " + lb);
                return null;
            }

            // 获取服务列表的下标
            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;
    }

    // CAS自旋获取当前实例下标值
    private int incrementAndGetModulo(int modulo) {
        for (;;) {
            int current = nextServerCyclicCounter.get();
            // 实际调用服务器位置下标=接口第几次请求数%服务器集群总数量
            int next = (current + 1) % modulo;
            if (nextServerCyclicCounter.compareAndSet(current, next))
                return next;
        }
    }

7.2、自定义

定义负载均衡接口

public interface ILoadBalancer {
    ServiceInstance instance(List<ServiceInstance> serviceInstances);
}

接口实现类

@Component
@Slf4j
public class MyLB implements ILoadBalancer{

    //记录当前请求次数
    private AtomicInteger currentRequest = new AtomicInteger(0);

    @Override
    public ServiceInstance instance(List<ServiceInstance> serviceInstances) {
        int index = getAndIncrement() % serviceInstances.size();
        ServiceInstance instance = serviceInstances.get(index);
        return instance;
    }

    private final int getAndIncrement() {
        int current, next;
        do {
            current = this.currentRequest.get();
            next = current > Integer.MAX_VALUE ? 0 : current + 1;
        } while (!this.currentRequest.compareAndSet(current, next));
        log.info("当前第:{}次访问", next);

        return next;
    }
}

使用

    @Autowired
    private DiscoveryClient discoveryClient;
    @Autowired
    private ILoadBalancer loadBalancer;

    public List<Product> selectProductListByMyLb() {
        // 获取服务列表
        List<String> services = discoveryClient.getServices();
        if (CollectionUtils.isEmpty(services)) {
            return null;
        }

        // 根据服务名称获取服务
        List<ServiceInstance> providerInstances = discoveryClient.getInstances("service-provider");
        if (CollectionUtils.isEmpty(providerInstances)) {
            return null;
        }

        ServiceInstance instance = loadBalancer.instance(providerInstances);
        String host = instance.getHost();
        int port = instance.getPort();
        StringBuffer sb = new StringBuffer("http://" + host + ":" + port + "/product/list");
        log.info("call address:{}", sb.toString());

        ResponseEntity<List<Product>> exchange = restTemplate.exchange(
                sb.toString(),
                HttpMethod.GET,
                null,
                new ParameterizedTypeReference<List<Product>>() {
                });

        return exchange.getBody();
    }

效果
image-20220415145004813

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值