SpringCloud实战--第十二篇:Ribbon从入门到手写负载均衡器

举报
老司机张师傅 发表于 2022/07/26 23:23:23 2022/07/26
【摘要】 好烦啊!!!Ribbon这也太难了! 别方,看完本篇,让你感觉Ribbon负载均衡敢敢单单。 前言说起来容易做起来难,一步一步都干完!!!学习一定要自己动手搞一搞,不能只眼会。 学习笔记是跟着尚硅谷的视频学的:传送门 关于RibbonRibbon是提供的客户端软件负载均衡算法和服务调用,简单来说就是在配置文件中列出LoadBalancer(负载平衡器,后简称LB)所有的服务,Ribbon会...

好烦啊!!!Ribbon这也太难了!

别方,看完本篇,让你感觉Ribbon负载均衡敢敢单单。


前言

在这里插入图片描述

说起来容易做起来难,一步一步都干完!!!
学习一定要自己动手搞一搞,不能只眼会。

学习笔记是跟着尚硅谷的视频学的:传送门

关于Ribbon

在这里插入图片描述

  • Ribbon是提供的客户端软件负载均衡算法和服务调用,简单来说就是在配置文件中列出LoadBalancer(负载平衡器,后简称LB)所有的服务,Ribbon会自动帮你使用某种负载均衡规则去连接这些机器。
  • Ribbon是Netfilx公司出的开源负载均衡+服务调用的一个组件。
  • Ribbon现在也停止维护了,但是ribbon-eureka组件仍然在被大规模使用,SpringCloud出了一个LoadBalancer组件由于以后替代Ribbon,但短时间内难以完成。
  • Nginx负载均衡是集中式的负载均衡(在Nginx服务器上实现负载均衡),Ribbon负载均衡是客户端的负载均衡(服务调用方实现的负载均衡)。
  • 结合RestTemplate实现调用。
  • Ribbon提供了很多负载均衡策略,如轮询、随机、根据响应时间加权等等。

先构建好基础工程(一篇一篇看过来的不用重新构建)

  1. 构建基础父工程
  2. Rest风格微服务
  3. 传统分布式方法
  4. 改造工程,抽取公共模块:
  5. 使用Eureka
  6. Eureka集群

想偷懒的请下载;gitee上我上传的基础工程代码:

 https://gitee.com/xiaoZ1712/cloud2021

基础工程构建完成的目录结构:
在这里插入图片描述
启动所有模块,访问

localhost:7001

显示如下,代表基础工程没问题
在这里插入图片描述


敌军还有30秒钟到达战场

我们先聊一些Ribbon相关的一些组件,别嫌我话多,这些都是有用的。

1. 引入Ribbon依赖

我们搭建的框架中,引入了spring-cloud-starter-netflix-eureka-client,新版的SpringCloud已经将该组件和Ribbon组件整合(传递依赖),所以引入该组件后我们已经默认引入了Ribbon
传递依赖是啥??? 请自行百度,学习下你的SpringBoot基础。
在这里插入图片描述
netflix-eureka-client依赖(添加过该依赖后会自动引入Ribbon)

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

当然,如果你想单独引入,也没问题,也不会和netflix-eureka-client冲突

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

2. RestTemplate的getForEntity方法

给我们的80工程Controller添加一个getForEntity的方法,使用RestTemplate的getForEntity方式调用

@GetMapping("/customer/payment/getForEntity/{id}")
public CommonResult getForEntity(@PathVariable("id") Long id){
    // 使用getForEntity或postForEntity获取的返回结果会是封装好的ResponseEntity,获取的是请求返回的完整信息(包括状态码等)
    // 这点和getForObject不同,getForObject是将返回的接口内容封装到我们的class参数类
    ResponseEntity<CommonResult> entity = restTemplate.getForEntity(PAYMENT_URL + "/payment/get/" + id, CommonResult.class);
    if (entity.getStatusCode().is2xxSuccessful()){ // 2xx代表成功了
        return entity.getBody(); // body是restTemplate.getForObject调用直接获取到的结果
    }else{
        return new CommonResult(444,"访问失败");
    }
}

在这里插入图片描述

3. 启动并测试

启动所有工程,访问

http://localhost/customer/payment/getForEntity/1

也是成功的,getForEntity方法让我们做一些网络异常、服务器错误等状态的手动处理。(状态码不是2xx)
在这里插入图片描述

