1.简介

Ribbon是Netflix公司开源的一个 客户端 负载均衡的项目(https://github.com/Netflix/ribbon),它是一个基于HTTP、TCP的客户端负载均衡器。

2. 什么是负载均衡?

负载均衡是微服务架构中必须使用的技术,通过负载均衡来实现系统的高可用、集群扩容等功能。负载均衡可通过硬件设备及软件来实现,硬件比如:F5、Array等,软件比如:LVS、Nginx等。

2.1 服务端负载均衡

服务端负载均衡.png
用户请求先到达负载均衡器(也相当于一个服务),负载均衡器根据负载均衡算法将请求转发到微服务。负载均衡算法有:轮训、随机、加权轮训、加权随机、地址哈希等方法,负载均衡器维护一份服务列表,根据负载均衡算法将请求转发到相应的微服务上,所以负载均衡可以为微服务集群分担请求,降低系统的压力。

2.2 客户端负载均衡

客户端负载均衡.png
1、在消费微服务中使用Ribbon实现负载均衡,Ribbon先从EurekaServer中获取服务列表。
2、Ribbon根据负载均衡的算法在本地去调用微服务。

3. Ribbon原理/架构

image.pngimage.png

总结: Ribbon其实就是一个软负载均衡的客户端组件, 他可以和其他所需请求的客户端结合使用,和eureka结合只是其中一个实例。

4. LB(Load Balance)

image.png

4.1 集中式LB

image.png

4.2 进程内LB

image.png

5. Ribbon核心组件IRule

5.1 IRule

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

image.png

5.2 如何修改算法

5.2.1 配置细节

意思就是不要放在启动类下的包下

image.png

5.2.2 新建package

image.png

5.2.3 配置类

  1. package com.atguigu.myrule;
  2. import com.netflix.loadbalancer.IRule;
  3. import com.netflix.loadbalancer.RoundRobinRule;
  4. import org.springframework.context.annotation.Bean;
  5. import org.springframework.context.annotation.Configuration;
  6. /**
  7. * @author JShawn 2021/3/23 11:26
  8. */
  9. @Configuration
  10. public class MySelfRule {
  11. @Bean
  12. public IRule myRule() {
  13. // 定义为随机
  14. return new RoundRobinRule();
  15. }
  16. }

5.2.4 主启动类添加@RibbonClient

指定调用的服务名+负载均衡算法配置类

image.png

5.3 Ribbon负载均衡算法

5.3.1 原理

image.png

5.3.2 源码(略)

5.3.3 自己实现

5.3.3.1 服务消费方

5.3.3.1 RestTemplate去掉注解@LoadBalanced
/**
 * 充当applicationContext.xml <bean id="" class="">
 * @author JShawn 2021/3/14 23:46
 */
@Configuration
public class ApplicationContextConfig {

    @Bean
    // 轮循多节点实例
    // @LoadBalanced
    public RestTemplate restTemplate() {
        return new RestTemplate();
    }
}

5.3.3.2 LoadBalancer接口
public interface LoadBalaner {
    ServiceInstance instance(List<ServiceInstance> instances);
}

5.3.3.3 MyLB
/**
 * @author JShawn 2021/3/23 12:42
 * 自己实现LoadBalance负载均衡 轮询 算法
 */
@Component
public class MyLB implements LoadBalaner {

    private AtomicInteger atomicInteger = new AtomicInteger(0);

    public final int getIncrement() {
        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"+next);
        return next;
    }

    @Override
    public ServiceInstance instance(List<ServiceInstance> instances) {
        // 总请求数 % 实例数
        int index = getIncrement() % instances.size();
        return instances.get(index);
    }
}

5.3.3.4 Controller
@GetMapping("payment/lb")
public String getPaymentLB() {
    List<ServiceInstance> instances = discoveryClient.getInstances("CLOUD-PAYMENT-SERVICE");
    if (instances == null || instances.size() == 0) {
        return null;
    }
    ServiceInstance serviceInstance = loadBalaner.instance(instances);
    URI uri = serviceInstance.getUri();
    System.out.println(uri+"/payment/lb");
    return restTemplate.getForObject(uri+"/payment/lb",String.class);
    // return uri+"/payment/lb";
}

5.3.3.2 服务提供方

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

6. RestTemplate

6.1 配置

package com.atguigu.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;

/**
 * @author JShawn 2021/3/14 23:46
 */
@Configuration
public class ApplicationContextConfig {

    //相当于applicationContext.xml <bean id="" class="">,加入IOC容器
    @Bean
    // 使用Ribbon提供的负载均衡算法,轮训多节点实例
    @LoadBalanced
    public RestTemplate restTemplate() {
        return new RestTemplate();
    }
}

6.2 API

image.png

6.3 getForObject方法/getForEntity方法

6.3.1 getForObject()

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

image.png

6.3.2 getForEntity()

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

image.png

7. 在客户端实现负载均衡

服务消费方 可以直接调用 服务提供方的多实例 而不用再关心地址和端口号,且该服务还有负载功能了,一句话 Ribbon+RestTemplate 远程调用

Spring Cloud引入Ribbon配合 restTemplate 实现客户端负载均衡与远程调用,实际就是轮询服务端多节点实例(默认采用的是轮询机制)。Java中远程调用的技术有很多,如:webservice、socket、rmi、Apache HttpClient、OkHttp等,互联网项目使用基于http的客户端较多,本项目使用OkHttp。

原理:由于Ribbon实现客户端负载均衡是基于RestTemplate,所以在RestTemplate的@Bean上加@LoadBalanced注解,添加@LoadBalanced注解后,restTemplate会走LoadBalancerInterceptor拦截器,此拦截器中会通过RibbonLoadBalancerClient查询服务地址,可以在此类打断点观察每次调用的服务地址和端口,两个cms服务(服务端)会轮流被调用。
@Bean
@LoadBalanced
public RestTemplate restTemplate() {
    return new RestTemplate(new OkHttp3ClientHttpRequestFactory());
}

8. 客户端配置

ribbon:
  MaxAutoRetries: 2 #最大重试次数,当Eureka中可以找到服务,但是服务连不上时将会重试
  MaxAutoRetriesNextServer: 3 #切换实例的重试次数
  OkToRetryOnAllOperations: false #对所有操作请求都进行重试,如果是get则可以,如果是post,put等操作没有实现幂等的情况下是很危险的,所以设置为false
  ConnectTimeout: 5000 #请求连接的超时时间
  ReadTimeout: 6000 #请求处理的超时时间

9. 依赖

<dependency>
  <groupId>org.springframework.cloud</groupId>
  <artifactId>spring‐cloud‐starter‐ribbon</artifactId>
</dependency>
<dependency>
  <groupId>com.squareup.okhttp3</groupId>
  <artifactId>okhttp</artifactId>
</dependency>

10. 使用

/**
 * 测试负载均衡调用
 */
@Test
public void testRibbon() {
    //服务id
    String serviceId = "XC‐SERVICE‐MANAGE‐CMS";
    for(int i=0;i<10;i++){
        //通过服务id调用
        ResponseEntity<CmsPage> forEntity = restTemplate.getForEntity("http://" + serviceId
        + "/cms/page/get/5a754adf6abb500ad05688d9", CmsPage.class);
        CmsPage cmsPage = forEntity.getBody();
        System.out.println(cmsPage);
    }
}