springcloud(3)—— 负载均衡Ribbon

一、什么是ribbon

Spring Cloud Ribbon是基于Netflix Ribbon实现的一套客户端负载均衡的工具。

简单的说,Ribbon是Netflix发布的开源项目,主要功能是提供客户端的软件负载均衡算法和服务调用。Ribbon客户端组件提供一系列完善的配置项如连接超时,重试等。

简单的说,就是在配置文件中列出Load Balancer(简称LB)后面所有的机器,Ribbon会自动的帮助你基于某种规则(如简单轮询,随机连接等)去连接这些机器。我们很容易使用Ribbon实现自定义的负载均衡算法。

Ribbon在工作时分成两步:

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

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

二、demo

前面在第一节里面实践eureka集群的时候我们就已经使用到了ribbon的负载均衡,可我们并没有导入相关jar包

<dependency>
    <groupld>org.springframework.cloud</groupld>
    <artifactld>spring-cloud-starter-netflix-ribbon</artifactid>
</dependency>

这是因为spring-cloud-starter-netflix-eureka-client自带了spring-cloud-starter-ribbon引用。

 2.1 构建消费者

注册中心继续使用之前的eureka(单机),服务提供者也使用之前的8001 和8002

创建

cloud-consumer-ribbon-order81
<dependencies>
        <!-- eureka-client -->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-actuator</artifactId>
        </dependency>
        <!--热部署-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-devtools</artifactId>
            <scope>runtime</scope>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
    </dependencies>
server:
  port: 81

spring:
  application:
    name: cloud-order-service

eureka:
  client:
    register-with-eureka: true
    etch-registry: true
    service-url:
      defaultZone: http://localhost:7001/eureka
      #defaultZone: http://eureka7001.com:7001/eureka,http://eureka7001.com:7002/eureka,http://eureka7001.com:7003/eureka

  instance:
    instance-id: ribbonconsumer81
    prefer-ip-address: true   #访问路径可以显示ip地址

  #Eureka客户端向服务端发送心跳的时间间隔,单位秒(默认30秒)
  lease-renewal-interval-in-seconds: 1
  #Eureka服务端在收到最后一次心跳后等待的时间上限,单位秒(默认90秒),超时剔除服务
  lease-expiration-duration-in-seconds: 2
package com.cjian.springcloud;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.ribbon.RibbonClient;

/**
 * @description:
 * @author: CJ
 * @time: 2021/9/6 19:51
 */
@SpringBootApplication
@RibbonClient(name = "CLOUD-PAYMENT-SERVICE")
public class OrderMain81 {
    public static void main(String[] args) {
        SpringApplication.run(OrderMain81.class, args);
    }
}
package com.cjian.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;

/**
 * @description:
 * @author: CJ
 * @time: 2021/9/6 19:53
 */
@Configuration
public class ApplicationContextConfig {
    //往容器中添加一个RestTemplate
    //RestTemplate提供了多种便捷访问远程http访问的方法
    @Bean
    @LoadBalanced //开启ribbon的负载均衡
    public RestTemplate restTemplate(){
        return new RestTemplate();
    }
}
package com.cjian.springcloud.controller;

import com.cjian.springcloud.bean.CommonResult;
import com.cjian.springcloud.bean.Payment;

import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.client.RestTemplate;

import javax.annotation.Resource;

/**
 * @description:
 * @author: CJ
 * @time: 2021/9/6 19:53
 */
@RestController
public class OrderController {

     public static final String PAYMENT_URL = "http://CLOUD-PAYMENT-SERVICE";

    @Resource
    private RestTemplate restTemplate;


    @GetMapping("/consumer/payment/get/{id}")
    public CommonResult<Payment> getPayment(@PathVariable("id") Long id){
        System.out.println("********查询的id:" + id);
        //getForObject两个参数:请求地址,返回的对象类型
        return restTemplate.getForObject(PAYMENT_URL + "/payment/get/" + id, CommonResult.class);
    }

}

