SpringCloud 使用 LoadBalance 实现客户端负载均衡
SpringCloud 从 2020.0.1 版本开始,从 Eureka 中移除了 Ribbon 组件,使用 LoadBalance 组件来代替 Ribbon 实现客户端负载均衡。LoadBalance 组件相对于 Ribbon 来说,仅支持两种负载均衡策略:【轮询策略】和【随机策略】,估计后续会增加更多的负载均衡算法策略吧,从我个人的使用经验来说,其实 Ribbon 的负载均衡功能挺好用的。
本篇博客以目前最新版本的 SpringCloud 2021.0.3 版本为例,通过代码的方式来介绍使用 LoadBalance 组件实现客户端负载均衡的用法,在本篇博客的最后会提供源代码的下载。
一、搭建工程
采用 Maven 搭建 springcloud_loadbalance 父工程,下面包含 5 个子工程:
- 1 个 Eureka 注册中心
- 1 个 Consumer 服务消费者
- 3 个 Provider 服务提供者
搭建后的效果如下图所示:
对于 springcloud_loadbalance 父工程 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
http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.jobs</groupId>
<artifactId>springcloud_loadbalance</artifactId>
<packaging>pom</packaging>
<version>1.0-SNAPSHOT</version>
<modules>
<module>eureka_app</module>
<module>provider_app1</module>
<module>provider_app2</module>
<module>provider_app3</module>
<module>consumer_app</module>
</modules>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<java.version>1.8</java.version>
</properties>
<!--spring boot-->
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.6.11</version>
<relativePath/>
</parent>
<!--Spring Cloud-->
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>2021.0.3</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
</project>
二、注册中心搭建
在本篇博客的 Demo 中,仅搭建一个 Eureka 注册中心就能够满足使用了。
首先在 pom 文件中引入 spring-cloud-starter-netflix-eureka-server 的依赖。
<?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_loadbalance</artifactId>
<groupId>com.jobs</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>eureka_app</artifactId>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!--eureka-server-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
</dependency>
</dependencies>
</project>
然后在启动来上,添加 @EnableEurekaServer 注解
package com.jobs.server;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.server.EnableEurekaServer;
// 启用EurekaServer
@EnableEurekaServer
@SpringBootApplication
public class EurekaApp {
public static void main(String[] args) {
SpringApplication.run(EurekaApp.class,args);
}
}
然后配置 Eureka 的 application 的 yml 文件。需要注意的是:需要将 Eureka 的 hostname 在本机的 hosts 文件中建立与本机 ip 的映射关系,因为后续在配置服务消费者和服务提供者的注册中心地址时,使用 Eureka 的 hostname 比较方便一些。
# eureka 的默认端口是 8761,这里使用默认端口
server:
port: 8761
eureka:
instance:
# 配置主机名,请在【hosts】文件中进行ip映射配置,
# 因为后续的服务消费者和服务提供者需要使用这里的 hostname 配置其注册的 eureka 地址
hostname: eureka-server
client:
service-url:
# 该 eureka 注册中心对外提供的注册地址
defaultZone: http://${eureka.instance.hostname}:${server.port}/eureka
# 是否将自己的路径注册到 eureka 上,单个 eureka 不需要把自己注册的自己上
register-with-eureka: false
# 是否需要从 eureka 中抓取路径,单个 eureka 不需要把自己从自己上获取服务的 url 地址
fetch-registry: false
server:
# 关闭 eureka 自我保护,当注册的服务未及时发送心跳时,自动移除服务
enable-self-preservation: false
# eureka 检查服务是否存在的时间间隔(毫秒)
eviction-interval-timer-in-ms: 5000
# 单个 eureka 节点,因为不需要注册到自己身上,所以下面不需要配置
spring:
application:
name: eureka-server
三、服务提供者搭建
本篇博客使用的服务提供者代码,跟上一篇博客的代码基本相同,只不过搭建了 3 个服务提供者,在接口返回的 json 结果中增加了 provider 字段,内容是服务提供者的 application 名称和配置的端口,用于验证负载均衡的调用效果,这里以 provider-app1 为例进行介绍。
首先 pom 文件需要引入 spring-cloud-starter-netflix-eureka-client 的依赖。
<?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_loadbalance</artifactId>
<groupId>com.jobs</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>provider_app1</artifactId>
<dependencies>
<!--spring boot web-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- eureka-client -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
</dependencies>
</project>
编写 provider-app1 的 application.yml 配置文件
server:
port: 8201
eureka:
instance:
# 配置主机名
hostname: provider-service1
# 显示 ip 地址,代替显示主机名
prefer-ip-address: true
# 所注册服务实例名称的显示形式
instance-id: ${eureka.instance.hostname}:${server.port}
# 每隔 3 秒发一次心跳包
lease-renewal-interval-in-seconds: 3
# 如果 15 秒没有发送心跳包,就让 eureka 把自己从服务列表中移除
lease-expiration-duration-in-seconds: 15
client:
service-url:
# 将当前 springboot 服务注册到 eureka 中
defaultZone: http://eureka-server:8761/eureka
# 是否将自己的路径注册到 eureka 上
register-with-eureka: true
# 是否需要从 eureka 中抓取路径
fetch-registry: true
# provider 集群需要使用相同的 application 名称
spring:
application:
name: provider-App
需要注意的是:本篇博客使用 3 个服务提供者搭建集群,因此 3 个服务提供者必须使用相同的 application 名称,这里统一配置为 provider-app ,最后列出服务提供者所提供的接口内容:
package com.jobs.provider.controller;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.env.Environment;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.HashMap;
import java.util.Map;
import java.util.UUID;
@RequestMapping("/provider")
@RestController
public class ProviderController {
@Autowired
private Environment env;
@RequestMapping("/getdata/{id}")
public Map GetData(@PathVariable("id") int id) {
//获取当前服务提供者的 hostname 和 port
String providerName = env.getProperty("eureka.instance.hostname")
+ ":" + env.getProperty("server.port");
Map result = new HashMap();
result.put("status", 0);
result.put("msg", "success");
result.put("provider", providerName);
result.put("get_id_value", id);
result.put("version", UUID.randomUUID().toString());
return result;
}
}
四、服务消费者搭建
这里是本篇博客介绍的重点,因为 SpringCloud 使用 LoadBalance 实现负载均衡,就是在服务消费者这里实现的。
首先需要引入 spring-cloud-starter-netflix-eureka-client 的依赖,其 pom 文件跟服务提供者相同,这里不再列出。
这里先列出服务消费者的 application.yml 配置文件内容
server:
port: 8100
eureka:
instance:
# 配置主机名
hostname: consumer-service
# 显示 ip 地址,代替显示主机名
prefer-ip-address: true
# 所注册服务实例名称的显示形式
instance-id: ${eureka.instance.hostname}:${server.port}
# 每隔 3 秒发一次心跳包
lease-renewal-interval-in-seconds: 3
# 如果 15 秒没有发送心跳包,就让 eureka 把自己从服务列表中移除
lease-expiration-duration-in-seconds: 15
client:
service-url:
# 将当前 springboot 服务注册到 eureka 中
defaultZone: http://eureka-server:8761/eureka
# 是否将自己的路径注册到 eureka 上
register-with-eureka: true
# 是否需要从 eureka 中抓取路径
fetch-registry: true
# consumer 使用的 application 名称
spring:
application:
name: consumer-App
# SpringCloud 从 2020.0.1 版本之后,客户端负载均衡抛弃了 ribbon
# 采用 SpringCloud LoadBalancer 默认使用的是轮询负载均衡算法
# 要想使用随机算法,也很简单,本篇博客的 demo 也就介绍,您也可以查看官网
# https://docs.spring.io/spring-cloud-commons/docs/current/reference/html/#spring-cloud-loadbalancer
SpringCloud 的 LoadBalance 仅支持 2 种负载均衡策略:轮询(默认)和随机,后续应该会支持更多,先介绍轮询策略的使用吧。
只需要在 RestTemplate 的 Bean 对象上增加 @LoadBalanced 注解即可,其实上一篇博客的 demo 就已经使用了。
package com.jobs.consumer.config;
import org.springframework.cloud.client.loadbalancer.LoadBalanced;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.client.RestTemplate;
/*
SpringCloud 从 2020.0.1 版本开始,从 eureka 中移除了 ribbon
使用 SpringCloud LoadBalance 替代 ribbon 进行客户端负载均衡
目前 SpringCloud LoadBalance 仅支持两种负载均衡策略:【轮询策略】和【随机策略】
*/
@Configuration
public class RestTemplateConfig {
//增加该注解后,即可支持客户端负载均衡
@LoadBalanced
@Bean
public RestTemplate restTemplate() {
return new RestTemplate();
}
}
然后使用 RestTemplate 调用服务提供者的接口时,使用服务提供者的 application 名称,从注册中心获取服务提供者集群的所有服务地址,自动在本地通过负载均衡的算法,决定要调用那一个服务地址。
需要注意的是:服务提供者的名称最好从 Eureka 注册中心界面中复制,这么不容易出错。
下面列出服务消费者调用服务提供者接口的代码
package com.jobs.consumer.controller;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.client.RestTemplate;
import java.util.Map;
@RequestMapping("/consumer")
@RestController
public class ConsumerController {
@Autowired
private RestTemplate restTemplate;
@RequestMapping("/getdata/{id}")
public Map GetData(@PathVariable("id") int id) {
//使用服务在 eureka 上注册的 application 名称代替 ip 和端口号
String url = "http://PROVIDER-APP/provider/getdata/" + id;
Map result = restTemplate.getForObject(url, Map.class);
return result;
}
}
注意:上面采用 PROVIDER-APP 代替了服务提供者的 ip 和 端口,PROVIDER-APP 是从注册中心界面中复制过来的。
到此为止,服务的消费者采用轮询的客户端负载均衡方式,调用服务提供者接口的代码示例,已经搭建完毕,可以进行测试验证。
五、采用随机负载均衡策略
服务的消费者如果想采用随机的负载均衡策略,非常简单,可以参考以下官网地址:
https://docs.spring.io/spring-cloud-commons/docs/current/reference/html/#spring-cloud-loadbalancer
需要通过以下两个步骤即可实现:
首先需要定义随机的算法对象,并通过 @Bean 将其加载到 Spring 容器中,具体如下:
package com.jobs.consumer.config;
import org.springframework.cloud.client.ServiceInstance;
import org.springframework.cloud.loadbalancer.core.RandomLoadBalancer;
import org.springframework.cloud.loadbalancer.core.ReactorLoadBalancer;
import org.springframework.cloud.loadbalancer.core.ServiceInstanceListSupplier;
import org.springframework.cloud.loadbalancer.support.LoadBalancerClientFactory;
import org.springframework.context.annotation.Bean;
import org.springframework.core.env.Environment;
public class RandomLoadBalancerConfig {
@Bean
ReactorLoadBalancer<ServiceInstance> randomLoadBalancer(
Environment environment, LoadBalancerClientFactory loadBalancerClientFactory) {
String name = environment.getProperty(LoadBalancerClientFactory.PROPERTY_NAME);
return new RandomLoadBalancer(loadBalancerClientFactory
.getLazyProvider(name, ServiceInstanceListSupplier.class), name);
}
}
然后在 RestTemplate 配置类上方,使用 @LoadBalancerClient 或 @LoadBalancerClients 注解,针对具体服务配置具体的客户端负载均衡算法策略,由于本篇博客只有一个服务提供者集群,因此这里使用 @LoadBalancerClient 注解进行演示:
package com.jobs.consumer.config;
import org.springframework.cloud.client.loadbalancer.LoadBalanced;
import org.springframework.cloud.loadbalancer.annotation.LoadBalancerClient;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.client.RestTemplate;
/*
SpringCloud 从 2020.0.1 版本开始,从 eureka 中移除了 ribbon
使用 SpringCloud LoadBalance 替代 ribbon 进行客户端负载均衡
目前 SpringCloud LoadBalance 仅支持两种负载均衡策略:【轮询策略】和【随机策略】
*/
//使用 @LoadBalancerClient 或 @LoadBalancerClients 注解,针对具体服务配置具体的客户端负载均衡算法策略
@LoadBalancerClient(name = "PROVIDER-APP", configuration = RandomLoadBalancerConfig.class)
@Configuration
public class RestTemplateConfig {
@LoadBalanced
@Bean
public RestTemplate restTemplate() {
return new RestTemplate();
}
}
重启消费者程序后,再调用服务提供者 PROVIDER-APP 所提供的接口时,采用的就是随机的客户端负载均衡策略。
OK,到此为止,有关 SpringCloud 在 2020.0.1 版本之后,使用 LoadBalance 代替 Ribbon 实现客户端负载均衡的实现方式,已经介绍完毕。目前官网只支持【轮询】和【随机】两种算法,希望后续能够像 Ribbon 一样支持更多的负载均衡算法。
本篇博客的源代码下载地址为:https://files.cnblogs.com/files/blogs/699532/springcloud_loadbalance.zip