Spring Cloud Rbibbon负载均衡调用 , Ribbon是Netflix发布的开源项目,主要功能是提供客户端软件的负载均衡算法和服务调用。Ribbon的客户端提供一系列的完善的配置项,重试。简单地说,就是在配置文件中列出Laod Balancer(LB)后面所有的机器,Ribbon会自动帮助你基于某种规则(如简单轮询、随即链接)区连接这些机器。

Ribbon负载均衡是本地负载均衡即客户端的负载均衡,在调用微服务接口时,会在注册中心上面获取注册信息服务列表缓存到JVM本地,从而在本地实现RPC远程服务调用技术。

1.依赖引入

在之前使用Eureka注册发现服务时,Eureka的依赖中已经整合了Ribbbon,所以在这种情况不用添加依赖,没有时需要添加下面的依赖:

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

2.负载演示

架构图

Ribbon在工作时分成两步:

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

Ribbon提供了多种策略,比如轮询、随机访问、根据响应时间加权。

Ribbon一般配合RestTemplate使用,我们之前在消费者服务的配置类:

package live.yremp.springcloud.config;

import org.springframework.cloud.client.loadbalancer.LoadBalanced;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.client.RestTemplate;

@Configuration
public class ApplicationContextConfig {
    @Bean
    @LoadBalanced //负载均衡能力
    public RestTemplate getRestTemplate(){
        return new RestTemplate();
    }
}

这里已经添加了@LoadBalance注解,开启负载均衡功能(默认是轮询方式)

2.RestTemplate的使用

2.1 GetForObject

GetForObject:返回对象为响应体中数据转化成的对象,基本可以理解为Json

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

2.2 GetForEntity

GetForEntity:返回对象为ResponseEntity 对象,包含了响应中一些重要的信息,比如响应头、响应状态码、响应体等等。

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

3. 核心组件IRule

3.1 IRule接口

IRule:根据特定算法从服务列表中选取一个要访问的服务,下面是IRule接口

public interface IRule {
    Server choose(Object var1);

    void setLoadBalancer(ILoadBalancer var1);

    ILoadBalancer getLoadBalancer();
}

它的实现类

  1. RoundRobinRule:轮询
  2. RandomRule:随机
  3. RetryRule:先按照RoundRobinRule的策略获取服务,失败之后在指定时间内重试
  4. WeightedResponseTimeRule:对于原有RoundRobinRule的拓展,响应时间越快的实例选择权重越大,越容易被选择。
  5. BestAvailableRule:会先过滤掉多次访问故障而处于断路器跳闸状态的服务,然后选择一个并发量较小的服务。
  6. AvailabilityFilteringRule:先过滤掉故障实例,再选者并发较小的实例
  7. ZoneAvoidanceRule:符合判断server所在区域的的性能和server的可用性选择服务。

4.如何替换

自定义配置类不能放到ComponentScan所能扫描的包以及子包下,可以新建一个包:myrule

MyselfRule

package live.yremp.myrule;

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

@Configuration
public class MyselfRule {
    @Bean
    public IRule myRule(){
        //定义为随机
        return new RandomRule();
    }
}

要使用自定义的配置类还需要在主启动类上添加@RibbonClient注解,如下

package live.yremp.springcloud;

import live.yremp.myrule.MyselfRule;
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;

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

此时访问将不是默认的轮询算法,不再是8002和8001交替出现,而是随机出现。

5.负载均衡算法

5.1 默认轮询算法原理

默认负载均衡算法: rest接口第几次请求数%服务器集群总数量=实际调用服务器位置下标,每次服务重启动后rest接口计数从1开始。

List instances = discoveryClient getInstances("CLOUD-PAYMENT-SERVICE");

  • List[0] instances =127.0.0.1:8001
  • List[1] instances =127.0.0.1:8002

8001+8002构成集群,他们共计两台机器,集群总数为2,按照轮询算法原理如下:

  1. 总请求数为1时:1%2=1 对应下标位置为1,则获得服务器地址为127.0.0.1:8002
  2. 总请求数为2时:2%2=0 对应下标位置为1,则获得服务器地址为127.0.0.1:8001
  3. 总请求数为3时:3%2=1 对应下标位置为1,则获得服务器地址为127.0.0.1:8002
  4. 总请求数为4时:4%2=0 对应下标位置为1,则获得服务器地址为127.0.0.1:8001
  5. 依次类推...

5.2 轮询源码分析

package com.netflix.loadbalancer;

import com.netflix.client.config.IClientConfig;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

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

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

    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) {
            //获取健康的(UP)状态的服务
            List<Server> reachableServers = lb.getReachableServers();
            //获取所有的服务
            List<Server> allServers = lb.getAllServers();
            //健康的服务计数
            int upCount = reachableServers.size();
            //所有的服务器
            int serverCount = allServers.size();
            //没有健康的服务(UP状态)记录异常
            if ((upCount == 0) || (serverCount == 0)) {
                log.warn("No up servers available from load balancer: " + lb);
                return null;
            }
            //下一次要启动的服务下标索引index
            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.
     */
    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) {
    }
}