4. Ribbon的核心组件IRule

IRule是Ribbon定义负载均衡算法接口(instance),同时Ribbon内置了一些IRule实现类来提供常用的负载算法,这种设计模式是典型的策略模式。

Ribbon自己的IRule实现类

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

全军出击—实战Ribbon

在工作或面试中,我们通常需要关注关于Ribbon的两个问题

  1. 如何使用Ribbin?如何更换Ribbon的负载均衡算法?
  2. 如果Ribbon提供的负载均衡算法不满足我们的需求,如何进行扩展?
    接下来,我们就这三个问题开启实战!!!

1. Ribbon的使用

之前其实我们已经讲到过,只是使用的话非常简单,只需要在RestTemplate上加一个注解即可,加上注解即代表开启了负载(注解不会写?建议您赶紧的\r\n——换一行)。

@LoadBalanced

添加完自己可以测试一下,启动所有服务,调用接口

http://localhost/customer/payment/getForEntity/1

你会发现8001和8002会被轮询调用
在这里插入图片描述

2. 更换Ribbon的算法

!!!! !!!特别注意!!!!!!!!!
官方文档明确给出了警告:这个自定义配置类不能放在@ComponentScan所扫描的当前包下以及子包下,否则我们自定义的这个配置类就会被所有的Ribbon客户端所共享,达不到特殊化定制的目的了。我们的主启动类加默认会ComponentScan自己所在的当前包和所有子包,所以我们自定义的Ribbon配置类不能和启动类在同一个包及子包下。
在这里插入图片描述
好了,此处不留爷,自有留爷处,我们再创建一个包。

myrule

在这里插入图片描述
然后创建一个自定义的配置,返回随机IRule的实现。

package com.atguigu.myrule;

import com.netflix.loadbalancer.IRule;
import com.netflix.loadbalancer.RandomRule;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
/**
 * @Author: Daisen.Z
 * @Date: 2021/12/23 14:54
 * @Version: 1.0
 * @Description:
 */
@Configuration
public class MyRule {
    @Bean
    public IRule getMyRule(){
        // 改编成随机算法
        return new RandomRule();
    }
}

在这里插入图片描述
接下来,我们需要在主启动类上声明一下,调用服务时,使用我们自己设置的这个负载均衡算法。

@RibbonClient(name = "CLOUD-PAYMENT-SERVICE",configuration = MyRule.class)

其中,name跟的是服务名,configuration 跟的是我们自己设置的配置类。

在这里插入图片描述

好了,重新启动一下,访问

http://localhost/customer/payment/getForEntity/1

会发现这时已经不再是一替一次的轮询算法了,而是随机的,可能两次8001,一次8002。
完美~~~

小结一下:

  1. 在RestTemplate上加@LoadBalanced注解即可开启Ribbon负载均衡。
  2. 创建一个Rule的配置嘞,里面有一个方法用于创建负载均衡算法的实现类(@Bean注解会把方法的返回值加载的SpringBoot),注意这个配置类不能和启动类在同一个包或者在他的子包(不能被@ComponentScan扫描到,否则会全局生效)。
  3. 创建完成后,我们需要在启动类上添加@RibbonClient注解,来配置上我们自定义的负载均衡算法。

3. Ribbon负载均衡算法原理

轮询算法的原理:

  1. Ribbon轮询负载均衡的原理是去注册中心获取出所有的相关服务的地址(是一个list形式)。
  2. 根据过来的请求次数(每次请求次数+1),相关服务集群的数量取余,余数代表下标,通过list.get(下标)来获取到要调用服务的地址。
  3. 知道地址了,就可以进行调用了,由此实现负载均衡。
    源码的解析:
    这里建议大家直接看视频上这一课,讲的很不错
    传送门

手写负载均衡器(写完之后你就知道均衡器的工作原理了)

1. 手写负载均衡器准备工作

这一块视频也讲的很好,条件允许也建议看视频;传送门

首先,给8001和8002添加一个用于测试的接口(直接返回端口,就可以看出负载均衡算法的效果)

// 负载均衡测试
@GetMapping("/payment/lb")
public String getLb(){
    return serverPort;
}

在这里插入图片描述
然后,去掉80工程的@LoadBalanced注解,不使用Ribbon自带的负载均衡。
在这里插入图片描述

2. 开始完整的手写一个本地的负载均衡器

创建lb包
在这里插入图片描述
创建LoadBalancer接口,内含一个负载均衡算法获取服务实例的方法接口。

