欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

SpringCloud - Ribbon客户端负载均衡

程序员文章站 2022-06-22 19:31:18
...

SpringCloud - Ribbon负载均衡

0. Ribbon概述

① LB负载均衡(Load Balance)是什么?

负载均衡的表现就是将用户的请求分摊到多个服务器上,从而达到高可用的目的。常见的负载均衡软件有:Nginx、LVS、硬件F5等。

② 目前主流的负载方案分为以下两种:

  • 集中式负载均衡,在消费者和服务提供方中间使用独立的代理方式进行负载,有硬件的(比如 F5),也有软件的(比如 Nginx)。
  • 客户端自己做负载均衡,根据自己的请求情况做负载,Ribbon 就属于客户端自己做负载。

③ Ribbon负载均衡 VS Nginx负载均衡:

  • Nginx是服务器端负载均衡 ,客户端的请求都发给Nginx,Nginx实现分发,将请求发送到不同的服务器上。

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

④ 集中式LB和进程内LB:

  • 集中式LoadBalancer:在服务的消费方和提供方之间使用的独立LoadBalancer设备(可以是硬件,如F5,也可以是软件,如Nginx),由该设施负责把请求通过某种策略转发至服务的提供方。

  • 进程内LoadBalancer:将LoadBalancer集成到消费方,消费者从服务注册中心获取到可用地址,自己再从这些地址中,选择要访问的服务器(如这个微服务器名称下有多个生产者的微服务,端口8001、8002、8003,进行轮循)。Ribbon就属于进程内LoadBalancer,它只是一个类库,集成消费者进程,消费者通过它获取服务提供方的地址。

Spring Cloud Ribbon 是一个基于 HTTP 和 TCP 的客户端负载均衡工具,它基于 Netflix Ribbon 实现。通过 Spring Cloud 的封装,可以让我们轻松地将面向服务的 REST 模版请求自动转换成客户端负载均衡的服务调用。

Spring Cloud Ribbon 虽然只是一个工具类框架,它不像服务注册中心、配置中心、API 网关那样需要独立部署,但是它几乎存在于每一个 Spring Cloud 构建的微服务和基础设施中。因为微服务间的调用,API 网关的请求转发等内容,实际上都是通过 Ribbon 来实现的。

⑤ ribbon其实就是一个负载均衡的客户端组件,他可以和其他所需请求的客户端组件结合使用,和Eureka结合只是其中的一个实例。ribbon其实就是负载均衡加RestTemplate

ribbon模块:

名 称 说 明
ribbon-loadbalancer 负载均衡模块,可独立使用,也可以和别的模块一起使用。
Ribbon 内置的负载均衡算法都实现在其中。
ribbon-eureka 基于 Eureka 封装的模块,能够快速、方便地集成 Eureka。
ribbon-transport 基于 Netty 实现多协议的支持,比如 HTTP、Tcp、Udp 等。
ribbon-httpclient 基于 Apache HttpClient 封装的 REST 客户端,集成了负载均衡模块,可以直接在项目中使用来调用接口。
ribbon-example Ribbon 使用代码示例,通过这些示例能够让你的学习事半功倍。
ribbon-core 一些比较核心且具有通用性的代码,客户端 API 的一些配置和其他 API 的定义。

⑥ Ribbon在工作时分为两步:

第一步先选择EurekaServer,它优先选择在同一个区域内负载较少的Server

第二步再根据用户指定的策略,再从server取到的服务注册列表中选择一个地址

其中Ribbon提供了多中策略:比如轮询,随机和根据时间响应加权。

SpringCloud - Ribbon客户端负载均衡

1. Ribbon的使用

① Euraka的最新版已经集成了ribbon:

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

② 也可以手动的导入ribbon:放到order消费者模块中,因为只有order访问pay生产者时需要负载均衡

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

③ RestTemplate类:

spring-cloud-starter-netflix-eureka-client坐标下,是引入了spring-cloud-starter-netflix-ribbon的,所以,我们仅仅只需要添加一个@LoadBalanced就可以实现负载均衡。

RestTemplate常见的方法有getForObject()、getForEntity()、postForObject()、postForEntity()方法。其中

  • ForObject()方法返回对象为响应体中数据转换成的对象,基本理解为JSON。

  • ForEntity()方法返回对象是ResponseEntity对象,包含了响应中的信息,比如响应头,响应状态码,响应体等。

④ 运行环境和测试:

将cloud-eureka-server7001、cloud-eureka-server7002的配置文件改成集群模式,让7001和7002互相注册,将cloud-provider-payment8001、cloud-provider-payment8002的配置文件改成集群模式,将cloud-consumer-order80的配置文件改成集群模式。