5.4 手写轮询算法

添加一个lb(LoadBalance)包下面有一个LoadBalancer接口以及它的实现类

5.4.1 LoadBalancder接口

package live.yremp.springcloud.lb;

import org.springframework.cloud.client.ServiceInstance;

import java.util.List;

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

5.4.2 MyLoadBalancer

package live.yremp.springcloud.lb;

import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.cloud.client.ServiceInstance;
import org.springframework.stereotype.Component;

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

@Component
public class MyLoadBalancer implements LoadBalancer {
    //原子整数类,使用CAS机制无锁化
    private AtomicInteger atomicInteger =new AtomicInteger(0);
    public final  int getAndIncrement(){
         int current;
         int next;
         //第一次 current=0 next应该为1 ,next为我没真实需要访问的次数
         do{
             current=this.atomicInteger.get();
             next=current>=214783647?0:current+1;
             //CAS自旋确保线程安全
         }while (!this.atomicInteger.compareAndSet(current,next));
        System.out.println("第几次访问数********* next"+next);
        //返回正确的访问数
        return  next;
    }
    @Override
    public ServiceInstance instances(List<ServiceInstance> serviceInstances) {
        //调用getAndIncrement()获取访问次数
        int index=getAndIncrement()%serviceInstances.size();
        //返回对应访问次数的服务下标索引
        return serviceInstances.get(index);
    }
}

在8001和8002的Controller加上下面的方法

    @GetMapping("/payment/lb")
    public String getPayment(){
        return serverPort;
    }

在消费者服务的Controller里面新建一个方法测试我我们自己的轮询负载均衡算法

package live.yremp.springcloud.controller;

import live.yremp.springcloud.entries.CommonResult;
import live.yremp.springcloud.entries.Payment;
import live.yremp.springcloud.lb.MyLoadBalancer;
import lombok.extern.slf4j.Slf4j;
import org.springframework.cloud.client.ServiceInstance;
import org.springframework.cloud.client.discovery.DiscoveryClient;
import org.springframework.http.ResponseEntity;
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://127.0.0.1:8001";
public static final String PAYMENT_URL="http://CLOUD-PAYMENT-SERVICE";

    @Resource
    private RestTemplate restTemplate;

    @Resource
    private MyLoadBalancer loadBalancer;

    @Resource
    private DiscoveryClient discoveryClient;

    @GetMapping("consumer/payment/create")
    public CommonResult<Payment> create( Payment payment){
        log.debug("consumer/payment/create:请求成功");
        return restTemplate.postForObject(PAYMENT_URL+"payment/create",payment,CommonResult.class);
    }

    @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/getForEntity/{id}")
    public CommonResult<Payment> getPayment2(@PathVariable("id") Long id){
        ResponseEntity<CommonResult> entity=restTemplate.getForEntity(PAYMENT_URL+"/payment/get/"+id,CommonResult.class);
        if (entity.getStatusCode().is2xxSuccessful()){
            return entity.getBody();
        }
        return new CommonResult<>(444,"操作失败");
    }

//下面这个方法是测试我们自己的负载均衡算法,会交替返回服务提供者端口号
    @GetMapping("/consumer/payment/lb")
    public String getPaymentLB(){
        List<ServiceInstance> instances= discoveryClient.getInstances("CLOUD-PAYMENT-SERVICE");
        if(instances==null || instances.size() <=0) {
            return null;
        }
        ServiceInstance serviceInstance =loadBalancer.instances(instances);
        URI uri =serviceInstance.getUri();
        return  restTemplate.getForObject(uri+"/payment/lb",String.class);
    }
}

注意需要去掉我们之前替换负载均衡算法中的@RibbonClient(name = "CLOUD-PAYMENT-SERVICE",configuration = MyselfRule.class)以及配置类中的@LoadBalance注解

5.4.3 测试结果

控制台打印结果

标签云

ajax AOP Bootstrap cdn Chevereto CSS Docker Editormd GC Github Hexo IDEA JavaScript jsDeliver JS樱花特效 JVM Linux Live2D markdown Maven MyBatis MyBatis-plus MySQL Navicat Oracle Pictures QQ Sakura SEO Spring Boot Spring Cloud Spring Cloud Alibaba SpringMVC Thymeleaf Vue Web WebSocket Wechat Social WordPress Yoast SEO 代理 分页 图床 小幸运 通信原理

Spring Cloud Rbibbon负载均衡调用
Spring Cloud Rbibbon负载均衡调用