服务负载均衡:Ribbon

一、Ribbon简介

1.1、什么是Ribbon

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

Ribbon 默认提供很多种负载均衡算法,例如轮询、随机等等,甚至包含自定义的负载均衡算法。

1.2、为什么要使用Ribbon

Ribbon 提供了一套微服务的负载均衡解决方案。目前业界主流的负载均衡方案可分成两类:

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

二、Ribbon入门案例

2.1、项目准备

接下来的操作全是基于上一次的Eureka的工程上进行操作,可参考Eureka文章。
在这里插入图片描述
因为我们之间项目中集成了新版的Eureka依赖,它里面就已经集成了Ribbon,所以无需添加依赖。
在这里插入图片描述

2.2、修改服务消费者

修改cloud-consumer-order80的ApplicationContextConfig代码,它是在spring容器中注册RestTemplate ,在里边新增以下代码@LoadBalanced就可以开启负载均衡。

我们一般采用RestTemplate +Ribbon的调用方式

@Configuration
public class ApplicationContextConfig {
    @Bean
    @LoadBalanced
    public RestTemplate getRestTemplate(){
        return new RestTemplate();
    }
}

三、Ribbon负载均衡策略

3.1、轮询策略(默认)

策略对应类名:RoundRobinRule

实现原理:轮询策略表示每次都顺序取下一个 provider,比如一共有 2 个 provider,第 1 次取第 1 个,第 2 次取第 2 个,第 3 次取第 1个,第4次取第2个,以此循环。

3.2、权重轮询策略

策略对应类名:WeightedResponseTimeRule

实现原理:

  • 根据每个 provider 的响应时间分配一个权重,响应时间越长,权重越小,被选中的可能性越低。
  • 原理:一开始为轮询策略,并开启一个计时器,每 30 秒收集一次每个 provider 的平均响应时间,当信息足够时,给每个 provider 附上一个权重,并按权重随机选择 provider,高权越重的 provider 会被高概率选中。

3.3、随机策略

策略对应类名:RandomRule

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

3.4、最少并发数策略

策略对应类名:BestAvailableRule

实现原理:选择正在请求中的并发数最小的 provider,除非这个 provider 在熔断中。

3.5、重试策略

策略对应类名:RetryRule

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

3.6、可用性敏感策略

策略对应类名:AvailabilityFilteringRule

实现原理:过滤性能差的 provider

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

3.7、区域敏感性策略

策略对应类名:ZoneAvoidanceRule

实现原理:

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

四、Ribbon负载均衡设置

4.1、全局替换

我们需要在consumer端编写配置类,注入负载均衡策略对象,所有服务请求均使用该策略。
注意: Ribbon官方规定这个配置类必须写在不被@ComponentScan所扫描的包下编写。

包的结构如下:
在这里插入图片描述

/**
 * Ribbon负载均衡策略配置类
 */
@Configuration
public class MyRibbonRule {

    @Bean
    public RandomRule randomRule() {
        return new RandomRule();
    }
}

然后在主配置类上标注注解RibbonClient,指定远程调用哪个服务以及负载均衡配置类。

@RibbonClient(name = "CLOUD-PAYMENT-SERVICE",configuration = MyRibbonRule.class)
@EnableEurekaClient
@SpringBootApplication
public class OrderMain80
{
    public static void main(String[] args) {
            SpringApplication.run(OrderMain80.class, args);
    }
}

测试效果如下:
在这里插入图片描述
在这里插入图片描述

五、Ribbon手写负载均衡

5.1、负载均衡算法

在这里插入图片描述

5.2、轮询策略源码

核心:自旋锁,private int incrementAndGetModulo(int modulo)方法用到了自旋锁

定义:自旋锁(spin lock)是一种非阻塞锁,也就是说,如果某线程需要获取锁,但该锁已经被其他线程占用时,该线程不会被挂起,而是在不断的消耗CPU的时间,不停的试图获取锁。线程反复检查锁变量是否可用。由于线程在这一过程中保持执行,因此是一种忙等待。

