1 负载均衡介绍
当前的主流负载均衡方案分为两种:
- 集中式负载均衡,在客户端和服务提供方中间使用独立的代理方式进行负载,比如硬件F5(直接跟交换机进行连接,任何请求都是先到达交换机,然后由交换机进行集群中的服务提供方选择),或者安装软件Nignx。硬件性能更高,成本高。
- 客户端根据自己的请求情况做负载均衡,Ribbon属于客户端自己做负载均衡
Ribbon是基于Netflix Ribbon实现的一套客户端负载均衡工具。Ribbon客户端组件提供了一系列完善的配置,如超时,重试等。通过Load Balancer 获得服务提供的所有机器实例。Ribbon会基于某种规则(轮询,随机)去调用这些服务,也可以实现自己的负载均衡算法。
1.1 客户端的负载均衡
例如Spring cloud Ribbon,客户端会有一个服务器地址列表,在发送请求前通过负载均衡算法去选择一个服务器,然后进行访问,这是客户端负载均衡。
1.2 服务端的负载均衡
由Nignx进行负载均衡,先发送请求,然后再通过负载均衡算法,在多个服务器之间选择一个进行访问。
1.3 常见负载均衡算法
- 随机, 通过随机选择服务执行,少用
- 轮询,负载均衡默认实现方式,请求来了之后排队处理
- 加权轮循,通过对服务器性能的分类,给高配置,低负载的服务器分配更高的权重,均衡各个服务器的压力
- 地址Hash,通过客户端请求的地址的HASH值取模映射进行服务器调度。ip->HASH
- 最小连接数,即使请求均衡了,压力也不一定会均衡,最小连接数法就是根据服务器情况,比如请求积压数等参数,将请求分配到当前压力最小的服务器上。最小活跃数。
2 Nacos使用Ribbon
Nacos 2021版本已经没有自带ribbon的整合了,需要引入支持的jar包loadbalancer,且2021版本取消了对ribbon的支持,无法通过修改Ribbon负载均衡的模式来实现nacos提供的负载均衡模式。 参考blog.csdn.net/qq_38050728…
但是老版本的默认使用ribbon(2021之前的版本,这里演示使用的老版本,如下三个依赖版本)。
<spring.cloud.alibaba.version>2.2.9.RELEASE</spring.cloud.alibaba.version>
<spring.boot.version>2.3.12.RELEASE</spring.boot.version>
<spring.cloud.version>Hoxton.SR12</spring.cloud.version>
1)首先引入nacos-discovery
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
我们查看nacos-discovery,可见它依赖了Ribbon,因此可以不用在pom文件中引入ribbon依赖。
2)在spring提供的RestTemplate上添加@LoadBalanced注解,就能自动实现负载均衡
3)修改controller:在controller中可以直接使用服务命进行调用。
3 Ribbon负载均衡策略
ribbon中的关键类如下图所示:
我们通过实现IRule接口可以自定义负载均衡策略,主要的服务选择逻辑方法在choose方法中。(idea中使用ctrl+H快捷键查看IRule的子类)
classDiagram
IClientConfigAware <|.. AbstractLoadBalancerRule
IRule <|.. AbstractLoadBalancerRule
AbstractLoadBalancerRule <|-- RoundRobbinRule
AbstractLoadBalancerRule <|-- RandomRule
AbstractLoadBalancerRule <|-- ClientConfigEnabledRoundRobinRule
AbstractLoadBalancerRule <|-- RetryRule
AbstractLoadBalancerRule <|-- NacosRule
RoundRobbinRule <|-- ResponseTimeWeightedRule
RoundRobbinRule <|-- WeightedResponseTimeRule
ClientConfigEnabledRoundRobinRule <|-- PredicateBasedRule
ClientConfigEnabledRoundRobinRule <|-- BestAvailableRule
PredicateBasedRule <|-- ZoneAvoidanceRule
PredicateBasedRule <|-- AvailabilityFilteringRule
class IRule{
+ Server choose
+ void setLoadBalancer
+ ILoadBalancer getLoadBalancer
}
基于上述类图,根据每个类名可大概知道每个策略。
-
IRule 所有负载均衡策略的父接口,核心方法choose,用来选择一个服务实例
-
AbstractLoadBalancerRule 抽象类,主要定义了一个ILoadBalancer,用于辅助负责均衡策略选择合适的服务端实例
-
RandomRule 随机选择一个服务实例,再RandomRule的无参构造方法中初始化一个Random对象,然后重写的choose方法调用了choose(ILoadBalancer lb, Object key)这个重载的方法。在重载方法中,每次利用random对象生成一个不大于服务实例总数的随机数,并将该数作为下标以获取一个服务实例。
-
RoundRobbinRelu:轮询负载均衡策略。重写的choose方法中,开启一个计数器count,在while循环中遍历服务清单,获取清单前先通过 incrementAndGetModulo获取一个下标,这个下标是一个不断自增长的数加1然后和服务清单总数取模后获得的,再用下标去服务清单列表中取服务,每次循环计数都会加1,如果连续10次都没有获取到服务,则会警告 log.warn("No up servers available from load balancer: " + lb);
-
RetryRelu:在轮询的基础上进行重试,首先RetryRule中定义了一个subRule,它的实现类就是RoundRobbinRule,然后在RetryRule的choose()方法中,每次还是采用RoundRobinRule中的choose规则来选一个服务实例,如果选到的正常实例正常就返回,如果选择的服务实例为null或者已经失效,则在失效时间deadline之前不断的重试。如果超过了deadline还是没有获取到,则返回一个null。
-
WeightedResponseTimeRule(基于权重的——nacos的NacosRule,Nacos还扩展了一个自己的基于配置的权重扩展),RoundRobinRule的一个子类,进行了功能扩展。根据每个实例的运行情况来计算出实例的权重,然后根据权重进行实例挑选。WeightedResponseTimeRule中有一个名叫DynamicServerWeightTask的定时任务,默认情况下,每隔30秒会计算一次各个服务实例的权重。(计算权重的规则:如果一个服务的平均响应时间越短则权重越大,该实例被选中的概率也就越大)
-
ClientConfigEnabledRoundRobinRule:内部定义了RoundRobbinRule.
-
BestAvailableRule: 在CientConfigEnabledRoundRobinRule的基础上主要增加了根据loadBalancerStats中保存的服务实例的状态信息来过滤掉失效的服务实例的功能,顺便找出并发请求最小的服务实例来使用。如果loadBalancerStats为null,则BestAvailableRule将采用父类的服务选择策略(线性轮询)。
-
ZoneAvoidance:(默认规则,复合判断server所在的区域(比如服务器部署在不同的省市区域)的性能和server的可用性选择服务器(如果没有区域的概念,则采用线性轮询))是PredicateBasedRule的一个实现类,只不过多了一个过滤条件,以ZoneAvoidancePredicate为主过滤条件和以AvailabilityPredicate为次过滤条件组成的一个CompositePredicate的组合过滤条件。过滤成功后采用先行轮询(RoundRobinRule)的方式从过滤结果中选择一个出来
-
AvailablilityFilteringRule(先过滤掉故障实例,再选择并发最小的实例)过滤掉一直连接失败的被标记为circuit tripped的后端server,并过滤掉那些高并发的后端Server或使用一个AvailabilityPredicate来包含过滤server的逻辑。其实就是检查status记录的各个server的运行状态
3.1 修改默认负载均衡策略
在配置调用其他服务所使用的负载均衡策略时,有两种。全局配置和局部配置。
- 全局配置:调用其它微服务,一律使用指定的负载均衡策略。即写一个配置类,指定对应的规则。配置类不能放到启动类所在的包下,因为启动类的 ComponentScan在没有指定basePackages时,会将当前类所在的包作为扫描包进行扫描。如果配置类放到启动类包下,会被所有的服务提供方共享。
@Configuration
public class RibbonRandomRuleConfig {
/**
* 方法名一定要为iRule
* @return
*/
@Bean
public IRule iRule(){
return new RandomRule();
}
}
- 局部配置:调用指定微服务提供的服务时,使用对应的负载均衡算法 修改application.yml
stock-service:
ribbon:
# 指定使用Nacos提供的负载均衡策略(优先调用同一集群的实例,基于随机权重)
NFLoadBalancerRuleClassName: com.alibaba.cloud.nacos.ribbon.NacosRule
3.2 自定义负载均衡策略
实现接口IRule或者继承基类AbstractLoadBalancerRule,主要的选择服务逻辑在choose方法中。例如写一个随机获取服务的负载均衡策略
public class CustomRule extends AbstractLoadBalancerRule {
@Override
public void initWithNiwsConfig(IClientConfig iClientConfig) {
}
@Override
public Server choose(Object o) {
ILoadBalancer lb = this.getLoadBalancer();
// 获得当前请求的服务的实例
List<Server> reachableServers = lb.getReachableServers();
// 线程安全的获取一个随机数
int random = ThreadLocalRandom.current().nextInt(reachableServers.size());
Server server = reachableServers.get(random);
if(server.isAlive()){
return null;
}
return server;
}
}
然后使用注解或者配置文件进行配置就可以了。
(懒加载)负载均衡器在第一次调用的时候才会进行动态加载,第一次会比较慢,甚至超时。配置的方式使用饥饿加载模式。
4 使用LoadBalancer替代Ribbon
Spring cloud的LoadBalancer是官方自己提供的客户端均衡负载器,用来代替ribbon 。提供了两种负载均衡的客户端
- RestTemplate:用于访问Rest服务的客户端,依赖jdk提供的HTTP连接工具。
- WebClient:从Spring WebFlux5.0版本开始提供的一个非阻塞的基于响应式编程的发送http请求的工具。提供了标准的HTTP的get,post,put,delete等方法。
使用RestTemplate时,如果spring cloud 是老版本(2021之前的),则需要在nacos-discovery中移除Ribbon依赖。
<!--nacos服务发现组件-->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
<exclusions>
<exclusion>
<!--移除ribbon依赖-->
<groupId>com.cxq.springcloud</groupId>
<artifactId>spring-cloud-starter-netflix-ribbon</artifactId>
</exclusion>
</exclusions>
</dependency>
或者直接在application.yml中禁用ribbon.
spring:
application:
name: order-service
cloud:
nacos:
discovery:
server-addr: 127.0.0.1:8848
discovery:
username: nacos
password: nacos
# 命名空间用来隔离不同的实例环境
namespace: public
loadbalancer:
ribbon:
enabled: false
建议直接移除。
而新版本不依赖ribbon,可直接引入loaderbalancer依赖,不用再去禁用。
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-loadbalancer</artifactId>
<version>2.2.9.RELEASE</version>
</dependency>