负载均衡的实现方式
- 客户端的负载均衡
- 服务端的负载均衡(经常使用nginx来实现)
服务端负载均衡
- 接收请求
- 选择服务器地址
- 转发请求
客户端负载均衡
- 选择服务器地址
- 发请求
也可以分为集中式负载均衡和进程内负载均衡
集中式负载均衡
即在服务的消费方和提供方之间使用独立的负载均衡设施(可以是硬件,如F5,可以是软件,如nginx),由该实施负责把访问请求通过某种策略转发至服务的提供方
进程内负载均衡
将负载均衡逻辑集成到消费方,消费方从服务注册中心获知有哪些地址可用,然后自己再从这些地址中选择出一个合适的服务器。
Ribbon就属于进程内负载均衡,它只是一个类库,集成于消费方进程,消费方通过它来获取服务提供方的地址
Ribbon的简单介绍
Spring Cloud Ribbon是基于Netfilx Ribbon实现一套客户端负载均衡的工具。简单的说,Ribbon是Netfilx 发布的开源项目,主要功能是提供客户端的软件负载均衡算法和服务调用。Ribbon客户端组件提供一系列完善的配置项,如:连接超时、重试等。
简单的说,就是在配置文件列出Load Balancer(简称LB)后面所有的机器,Ribbon会自动的帮助你基于某种规则(如:简单轮询、随机连接等)去连接这些机器。
我们很容易使用Ribbon实现自定义负载均衡算法。
Ribbon 注册到nacos server之后,Ribbon会从nacos server获取服务列表
Ribbon 负载均衡策略的简单介绍
IRule 接口的实现类定义了一系列负载规则
IRule的类图
负载策略的大致功能实现
策略名 | 策略声明 | 策略描述 | 实现说明 |
---|---|---|---|
BestAvailableRule | public class BestAvailableRule extends ClientConfigEnabledRoundRobinRule | 选择一个最小的并发请求的server | 逐个考察Server,如果Server被tripped了,则忽略,在选择其中ActiveRequestsCount最小的server |
AvailabilityFilteringRule | public class AvailabilityFilteringRule extends PredicateBasedRule | 过滤掉那些因为一直连接失败的被标记为circuit | tripped的后端server,并过滤掉那些高并发的的后端server(active connections 超过配置的阈值) 使用一个AvailabilityPredicate来包含过滤server的逻辑,其实就就是检查status里记录的各个server的运行状态 |
WeightedResponseTimeRule | public class WeightedResponseTimeRule extends RoundRobinRule | 根据响应时间分配一个weight,响应时间越长,weight越小,被选中的可能性越低。 | 一个后台线程定期的从status里面读取评价响应时间,为每个server计算一个weight。Weight的计算也比较简单responsetime 减去每个server自己平均的responsetime是server的权重。当刚开始运行,没有形成status时,使用roubine策略选择server。 |
RetryRule | public class RetryRule extends AbstractLoadBalancerRule | 对选定的负载均衡策略机上重试机制。 | 在一个配置时间段内当选择server不成功,则一直尝试使用subRule的方式选择一个可用的server |
RoundRobinRule | public class RoundRobinRule extends AbstractLoadBalancerRule | roundRobin方式轮询选择server | 轮询index,选择index对应位置的server |
RandomRule | public class RandomRule extends AbstractLoadBalancerRule | 随机选择一个server | 在index上随机,选择index对应位置的server |
ZoneAvoidanceRule | public class ZoneAvoidanceRule extends PredicateBasedRule | 复合判断server所在区域的性能和server的可用性选择server | 使用ZoneAvoidancePredicate和AvailabilityPredicate来判断是否选择某个server,前一个判断判定一个zone的运行性能是否可用,剔除不可用的zone(的所有server),AvailabilityPredicate用于过滤掉连接数过多的Server。 |
ResponseTimeWeightedRule已经被弃用,作用和WeightedResponseTimeRule一样。
还是以Nacos的简单介绍以及服务注册与发现功能的基本使用这篇博客为基础,我们来探究一下Ribbon负载均衡的基本使用
示例
nacos-provider(提供服务)
基本项目结构如下
还是Nacos的简单介绍以及服务注册与发现功能的基本使用这篇博客的代码,可以去这篇博客的nacos-provider章节查看,我就不搬运过来了
nacos-consumer(消费服务)
基本结构如下
其中feign包是使用feign调用远程服务不用管它
pom.xml文件
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>springcloud</artifactId>
<groupId>com.xt</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>nacos-consumer</artifactId>
<properties>
<maven.compiler.source>8</maven.compiler.source>
<maven.compiler.target>8</maven.compiler.target>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
</dependencies>
</project>
application.yml配置文件
server:
port: 8085
spring:
application:
name: nacos-consumer
cloud:
nacos:
discovery:
server-addr: 部署nacos server的服务器IP:8848
feign:
hystrix:
enabled: true
RibbonConfig
写一个ribbon配置类
IRule 接口的实现类定义了一系列负载规则
如果自己不指定,默认使用的就是轮询算法。
先使用RandomRule的随机来做测试
@Configuration
public class RibbonConfig {
@Bean
public IRule iRule() {
// 权重负载策略 nacos的负载均衡实现
// return new NacosRule(); 是spring cloud alibaba 继承了netflix.loadbalancer包的AbstractLoadBalancerRule抽象类的实现
// 会先过滤掉由于多次访问故障而处于断路器跳闸状态的服务,然后选择一个并发量最小的服务
// return new BestAvailableRule();
// 复合判断server所在区域的性能和server的可用性选择服务器
// return new ZoneAvoidanceRule();
// 先过滤掉故障实例,再选择并发较小的实例
// return new AvailabilityFilteringRule();
// 对RoundRobinRule 轮询的扩展,响应速度越快的实例选择权重越多大,越容易被选择
// return new WeightedResponseTimeRule();
// return new ResponseTimeWeightedRule(); 已经被弃用
// 随机
return new RandomRule();
// 先按照RoundRobinRule 轮询的策略获取服务,如果获取服务失败则在指定时间内进行重试,获取可用的服务
// return new RetryRule();
}
}
controller
@RestController
public class TestController {
private final RestTemplate restTemplate;
@Autowired
public TestController(RestTemplate restTemplate) {this.restTemplate = restTemplate;}
@RequestMapping(value = "/echo-restemplate/{str}", method = RequestMethod.GET)
public String echo(@PathVariable String str) {
return restTemplate.getForObject("http://nacos-provider/echo/" + str, String.class);
}
@Autowired
EchoService echoService;
@RequestMapping(value = "/echo-feign/{str}",method = RequestMethod.GET)
public String feign(@PathVariable String str) {
return echoService.echo(str);
}
}
feign(远程调用服务的东西)
@Component
@FeignClient(name = "nacos-provider")
public interface EchoService {
@RequestMapping(value = "/echo-error/{str}",method = RequestMethod.GET)
String echo(@PathVariable("str") String str);
//fallback实现类
@Component
class EchoServiceFallback implements EchoService{
@Override
public String echo(@PathVariable("str") String str) {
return "接口请求失败";
}
}
}
MyLoadBalancer类(自定义负载均衡的实现,我这里写死了,只访问一个实例)
public class MyLoadBalancer extends AbstractLoadBalancerRule{
public MyLoadBalancer() {
}
@SuppressWarnings({"RCN_REDUNDANT_NULLCHECK_OF_NULL_VALUE"})
public Server choose(ILoadBalancer lb, Object key) {
if (lb == null) {
return null;
} else {
Server server = null;
while(server == null) {
//测试当前线程是否被中断(检查中断标志),返回一个boolean并清除中断状态,第二次再调用时中断状态已经被清除,将返回一个false。
if (Thread.interrupted()) {
return null;
}
//获取已启动且可访问的服务器。
List<Server> upList = lb.getReachableServers();
//获取所有已知的服务器(可访问和不可访问)
List<Server> allList = lb.getAllServers();
//全部服务的实例个数
int serverCount = allList.size();
if (serverCount == 0) {
return null;
}
//RandomRule使用ThreadLocalRandom获取随机数,我这里直接写死,我只要1实例提供服务
int index = 1;
server = (Server)upList.get(index);
if (server == null) {
//放出CPU资源
Thread.yield();
} else {
if (server.isAlive()) {
return server;
}
server = null;
Thread.yield();
}
}
return server;
}
}
protected int chooseRandomInt(int serverCount) {
//ThreadLocalRandom,在多线程下,它为每个线程维护一个 seed 变量
return ThreadLocalRandom.current().nextInt(serverCount);
}
@Override
public Server choose(Object key) {
return this.choose(this.getLoadBalancer(), key);
}
@Override
public void initWithNiwsConfig(IClientConfig clientConfig) {
}
}
NacosConsumerApplication启动类
使用RestTemplate来请求服务,向Spring的IOC注入一个bean: restTemplate;并通过@LoadBalanced注解表明这个restRemplate开启负载均衡的功能。
@SpringBootApplication
@EnableDiscoveryClient
@EnableFeignClients
@RibbonClient(name="nacos-provider",configuration= RibbonConfig.class)
public class NacosConsumerApplication {
// @Bean
// @Scope(value = "prototype")
// public IRule loadBalanceRule() {
// return new NacosRule();
// }
@Bean
@LoadBalanced
public RestTemplate restTemplate() {
return new RestTemplate();
}
public static void main(String[] args){
SpringApplication.run(NacosConsumerApplication.class, args);
}
}
像Nacos的简单介绍以及服务注册与发现功能的基本使用这篇博客一样,先开启三个nacos-provider服务。再开启一个nacos-consumer服务
登录服务器部署的nacos服务的控制台(默认账号和密码都是nacos)
服务器IP:8848/nacos
我们可以看到我们注册好的4个服务实例
测试Ribbon自带的随机策略
配置类,设置成 RandomRule
return new RandomRule();
如下所示,将RandomRule注入到Spring 的IOC 容器中进行管理
@Configuration
public class RibbonConfig {
@Bean
public IRule iRule() {
// 权重负载策略 nacos的负载均衡实现
// return new NacosRule(); 是spring cloud alibaba 继承了netflix.loadbalancer包的AbstractLoadBalancerRule抽象类的实现
// 会先过滤掉由于多次访问故障而处于断路器跳闸状态的服务,然后选择一个并发量最小的服务
// return new BestAvailableRule();
// 复合判断server所在区域的性能和server的可用性选择服务器
// return new ZoneAvoidanceRule();
// 先过滤掉故障实例,再选择并发较小的实例
// return new AvailabilityFilteringRule();
// 对RoundRobinRule 轮询的扩展,响应速度越快的实例选择权重越多大,越容易被选择
// return new WeightedResponseTimeRule();
// return new ResponseTimeWeightedRule(); 已经被弃用
// 随机
return new RandomRule();
// 先按照RoundRobinRule 轮询的策略获取服务,如果获取服务失败则在指定时间内进行重试,获取可用的服务
// return new RetryRule();
}
}
访问nacos-consumer服务,然后nacos-consumer服务访问请求nacos-provider服务,点击刷新多次
1号provider服务实例
2号provider服务实例
3号provider服务实例
可以看出,确实是随机策略
测试自定义的负载均衡策略
修改RibbonConfig,向Spring IOC容器中注入MyLoadBalancer
return new MyLoadBalancer();
@Configuration
public class RibbonConfig {
@Bean
public IRule iRule() {
// 权重负载策略 nacos的负载均衡实现
// return new NacosRule(); 是spring cloud alibaba 继承了netflix.loadbalancer包的AbstractLoadBalancerRule抽象类的实现
// 会先过滤掉由于多次访问故障而处于断路器跳闸状态的服务,然后选择一个并发量最小的服务
// return new BestAvailableRule();
// 复合判断server所在区域的性能和server的可用性选择服务器
// return new ZoneAvoidanceRule();
// 先过滤掉故障实例,再选择并发较小的实例
// return new AvailabilityFilteringRule();
// 对RoundRobinRule 轮询的扩展,响应速度越快的实例选择权重越多大,越容易被选择
// return new WeightedResponseTimeRule();
// return new ResponseTimeWeightedRule(); 已经被弃用
// 随机
// return new RandomRule();
// 先按照RoundRobinRule 轮询的策略获取服务,如果获取服务失败则在指定时间内进行重试,获取可用的服务
// return new RetryRule();
//自己的负载均衡策略,只访问可访问服务列表的下标为1的服务实例
return new MyLoadBalancer();
}
}
重新运行nacos-consumer服务实例
然后再次使用浏览器访问nacos-consumer服务,让他去消费nacos-provider服务。
我多次点击刷新浏览器,只有3号provider服务实例的被调用次数在增加,说明我们的修改生效了。
注意:虽然我的负载均衡策略是设置的活动服务实例列表中的下标为1的服务实例接收消费。这里虽然是3号provider实例,但是下标为1是指列表里面的下标为1
//获取已启动且可访问的服务器。
List<Server> upList = lb.getReachableServers();
References:
- https://www.cnblogs.com/roytian/p/12176321.html
- https://zhuanlan.zhihu.com/p/180300022
- https://www.jianshu.com/p/861ed1960014
(写博客主要是对自己学习的归纳整理,资料大部分来源于书籍、网络资料和自己的实践,整理不易,但是难免有不足之处,如有错误,请大家评论区批评指正。同时感谢广大博主和广大作者辛苦整理出来的资源和分享的知识。)