作用:自旋锁避免了进程上下文的调度开销。线程一直处于用户态,减少了用户态到内核态的开销与损耗(减少了上下文切换)。

适用场景
1、多线程
2、使用者占有锁的时间短

轮询源码:

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() {
        this.nextServerCyclicCounter = new AtomicInteger(0);
    }

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

    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;
                }

                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;
    }

    public Server choose(Object key) {
        return this.choose(this.getLoadBalancer(), key);
    }

    public void initWithNiwsConfig(IClientConfig clientConfig) {
    }
}

5.3、手写负载均衡

(1)我们可以在cloud-consumer-order80服务消费端创建一个接口LoadBalancer

public interface LoadBalancer {
    ServiceInstance instances(List<ServiceInstance> serviceInstances);
}

(2)在这个包中新建类:MyLoadBalanced实现LoadBalancer接口

package com.atguigu.springcloud.lb;

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

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

@Component
public class MyLoadBalanced implements LoadBalancer{
    private AtomicInteger atomicInteger = new AtomicInteger(0);

    //使用自旋锁进行累加操作
    private final int getAndIncrement() {
        int current;
        int next;
        do {
            current = this.atomicInteger.get();
            next = current >= Integer.MAX_VALUE ? 0 : current + 1;
            //第一个参数是期望值,第二个参数是修改值
        } while (!this.atomicInteger.compareAndSet(current, next));
        System.out.println("访问次数:" + next);
        return next;
    }
    
    //获取服务提供者实例下标
    @Override
    public ServiceInstance instances(List<ServiceInstance> serviceInstances) {
        int index = getAndIncrement() % serviceInstances.size();
        return serviceInstances.get(index);
    }
}

(3)然后在生产者端cloud-provider-payment8001和cloud-provider-payment8002的Controller编写测试代码,方便查看负载均衡效果。

    @GetMapping(value = "/payment/lb")
    public Integer getPaymentLB(){
        return serverPort;
    }

(4)然后在生产者端cloud-consumer-order80的Controller编写远程调用测试代码。

package com.atguigu.springcloud.controller;

import com.atguigu.springcloud.entities.CommonResult;
import com.atguigu.springcloud.entities.Payment;
import com.atguigu.springcloud.lb.LoadBalancer;
import lombok.extern.log4j.Log4j;
import lombok.extern.slf4j.Slf4j;
import org.springframework.cloud.client.ServiceInstance;
import org.springframework.cloud.client.discovery.DiscoveryClient;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.client.RestTemplate;

import javax.annotation.Resource;
import java.net.URI;
import java.util.List;

@RestController
@Slf4j
public class OrderController {
    public static final String PAYMENT_URL = "http://CLOUD-PAYMENT-SERVICE";

    @Resource
    private RestTemplate restTemplate;

    @Resource
    private LoadBalancer loadBalancer;

    @Resource
    private DiscoveryClient discoveryClient;


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

    @GetMapping("/consumer/payment/create")
    public CommonResult create(Payment payment)
    {
        return restTemplate.postForObject(PAYMENT_URL+"/payment/create",payment,CommonResult.class);
    }

    @GetMapping("/consumer/payment/lb")
    public Integer getlb()
    {
        //获取指定服务名的实例
        List<ServiceInstance> instances = discoveryClient.getInstances("CLOUD-PAYMENT-SERVICE");
        if(instances==null||instances.size()<=0){
            return null;
        }
        //调用负载均衡方法
        ServiceInstance serviceInstance = loadBalancer.instances(instances);
        //获取当前的uri
        URI uri = serviceInstance.getUri();
        return restTemplate.getForObject(uri+"/payment/lb",Integer.class);
    }

}

运行效果如下:
在这里插入图片描述

注意:需要去掉@LoadBalance注解

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值