四、ribbon
负载均衡(Load Balancer,简称LB):其含义就是指将负载(客户端发送的请求、工作任务等)进行平衡、分摊到多个服务上处理,从而协同完成工作任务。
Spring Cloud Ribbon是基于Netflix Ribbon实现的一套客户端负载均衡工具,它将提供一系列完整的配置项如:连接超时、重试等等,简单的说,就是在配置文件中列出LB后面所有的机器,Ribbon会自动基于某种规则(如简单轮询、随即连接等等)去连接这些机器。我们也可以更容易使用Ribbon实现自定义的负载均衡算法
- ribbon作用:
- 将可用的服务列表返回给客户端
- 核心:将用户的请求平摊的分配到多个服务上,从而达到系统的HA(高可用)
- 常用的负载均衡软件有Nginx、Lvs等
- dubbo、Spring Cloud中均提供了负载均衡,Spring Cloud的负载均衡算法可以自定义
- 负载均衡简单分类
- 集中式LB
即在服务的消费方和提供方之间使用独立的LB设施,如Nginx(反向代理服务器),由该设施负责把访问请求通过某种策略转发至服务提供方 - 进程式LB
将LB逻辑集成到消费方,消费方从服务注册中心获知有哪些地址可以用,然后自己再从这些地址中选出一个合适的服务器
Ribbon属于进程内LB,它只是一个类库,集成于消费方进程,消费方通过它来获取到服务提供方的地址
4.1 Ribbon使用(单服务提供者)
- 在相应的消费者module中添加依赖
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-ribbon</artifactId>
<version>1.4.6.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-eureka</artifactId>
<version>1.4.6.RELEASE</version>
</dependency>
- 编写客户端配置文件
# Eureka 配置
eureka:
client:
register-with-eureka: false # 不向Eureka注册自己,消费者只需要去注册中心拿服务
service-url:
# 客户端可以从以下三个注册中心中随机选择一个,取出服务
defaultZone: http://eureka7001.com:7001/eureka/,http://eureka7002.com:7002/eureka/,http://eureka7003.com:7003/eureka/
- 在主启动类中添加开启Eureka注解
@EnableEurekaClient
- 在configBean中添加负载均衡注解
// 配置实现负载均衡的RestTemplate
@Bean
@LoadBalanced // Ribbon
public RestTemplate getRestTemplate() {
return new RestTemplate();
}
**注意:**使用了Ribbon做负载均衡,此时消费者请求的url应该是一个变量,变量名为需要调用的服务的名称(即注册中心所显示的服务名,本例中服务只有一个,为SPRINGCLOUD-PROVIDER-DEPT),因此需要对消费者的controller层做出改变,将原来的http://localhost:8001更换成对应的服务名
// private static final String REST_URL_PREFIX = "http://localhost:8001";
// 使用Ribbon做负载均衡时,消费者请求的地址应该是一个变量,该变量即服务的名称
private static final String REST_URL_PREFIX = "http://SPRINGCLOUD-PROVIDER-DEPT";
启动注册中心、服务提供者、消费者,在注册中心可以看到服务已注册
消费者可成功调用服务
结论:Ribbon和Eureka整合以后,客户端可以直接调用服务,不用关心注册中心的端口号和IP地址
4.2 Ribbon使用(多服务提供者)
- 创建三个不同的数据库db01、db02、db03
- 复制得到另外两个服务提供者8002、8003,服务提供者的功能均相同,且服务名称也相同(名称在如下yml文件中进行配置)
spring:
application:
name: springcloud-provider-dept
- 启动3个服务提供者、两个注册中心、一个消费者
消费者每次发出请求localhost:81/consumer/dept/list,都会调用到不同的数据库,由于默认采用轮询的负载均衡方式,因此调用顺序为db01、db02、db03
4.3 自定义负载均衡策略
在与springcloud包同级的位置建立配置包myrule,以建立自定义Ribbon类,并在启动类中添加注解@RibbonClient,configuration = MyRule.class表示在微服务启动的时候,会加载自定义的Ribbon类
package com.mark.springcloud;/*
* @project: springcloud
* @name: DeptConsumer_80
* @author: Mark
* @Date: 2021/7/22
* @Time: 23:00
*/
import com.mark.myrule.MyRule;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.EnableEurekaClient;
import org.springframework.cloud.netflix.ribbon.RibbonClient;
@SpringBootApplication
@EnableEurekaClient
// 对名称为SPRINGCLOUD-PROVIDER-DEPT的服务进行均衡
@RibbonClient(name = "SPRINGCLOUD-PROVIDER-DEPT", configuration = MyRule.class)
public class DeptConsumer_81 {
public static void main(String[] args) {
SpringApplication.run(DeptConsumer_81.class, args);
}
}
在myrule包下新建自定义配置类、负载均衡策略类
配置类
package com.mark.myrule;/*
* @project: springcloud
* @name: MyRule
* @author: Mark
* @Date: 2021/7/24
* @Time: 21:03
*/
import com.netflix.loadbalancer.IRule;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class MyRule {
@Bean
public IRule myRandomRule() {
return new MarkRandomRule(); // 默认为轮询,现自定义为MarkRandomRule()
}
}
负载均衡策略类(这一程序主要是从自带的均衡算法RandomRule()更改得来)
package com.mark.myrule;/*
* @project: springcloud
* @name: MarkRandomRule
* @author: Mark
* @Date: 2021/7/24
* @Time: 21:21
*/
import com.netflix.client.config.IClientConfig;
import com.netflix.loadbalancer.AbstractLoadBalancerRule;
import com.netflix.loadbalancer.ILoadBalancer;
import com.netflix.loadbalancer.Server;
import java.util.List;
import java.util.concurrent.ThreadLocalRandom;
public class MarkRandomRule extends AbstractLoadBalancerRule {
// 每个服务,访问5次,5次后换下一个服务,总共3个服务
private int total = 0; // 被调用的次数
private int currentIndex = 0; // 当前是谁在提供服务
// @edu.umd.cs.findbugs.annotations.SuppressWarnings(value = "RCN_REDUNDANT_NULLCHECK_OF_NULL_VALUE")
public Server choose(ILoadBalancer lb, Object key) {
if (lb == null) {
return null;
}
Server server = null;
while (server == null) {
if (Thread.interrupted()) {
return null;
}
List<Server> upList = lb.getReachableServers(); // 获取活着的服务
List<Server> allList = lb.getAllServers(); // 获取所有服务
int serverCount = allList.size();
if (serverCount == 0) {
return null;
}
// int index = chooseRandomInt(serverCount); // 生存区间随机数
// server = upList.get(index); // 从活着的服务中随机获取一个
// ==========================================
if (total < 5) {
server = upList.get(currentIndex); // 从活着的服务中,获取指定的服务
total++;
} else {
total = 0;
currentIndex++;
if (currentIndex > upList.size()) { // 如果服务索引大于了活服务的数量
currentIndex = 0; // 重新从第一个服务开始调用
}
server = upList.get(currentIndex);
}
// ==========================================
if (server == null) {
Thread.yield();
continue;
}
if (server.isAlive()) {
return (server);
}
server = null;
Thread.yield();
}
return server;
}
protected int chooseRandomInt(int serverCount) {
return ThreadLocalRandom.current().nextInt(serverCount);
}
@Override
public Server choose(Object key) {
return choose(getLoadBalancer(), key);
}
@Override
public void initWithNiwsConfig(IClientConfig clientConfig) {
// TODO Auto-generated method stub
}
}
访问路径 http://localhost:81/consumer/dept/list 可成功调用服务,并且当访问同一服务的次数大于5次后,将切换服务,与自定义负载均衡策略相符
前5次访问的结果
第6次的访问结果