package com.atguigu.springcloud.lb;

import org.springframework.cloud.client.ServiceInstance;

import java.util.List;

/**
 * @Author: Daisen.Z
 * @Date: 2021/12/23 15:50
 * @Version: 1.0
 * @Description:
 */
public interface LoadBalancer {

    ServiceInstance instances(List<ServiceInstance> serviceInstances);

}

在这里插入图片描述
创建一个LoadBalancer的实现类MyLB

package com.atguigu.springcloud.lb;

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

import java.util.List;

/**
 * @Author: Daisen.Z
 * @Date: 2021/12/23 15:51
 * @Version: 1.0
 * @Description:
 */
@Component
public class MyLB implements LoadBalancer {
    @Override
    public ServiceInstance instances(List<ServiceInstance> serviceInstances) {
        return null;
    }
}

在这里插入图片描述
编写MyLB,其中的getAndIncrement()方法代表请求累加,给该方法加final是防止篡改

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;

/**
 * @Author: Daisen.Z
 * @Date: 2021/12/23 15:51
 * @Version: 1.0
 * @Description:
 */
@Component
public class MyLB implements LoadBalancer {

    private  AtomicInteger atomicInteger = new AtomicInteger(0);

    // 访问次数自增累加(多线程异步环境,使用以下方式自旋)
    // 返回值代表是第几次访问
    public final int getAndIncrement(){
        int current;
        int next;
        do {
            current = this.atomicInteger.get();
            // 不能超过Integer的最大值
            next = current >= Integer.MAX_VALUE ? 0: current +1;
            // 设置值,高并发情况下可能会数字重复,设置成功就会返回true,取反条件不成立叫跳出啦,设置失败会返回false,需要再重新设置一次
        }while (! this.atomicInteger.compareAndSet(current,next));
        System.out.println("*********第几次访问next:"+next);
        return next;
    }

    @Override
    public ServiceInstance instances(List<ServiceInstance> serviceInstances) {

        // 请求次数模服务集群的数量,取余作为获取的服务下标
       int index =  getAndIncrement() % serviceInstances.size();
        return serviceInstances.get(index);
    }
}

3. 修改80的Controller

在OrderController注入我们手写的MyLB负载均衡器

@Resource
private LoadBalancer loadBalancer;

在OrderController引入DiscoveryClient(用于发现注册中心上服务的客户端)

@Resource
private DiscoveryClient discoveryClient;

在这里插入图片描述
添加测试方法

// 负载均衡测试
@GetMapping("/customer/payment/lb")
public String getLb(){
    // 1. 获取服务实例
    List<ServiceInstance> instances = discoveryClient.getInstances("CLOUD-PAYMENT-SERVICE");

    // 2. 判断有无服务存在
    if (CollectionUtils.isEmpty(instances)){
        // 没有有效的服务,直接返回null
        return null;
    }

    // 3. 调用自定义的均衡器获取要调用的服务
    ServiceInstance instance = loadBalancer.instances(instances);

    // 4. 获取instance服务的地址
    URI uri = instance.getUri();

    // 5. 调用接口,返回信息
    return restTemplate.getForObject(uri+"/payment/lb",String.class);
}

在这里插入图片描述

4. 开始测试!!!

直接启动所有服务
访问我们刚才添加的接口

localhost/customer/payment/lb

在这里插入图片描述
多刷新几次,发现我们自己写的轮询负载均衡器已经生效啦!!!
在这里插入图片描述

看看,是不是敢敢单单~


总结

  • Nginx负载均衡是集中式的负载均衡(在Nginx服务器上实现负载均衡),Ribbon负载均衡是客户端的负载均衡(服务调用方实现的负载均衡)。
  • Ribbon提供了很多负载均衡策略,如轮询、随机、根据响应时间加权等等。
  • 给RestTemplate加上@LoadBalanced注解表示开启Ribbon。
  • 负载均衡的本质是Ribbon从注册中心拿到所有的服务实例,通过对请求进行计算得出要调用哪个实例,然后再进行调用。
  • 手写一个负载均衡器可以让你更好的了解Ribbon的原理。
  • 学完本篇,你会觉得Ribbon灰常简单(但是!!!骚年,这只是表相,当你知道的越多你就会越明白自己的无知~~)

在这里插入图片描述

【版权声明】本文为华为云社区用户原创内容,转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息, 否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。