SpringCloud实战--第十二篇:Ribbon从入门到手写负载均衡器
好烦啊!!!Ribbon这也太难了!
别方,看完本篇,让你感觉Ribbon负载均衡敢敢单单。
前言
说起来容易做起来难,一步一步都干完!!!
学习一定要自己动手搞一搞,不能只眼会。
学习笔记是跟着尚硅谷的视频学的:传送门
关于Ribbon
- Ribbon是提供的客户端软件负载均衡算法和服务调用,简单来说就是在配置文件中列出LoadBalancer(负载平衡器,后简称LB)所有的服务,Ribbon会自动帮你使用某种负载均衡规则去连接这些机器。
- Ribbon是Netfilx公司出的开源负载均衡+服务调用的一个组件。
- Ribbon现在也停止维护了,但是ribbon-eureka组件仍然在被大规模使用,SpringCloud出了一个LoadBalancer组件由于以后替代Ribbon,但短时间内难以完成。
- Nginx负载均衡是集中式的负载均衡(在Nginx服务器上实现负载均衡),Ribbon负载均衡是客户端的负载均衡(服务调用方实现的负载均衡)。
- 结合RestTemplate实现调用。
- Ribbon提供了很多负载均衡策略,如轮询、随机、根据响应时间加权等等。
先构建好基础工程(一篇一篇看过来的不用重新构建)
想偷懒的请下载;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的两个问题
- 如何使用Ribbin?如何更换Ribbon的负载均衡算法?
- 如果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。
完美~~~
小结一下:
- 在RestTemplate上加@LoadBalanced注解即可开启Ribbon负载均衡。
- 创建一个Rule的配置嘞,里面有一个方法用于创建负载均衡算法的实现类(@Bean注解会把方法的返回值加载的SpringBoot),注意这个配置类不能和启动类在同一个包或者在他的子包(不能被@ComponentScan扫描到,否则会全局生效)。
- 创建完成后,我们需要在启动类上添加@RibbonClient注解,来配置上我们自定义的负载均衡算法。
3. Ribbon负载均衡算法原理
轮询算法的原理:
- Ribbon轮询负载均衡的原理是去注册中心获取出所有的相关服务的地址(是一个list形式)。
- 根据过来的请求次数(每次请求次数+1),相关服务集群的数量取余,余数代表下标,通过list.get(下标)来获取到要调用服务的地址。
- 知道地址了,就可以进行调用了,由此实现负载均衡。
源码的解析:
这里建议大家直接看视频上这一课,讲的很不错
传送门
手写负载均衡器(写完之后你就知道均衡器的工作原理了)
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灰常简单(但是!!!骚年,这只是表相,当你知道的越多你就会越明白自己的无知~~)
- 点赞
- 收藏
- 关注作者
评论(0)