2.2 测试

分别启动eureka,8001 和8002 以及消费者81

访问 http://localhost:81/consumer/payment/get/3

再次访问:

 多试几次,发现是轮询的模式,那ribbon一共提供了几种模式呢 :

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

 2.3 修改负载均衡规则

2.3.1 通过配置类

官方文档明确给出了警告:

这个自定义配置类不能放在@ComponentScan所扫描的当前包下以及子包下,

否则我们自定义的这个配置类就会被所有的Ribbon客户端所共享,达不到特殊化定制的目的了。

也就是说不要将Ribbon配置类与主启动类同包

package com.cjian.lb;

import com.netflix.loadbalancer.IRule;
import com.netflix.loadbalancer.RandomRule;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

/**
 * @description:
 * @author: CJ
 * @time: 2021/9/8 15:15
 */
@Configuration
public class MySelfRule {

    @Bean//替换负载均衡方式--随机
    public IRule myRule(){
        return new RandomRule();
    }
}

修改启动类:

2.3.2  自己实现一个轮询的负载均衡算法

8001/8002微服务改造- controller

只是为了验证功能,返回端口号而已

@RestController
public class PaymentController{

    ...
    
	@GetMapping(value = "/payment/lb")
    public String getPaymentLB() {
        return serverPort;//返回服务接口
    }
    
    ...
}

80订单微服务改造

①.ApplicationContextConfig去掉注解@LoadBalanced,OrderMain80去掉注解@RibbonClient

②.创建MyLoadBalancer接口

package com.cjian.lb;

import org.springframework.cloud.client.ServiceInstance;

import java.util.List;

/**
 * @description:
 * @author: CJ
 * @time: 2021/9/8 15:30
 */
public interface MyLoadBalancer {
    ServiceInstance instances(List<ServiceInstance> serviceInstances);
}

③.实现上面的接口

package com.cjian.lb;

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

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

/**
 * @description:
 * @author: CJ
 * @time: 2021/9/8 15:29
 */
@Component//需要跟主启动类同包,或者在其子孙包下。
public class MyLB implements MyLoadBalancer {
    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: "+next);
        return next;
    }

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

        return serviceInstances.get(index);
    }
}

④.修改OrderController

import org.springframework.cloud.client.ServiceInstance;
import org.springframework.cloud.client.discovery.DiscoveryClient;
import com.lun.springcloud.lb.LoadBalancer;

@RestController
public class OrderController {

    public static final String PAYMENT_URL = "http://CLOUD-PAYMENT-SERVICE";

	...

    @Resource
    private MyLoadBalancer myLoadBalancer;

    @Resource
    private DiscoveryClient discoveryClient;

	...

    @GetMapping(value = "/consumer/payment/lb")
    public String getPaymentLB()
    {
        List<ServiceInstance> instances = discoveryClient.getInstances("CLOUD-PAYMENT-SERVICE");

        if(instances == null || instances.size() <= 0){
            return null;
        }

        ServiceInstance serviceInstance = myLoadBalancer.instances(instances);
        URI uri = serviceInstance.getUri();

        return restTemplate.getForObject(uri+"/payment/lb",String.class);

    }
}

重启相关服务后,再次验证......

2.3.2 修改yml

ribbon:
  # 值的是建立连接所用的时间,使用与网络状态正常的情况,两端连接所用的时间
  ReadTimeout: 5000
  # 指的是建立连接后从服务器读取到可用资源所用的时间
  ConnectionTimeout: 5000
  ##最大自动重试次数
  maxAutoRetries: 3
  ## 换实例重试次数
  MaxAutoRetriesNextServer: 2
  #修改负载均衡方式
  NFLoadBalancerRuleClassName: com.netflix.loadbalancer.RandomRule

猜你喜欢

转载自blog.csdn.net/cj_eryue/article/details/120214304