目录
一、负载均衡器
目前主流的负载方案分为以下两种:服务端负载均衡(nginx)、客户端负载均衡(ribbon);
1.1 集中式负载均衡,服务端负载均衡
在消费者和服务提供方中间使用独立的代理方式进行负载,有硬件的(比如 F5),也有软件的(比如 Nginx)。
例如Nginx,通过Nginx进行负载均衡,先发送请求,然后通过负载均衡算法,在多个服务器之间选择一个进行访问;即在服务器端再进行负载均衡算法分配。
1.2 客户端负载均衡器
客户端根据自己的请求情况做负载均衡,Ribbon 就属于客户端自己做负载均衡。
二、Ribbon
Spring Cloud Ribbon是基于Netflix Ribbon 实现的一套客户端的负载均衡工具,Ribbon客户端组件提供一系列的完善的配置,如超时,重试等。通过Load Balancer获取到服务提供的所有机器实例,Ribbon会自动基于某种规则(轮询,随机)去调用这些服务。Ribbon也可以实现我们自己的负载均衡算法。
spring cloud中的ribbon,客户端会有一个服务器地址列表,在发送请求前通过负载均衡算法选择一个服务器,然后进行访问,这是客户端负载均衡;即在客户端就进行负载均衡算法分配。
三、常见的负载均衡算法
如果使用的RestTemplate进行服务调用,那么创建RestTemplate的方法上面加@LoadBalanced注解就会开启Ribbon的负载均衡,Ribbon负载均衡有以下7中规则,默认轮询。
-
随机,通过随机选择服务进行执行,一般这种方式使用较少;
-
轮询,负载均衡默认实现方式,请求来之后排队处理;
-
加权轮询,通过对服务器性能的分型,给高配置,低负载的服务器分配更高的权重,均衡各个服务器的压力;
-
地址Hash,通过客户端请求的地址的HASH值取模映射进行服务器调度。 ip --->hash
-
最小链接数,即使请求均衡了,压力不一定会均衡,最小连接数法就是根据服务器的情况,比如请求积压数等参数,将请求分配到当前压力最小的服务器上。 最小活跃数
四、在Nacos中使用Ribbon
在此示例中使用ribbon服务去调用a服务,由于要使用ribbon并体验其负载均衡等算法。需要把a服务起两个端口并注册到注册中心中(详细见方式一、方式二),这样ribbon去调用时更能直观的看出负载算法调用结果。如下图;下面会在a服务中介绍起两个端口方式;
父pom文件
<?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 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.2.5.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.chensir</groupId>
<artifactId>nacos-root</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>nacos-root</name>
<description>nacos-root</description>
<properties>
<java.version>1.8</java.version>
<spring.cloud.version>Hoxton.SR3</spring.cloud.version>
</properties>
<packaging>pom</packaging>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.24</version>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>${spring.cloud.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
<version>2.2.1.RELEASE</version>
</dependency>
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
<version>2.2.1.RELEASE</version>
</dependency>
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-all</artifactId>
<version>5.8.3</version>
</dependency>
</dependencies>
</dependencyManagement>
<modules>
<module>nacos-spring-boot-config</module>
<module>nacos-spring-cloud-config</module>
<module>nacos-spring-cloud-discover-a</module>
<module>nacos-spring-cloud-discover-b</module>
<module>nacos-spring-cloud-openfeign-c</module>
<module>nacos-spring-cloud-ribbon</module>
<module>nacos-spring-cloud-gateway</module>
<module>spring-cloud-hystrix</module>
</modules>
</project>
ribbon服务
pom文件
<?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 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>com.chensir</groupId>
<artifactId>nacos-root</artifactId>
<version>0.0.1-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>
<artifactId>nacos-spring-cloud-ribbon</artifactId>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-all</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-ribbon</artifactId>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<excludes>
<exclude>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</exclude>
</excludes>
</configuration>
</plugin>
</plugins>
</build>
</project>
配置文件
server.port=8073
spring.application.name=nacos-app-ribbon
spring.cloud.nacos.discovery.server-addr=localhost:8848
logging.level.com.chensir = debug
FeignConfig
@Configuration
public class FeignConfig
{
@Bean
Logger.Level feignLoggerLevel()
{
return Logger.Level.FULL;
}
}
MyRuleConfig
@Configuration
//全局
@RibbonClients(defaultConfiguration = MyRuleConfig.class)
//针对某个服务修改 手写ribbon时可使用
//@RibbonClient(name = "nacos-app-a",configuration = MyRuleConfig.class)
public class MyRuleConfig {
@Bean
public IRule rule(){
// RandomRule() 随机
// return new RandomRule();
// RoundRobinRule() 轮询
return new RoundRobinRule();
// 自定义
// return new MyRule();
}
}
AClient
RibbonController
启动类
a服务
由于需要测试负责均衡算法,a服务需要按照多个端口启动并注册到注册中心中。下面会介绍两种多端口启动方式;
pom文件
<?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 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>com.chensir</groupId>
<artifactId>nacos-root</artifactId>
<version>0.0.1-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>
<artifactId>nacos-spring-cloud-discover-a</artifactId>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-all</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<excludes>
<exclude>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</exclude>
</excludes>
</configuration>
</plugin>
</plugins>
</build>
</project>
配置文件
以方式一运行,方式二在本机只能起一个,起多个端口还需docker部署;
详细见方式一
AController
@RestController
@Slf4j
public class AController {
@Value("${server.port}")
private String port;
@GetMapping("/echo")
public String test(@RequestParam(value = "name",defaultValue = "这是默认值") String name) {
String ip = NetUtil.getLocalhost().getHostAddress();
return StrUtil.format("这是a应用({}:{})输出的结果:{}",ip,port,name);
}
}
启动类
结果:
若Ribbon服务中MyRuleConfig使用了轮询算法负载均衡,那么会循环调用 8097和8098端口;
方式一、多个配置文件启动
在配置文件中复制多个,除了端口号port不同外,其他配置信息均相同;
在Edit Configurations中复制两个启动类,并在Active profiles中输入对应的端口号;然后分别启动两个启动类即可;
方式二、配置文件读取机器的环境变量
此方式只需要一个配置文件即可;在配置文件中给port起一个名字,如下图:
然后在Edit Configurations配置(也不用复制两个启动类)步骤如下:
这样就配置好了,然后使用docker部署时可指定端口运行;
以上读取机器环境变量使用docker部署步骤:
1、配置文件server.port= ${APPPORT} 需要这样写;
2、在环境变量中加入对应key值;
3、dockerfile
4、打包 构建镜像
5、指定端口运行:
docker run -d --network=host --name a8031 -e APPPORT=8031 a:v1
这样就运行了端口号为8031的a服务,若再以别的端口号运行 只需要该上方运行命令指定端口号即可(jar包不用修改,仍用同一个jar包)
手写一个负载均衡算法
经过使用轮询、随机等负载均衡算法可看,他们均继承了AbstractLoaBalancerRule抽象类,那么实现自己的负载均衡算法也需要继承与此类。详细可见下方;
MyRule
public class MyRule extends AbstractLoadBalancerRule {
@Autowired
private NacosDiscoveryProperties nacosDiscoveryProperties;
//AtomicInteger计数器 是concurrent这个包下的,这个cut包下的全是线程安全的
private static AtomicInteger count = new AtomicInteger(0);
Server server = null;
@SneakyThrows
@Override
public Server choose(Object key) {
BaseLoadBalancer loadBalancer = (BaseLoadBalancer) this.getLoadBalancer();
//获取到服务名
String name = loadBalancer.getName();
NamingService namingService = nacosDiscoveryProperties.namingServiceInstance();
//查询所以实例 name 服务名,true 健康的 可返回服务中ip 端口号
List<Instance> instances = namingService.selectInstances(name, true);
//将instances列表转换成数据流,然后取实例中权重最大值
Instance instanceMax = instances.stream().max(Comparator.comparing(Instance::getWeight)).get();
//取权重最小值
Instance instanceMin = instances.stream().min(Comparator.comparing(Instance::getWeight)).get();
//每次加1
int i = count.getAndAdd(1);
int mod = i % 5;
if (mod == 0) {
//取模除于5等于0时用权重最小那个服务 计数器为0 5 10 15等时用权重最小那个。
server = new Server(instanceMin.getIp(), instanceMin.getPort());
} else {
//计数器除于5不等于0时 用权重最大那个!
server = new Server(instanceMax.getIp(), instanceMax.getPort());
}
return server;
}
@Override
public void initWithNiwsConfig(IClientConfig iClientConfig) {
}
}
MyRuleConfig
@Configuration
//全局
//@RibbonClients(defaultConfiguration = MyRuleConfig.class)
//针对某个服务修改
@RibbonClient(name = "nacos-app-a",configuration = MyRuleConfig.class)
public class MyRuleConfig {
@Bean
public IRule rule(){
// RandomRule() 随机
// new RandomRule();
// return new RandomRule();
// RoundRobinRule() 轮询
// new RoundRobinRule();
// return new RoundRobinRule();
// 自定义
return new MyRule();
}
}