在cloud-consumer-order80模块中的OrderController里,添加两个方法,分别调用getForEntity()和postForEntity()方法。

@GetMapping("/consumer/payment/getForEntity/{id}")
public CommonResult getPaymentById2(@PathVariable("id") Long id) {
    ResponseEntity<CommonResult> entity = restTemplate.getForEntity(PAYMENT_URL + "/payment/get/" + id, CommonResult.class);
    //打印响应状态码
    System.out.println("status code=" + entity.getStatusCode());
    //打印响应头
    System.out.println("headers=" + entity.getHeaders());
    //打印响应体
    if (entity.getStatusCode().is2xxSuccessful()) {
        return entity.getBody();
    } else {
        return new CommonResult(404, "查找失败");
    }
}
@GetMapping("/consumer/payment/create2")
public CommonResult create2(Payment payment) {
    return restTemplate.postForEntity(PAYMENT_URL + "/payment/create", payment, CommonResult.class).getBody();
}

先启动Eureka注册中心,再启动两个生产者,最后启动消费者,浏览器发送请求http://localhost//consumer/payment/getForEntity/1来调用ForEntity()方法进行测试。在浏览器端,可以看到port的值,不断在8001和8002之前进行切换。

2. 替换Ribbon的负载均衡算法

IRule接口,Riboon使用该接口,根据特定算法从所有服务中,选择一个服务,Rule接口有7个实现类,每个实现类代表一个负载均衡算法。

SpringCloud - Ribbon客户端负载均衡

① 官方文档说明:如果想要替换Ribbon的轮询算法,这个自定义配置类不能放在@ComponentScan所扫描的当前包下以及子包下,否则我们自定义的这个配置类就会被所有的Ribbon客户端所共享,达不到特殊化定制的目的了,也就是不能放在主启动类所在的包及子包下,因此新建一个包并定义一个配置类将轮询算法换为随机算法:

package com.atguigu.myrule;

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

② 在80主启动类上加一个注解(使用指定的算法)

@SpringBootApplication
@EnableEurekaClient
@RibbonClient(name = "CLOUD-PAYMENT-SERVICE",configuration = MySelfRule.class)
//name是服务提供者名称(地址),表明负载均衡用在服务提供者上
//configuration = MySelfRule.class是调用自定义的负载均衡配置类(使用自定义的随机算法)
public class OrderMain80 {
    public static void main(String[] args) {
       SpringApplication.run(OrderMain80.class,args);
    }
}

表示访问CLOUD_PAYMENT_SERVICE的服务时,使用我们自定义的负载均衡算法

3. 手写轮询算法

轮询算法:rest接口第几次请求数 % 服务器集群总数量 = 实际调用服务器位置下标,每次重启后rest接口从1计数。

① payment服务提供者模块(8001,8002)的Controller层加入一个方法返回服务的端口号:

@Value("${server.port}")
private String serverPort;

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

② 修改order 80消费者模块:

在配置类config中去掉@LoadBalanced注解(自带的负载均衡机制)

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

在80模块中自定义接口并编写实现类:

package com.atguigu.springcloud.lb;

public interface LoadBalance {
    ServiceInstance instances(List<ServiceInstance> serviceInstances);
}
public class MyLB implements LoadBalance{

    private AtomicInteger atomicInteger = new AtomicInteger(0);

    //获取下一个要调用的服务的id
    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 : "+next);
        return next;
    }

    //根据所有的服务,选择一个本次调用的服务
    @Override
    public ServiceInstance instances(List<ServiceInstance> serviceInstances) {
        //拿到id后,进行取余运算,得到真正要调用的服务
        int index = getAndIncrement() % serviceInstances.size();
        return serviceInstances.get(index);
    }
}

在Controller层方法添加自定义的负载均衡算法:

@RestController
@Slf4j
public class OrderController {
    
    //将访问地址改为服务名称
    private static final String PAYMENT_URL = "http://CLOUD-PAYMENT-SERVICE";

    @Resource
    private RestTemplate restTemplate;

    @Resource
    private DiscoveryClient discoveryClient;

    @Resource
    private LoadBalance loadBalance;

       @GetMapping(value = "/consumer/payment/lb")
    public String getPaymentLB(){
        //拿到 CLOUD-PAYMENT-SERVICE下的所有服务
        List<ServiceInstance> instances = discoveryClient.getInstances("CLOUD-PAYMENT-SERVICE");
        if(instances==null || instances.size()<=0){
           return null;
        }
        ServiceInstance serviceInstance = loadBalance.instances(instances);
        return restTemplate.getForObject(PAYMENT_URL+"/payment/lb",String.class);
    }
}

浏览器访问http://localhost/consumer/payment/lb,发现8001和8002轮询访问。