3.Spring Cloud LoadBalancer 入门与使用
- 1.什么是 LoadBalancer?
- 1.1负载均衡分类
- 1.2 常见负载均衡策略
- 2.为什么要学 LoadBalancer?
- 3.如何使用?
- 4.默认负载均衡策略
- 5.随机负载均策略
- 5.1 创建随机负载均衡器
- 5.2 设置随机负载均衡器 (局部设置)
- 5.3 设置全局负载均衡器
- 6.Nacos 权重负载均器
- 6.1 创建 Nacos 负载均衡器
- 6.2 设置负载均衡器
- 7.自定义负载均衡器
- 7.1 创建自定义负载均衡器
- 7.2 封装自定义负载均衡器
- 7.3 设置自定义负载均器
- 8.缓存
- 关闭缓存
- 9.执行原理
- 底层执行原理
- 1. `ServiceInstanceListSupplier`
- 2. `LoadBalancerClient`
- 3. `LoadBalancer`
- 执行流程
- 源码示例
- 总结
1.什么是 LoadBalancer?
LoadBalancer(负载均衡器)是一种网络设备或软件机制,用于分发传入的网络流量负载(请求)到多个后端目标服务器上,从而实现系统资源的均衡利用和提高系统的可用性和性能。
1.1负载均衡分类
负载均衡分为服务器端负载均衡和客户端负载均衡。
- 服务器端负载均衡指的是存放在服务器端的负载均衡器,例如 Nginx、HAProxy、F5 等.
- 客户端负载均衡指的是嵌套在客户端的负载均衡器,例如 Ribbon、Spring Cloud LoadBalancer。
1.2 常见负载均衡策略
但无论是服务器端负载均衡和客户端负载均衡,它们的负载均衡策略都是相同的,因为负载均衡策略本质上是一种思想。
常见的负载均衡策略有以下几个:
- 轮询(Round Robin):轮询策略按照顺序将每个新的请求分发给后端服务器,依次循环。这是一种最简单的负载均衡策略,适用于后端服务器的性能相近,且每个请求的处理时间大致相同的情况。
- 随机选择(Random):随机选择策略随机选择一个后端服务器来处理每个新的请求。这种策略适用于后端服2务器性能相似,且每个请求的处理时间相近的情况,但不保证请求的分发是均的。
- 最少连接(Least Connections):最少连接策略将请求分发给当前连接数最少的后端服务器。这可以确保负载均衡在后端服务器的连接负载上均衡,但需要维护连接计数。
- IP 哈希(IP Hash):IP 哈希策略使用客户端的 IP 地址来计算哈希值,然后将请求发送到与哈希值对应的后端服务器。这种策略可用于确保来自同一客户端的请求都被发送到同一台后端服务器,适用于需要会话保持的情况。
- 加权轮询(Weighted Round Robin):加权轮询策略给每个后端服务器分配一个权重值,然后按照权重值比例来分发请求。这可以用来处理后端服务器性能不均衡的情况,将更多的请求分发给性能更高的服务器。
- 加权随机选择(Weighted Random):加权随机选择策略与加权轮询类似,但是按照权重值来随机选择后端服务器。这也可以用来处理后端服务器性能不均衡的情况,但是分发更随机。
- 最短响应时间(Least Response Time):最短响应时间策略会测量每个后端服务器的响应时间,并将请求发送到响应时间最短的服务器。这种策略可以确保客户端获得最快的响应,适用于要求低延迟的应用。
2.为什么要学 LoadBalancer?
作为早期版本中内置的负载均衡器 Ribbon,在 Spring Cloud 2020.0.0 中已经被移除了,更新日志详见,https://github.com/spring-cloud/spring-cloud-release/wiki/Spring-Cloud-2020.0-Release-Notes取而代之的是 Spring Cloud LoadBalancer,并日它也是 Spring cloud 官方提供的负载均衛器,所以咱们的课程就要学习最新最主流的机制栈,而 Spring Cloud LoadBalancer 则是绕不过去的必学知识。
3.如何使用?
在项目中添加 Spring Cloud OpenFeign 和注册中心如 Nacos 之后,再添加 Spring Cloud LoadBalancer 则会在进行接口调用时直接使用 Spring Cloud LoadBalancer。
4.默认负载均衡策略
Spring Cloud LoadBalancer 负载均衡策略默认的是轮询,这一点可以通过 Spring Cloud LoadBalancer 的配置类LoadBalancerClientConfiguration 中发现,它的部分源码如下:
public class LoadBalancerClientConfiguration {
private static final int REACTIVE_SERVICE_INSTANCE_SUPPLIER_ORDER = 193827465;
@Bean
@ConditionalOnMissingBean
public ReactorLoadBalancer<ServiceInstance> reactorServiceInstanceLoadBalancer(Environment environment,
LoadBalancerClientFactory loadBalancerClientFactory) {
String name = environment.getProperty(LoadBalancerClientFactory.PROPERTY_NAME);
return new RoundRobinLoadBalancer(
loadBalancerClientFactory.getLazyProvider(name, ServiceInstanceListSupplier.class), name);
}
继续查看 RoundRobinLoadBalancer 核心实现源码如下:
private Response<ServiceInstance> getInstanceResponse(List<ServiceInstance> instances) {
if (instances.isEmpty()) {
if (log.isWarnEnabled()) {
log.warn("No servers available for service: " + serviceId);
}
return new EmptyResponse();
}
// Do not move position when there is only 1 instance, especially some suppliers
// have already filtered instances
if (instances.size() == 1) {
return new DefaultResponse(instances.get(0));
}
// Ignore the sign bit, this allows pos to loop sequentially from 0 to
// Integer.MAX_VALUE
int pos = this.position.incrementAndGet() & Integer.MAX_VALUE;
ServiceInstance instance = instances.get(pos % instances.size());
return new DefaultResponse(instance);
}
5.随机负载均策略
Spring Cloud LoadBalancer 内置了两种负载均衡策略
- 轮询负载均衡策略,默认负载均衡策略。
- 随机负载均衡策略
而要实现随机负载均衡策略的步骤如下:
- 创建随机负载均衡策略。
- 设置随机负载均衡策略。
5.1 创建随机负载均衡器
public class RandomLoadBalancerConfig {
@Bean
public ReactorLoadBalancer<ServiceInstance> randomLoadBalancer(
Environment environment, LoadBalancerClientFactory loadBalancerClientFactory) {
String name = environment.getProperty("loadbalancer.client.name");
return new RandomLoadBalancer(
loadBalancerClientFactory.getLazyProvider(name,
ServiceInstanceListSupplier.class), name);
}
}
5.2 设置随机负载均衡器 (局部设置)
package com.example.consumer.service;
import com.example.consumer.config.CustomLoadBalancerConfig;
import com.example.consumer.config.NacosLoadBalancerConfig;
import com.example.consumer.config.RandomLoadBalancerConfig;
import org.springframework.cloud.loadbalancer.annotation.LoadBalancerClient;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.stereotype.Service;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
@Service
@FeignClient("loadbalancer-service")
// 设置局部负载均衡策略
@LoadBalancerClient(name = "loadbalancer-service",
configuration = RandomLoadBalancerConfig.class)
public interface UserService {
@RequestMapping("/user/getname")
String getName(@RequestParam("id") Integer id);
}
5.3 设置全局负载均衡器
package com.example.consumer;
import com.example.consumer.config.CustomLoadBalancerConfig;
import com.example.consumer.config.RandomLoadBalancerConfig;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.loadbalancer.annotation.LoadBalancerClients;
import org.springframework.cloud.openfeign.EnableFeignClients;
@SpringBootApplication
@EnableFeignClients // 开启 Openfeign
// 设置全局的负载均衡策略
@LoadBalancerClients(defaultConfiguration =
RandomLoadBalancerConfig.class)
public class ConsumerApplication {
public static void main(String[] args) {
SpringApplication.run(ConsumerApplication.class, args);
}
}
6.Nacos 权重负载均器
Nacos 中支持两种负载均衡器,一种是权重负载均衡器,另一种是第三方 CMDB(地域就近访问)标签负载均後器,我们可以将 Spring Cloud Loadbalancer 直接配置为 Nacos 的负载均衡器,它默认就是权重负载均衡策略。它的配置有以下两步:
- 创建 Nacos 负载均衡器
- 设置负载均衡器
6.1 创建 Nacos 负载均衡器
@LoadBalancerClients(defaultConfiguration = NacosLoadBalancerConfig.class)
public class NacosLoadBalancerConfig {
@Resource
private NacosDiscoveryProperties nacosDiscoveryProperties;
@Bean
public ReactorLoadBalancer<ServiceInstance> nacosLoadBalancer(
Environment environment, LoadBalancerClientFactory loadBalancerClientFactory) {
String name = environment.getProperty("loadbalancer.client.name");
return new NacosLoadBalancer(
loadBalancerClientFactory.getLazyProvider(name,
ServiceInstanceListSupplier.class), name
,nacosDiscoveryProperties);
}
}
6.2 设置负载均衡器
@SpringBootApplication
@EnableFeignClients // 开启 Openfeign
// 设置全局的负载均衡策略
@LoadBalancerClients(defaultConfiguration =
NacosLoadBalancerConfig.class)
public class ConsumerApplication {
public static void main(String[] args) {
SpringApplication.run(ConsumerApplication.class, args);
}
}
7.自定义负载均衡器
实现自定义负载均衡策略需要以下 3步:
- 创建自定义负载均衡器
- 封装自定义负载均衡器
- 为服务设置自定义负载均衡器
7.1 创建自定义负载均衡器
package com.example.consumer.config;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletRequestWrapper;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.beans.factory.ObjectProvider;
import org.springframework.cloud.client.ServiceInstance;
import org.springframework.cloud.client.loadbalancer.DefaultResponse;
import org.springframework.cloud.client.loadbalancer.EmptyResponse;
import org.springframework.cloud.client.loadbalancer.Request;
import org.springframework.cloud.client.loadbalancer.Response;
import org.springframework.cloud.loadbalancer.core.*;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import reactor.core.publisher.Mono;
import java.util.List;
public class CustomLoadBalancer implements ReactorServiceInstanceLoadBalancer {
private static final Log log = LogFactory.getLog(RandomLoadBalancer.class);
private final String serviceId;
private ObjectProvider<ServiceInstanceListSupplier> serviceInstanceListSupplierProvider;
public CustomLoadBalancer(ObjectProvider<ServiceInstanceListSupplier> serviceInstanceListSupplierProvider, String serviceId) {
this.serviceId = serviceId;
this.serviceInstanceListSupplierProvider = serviceInstanceListSupplierProvider;
}
public Mono<Response<ServiceInstance>> choose(Request request) {
ServiceInstanceListSupplier supplier = (ServiceInstanceListSupplier)this.serviceInstanceListSupplierProvider.getIfAvailable(NoopServiceInstanceListSupplier::new);
return supplier.get(request).next().map((serviceInstances) -> {
return this.processInstanceResponse(supplier, serviceInstances);
});
}
private Response<ServiceInstance> processInstanceResponse(ServiceInstanceListSupplier supplier, List<ServiceInstance> serviceInstances) {
Response<ServiceInstance> serviceInstanceResponse = this.getInstanceResponse(serviceInstances);
if (supplier instanceof SelectedInstanceCallback && serviceInstanceResponse.hasServer()) {
((SelectedInstanceCallback)supplier).selectedServiceInstance((ServiceInstance)serviceInstanceResponse.getServer());
}
return serviceInstanceResponse;
}
private Response<ServiceInstance> getInstanceResponse(List<ServiceInstance> instances) {
if (instances.isEmpty()) {
if (log.isWarnEnabled()) {
log.warn("No servers available for service: " + this.serviceId);
}
return new EmptyResponse();
} else {
// 核心:自定义随机策略
// 获取 Request 对象
ServletRequestAttributes attributes = (ServletRequestAttributes)
RequestContextHolder.getRequestAttributes();
HttpServletRequest request = attributes.getRequest();
String ipAddress = request.getRemoteAddr();
System.out.println("用户 IP:" + ipAddress);
int hash = ipAddress.hashCode();
// 自定义负载均衡策略【这行代码是关键】
int index = hash % instances.size();
// 得到服务实例方法
ServiceInstance instance = (ServiceInstance) instances.get(index);
return new DefaultResponse(instance);
}
}
}
7.2 封装自定义负载均衡器
public class CustomLoadBalancerConfig {
@Bean
public ReactorLoadBalancer<ServiceInstance> customLoadBalancer(
Environment environment, LoadBalancerClientFactory loadBalancerClientFactory) {
String name = environment.getProperty("loadbalancer.client.name");
return new CustomLoadBalancer(
loadBalancerClientFactory.getLazyProvider(name,
ServiceInstanceListSupplier.class), name);
}
}
7.3 设置自定义负载均器
@SpringBootApplication
@EnableFeignClients // 开启 Openfeign
// 设置全局的负载均衡策略
@LoadBalancerClients(defaultConfiguration =
CustomLoadBalancerConfig.class)
public class ConsumerApplication {
public static void main(String[] args) {
SpringApplication.run(ConsumerApplication.class, args);
}
}
8.缓存
Spring Cloud LoadBalancer 在获取实例时有两种选择:
- 即时获取:每次从注册中心得到最新健康的实例,效果好、开销太大。
- 缓存服务列表:每次得到服务列表之后,缓存一段时间,这样既能保证性能,同时也能兼容一定的及时性。而 Spring Cloud LoadBalancer 中默认开启了缓存服务列表的功能。Spring
Cloud LoadBalancer 默认缓存的重要特性有两项:
- 缓存的过期时间为 35s。
- 缓存保存个数为 256 个
我们可以通过以下配置来改变这些配置:
关闭缓存
loadbalancer:
cache:
enabled: true # 关闭 loadbalancer 缓存
ttl: 10 # 缓存存活时间
capacity: 1000 # 缓存存储容量
9.执行原理
OpenFeign 底层是通过 HTTP 客户端对象 RestTemplate 实现接口请求的,而负载均衡器的作用只是在请求客户端发送请求之前,得到一个服务的地址给到 RestTemplate 对象,而 Spring Cloud LoadBalancer 的整体类图如下:
通过查看 Spring Cloud LoadBalancer 源码我们可以发现,@LoadBalanced 注解出 spring-cloud-commons 实现查看实现逻辑我们发现, spring-cloud-commons 存在自动配置类 LoadBalancerAutoConfiquration,当满足条件时将自动创建 LoadBalancerInterceptor 并注入到 RestTemplate 中,部分源码如下:
Spring Cloud LoadBalancer 是 Spring Cloud 提供的一种客户端负载均衡解决方案,用于替代 Netflix Ribbon。它通过将负载均衡逻辑从服务端移到客户端,使得每个客户端实例都可以独立地选择要调用的服务实例,从而实现更灵活和高效的负载均衡。
底层执行原理
Spring Cloud LoadBalancer 的核心组件包括 ServiceInstanceListSupplier
、LoadBalancerClient
和 LoadBalancer
。下面结合源码来详细说明其执行原理。
1. ServiceInstanceListSupplier
ServiceInstanceListSupplier
是一个接口,用于提供服务实例列表。它的实现类负责从服务注册中心(如 Eureka、Consul 等)获取可用的服务实例列表。
public interface ServiceInstanceListSupplier {
Flux<List<ServiceInstance>> get();
}
Flux
是 Reactor 库中的一个类,表示一个异步序列。ServiceInstanceListSupplier
的 get
方法返回一个 Flux
,它会异步地提供服务实例列表。
2. LoadBalancerClient
LoadBalancerClient
是一个接口,定义了负载均衡客户端的基本操作。它的主要方法是 choose
,用于选择一个服务实例。
public interface LoadBalancerClient {
<T> ServiceInstance choose(String serviceId, Request<T> request);
}
choose
方法接受服务 ID 和请求信息,返回一个 ServiceInstance
对象,表示选择的服务实例。
3. LoadBalancer
LoadBalancer
是负载均衡的核心接口,定义了负载均衡的策略。它的主要方法是 choose
,用于根据负载均衡策略选择一个服务实例。
public interface LoadBalancer<T> {
Mono<Response<T>> choose(Request request);
}
choose
方法返回一个 Mono<Response<T>>
,其中 Mono
是 Reactor 库中的另一个类,表示一个异步的单值序列。
执行流程
-
获取服务实例列表:
ServiceInstanceListSupplier
从服务注册中心获取可用的服务实例列表,并返回一个Flux<List<ServiceInstance>>
。
-
选择服务实例:
LoadBalancer
使用负载均衡策略(如轮询、随机等)从服务实例列表中选择一个服务实例。LoadBalancerClient
调用LoadBalancer
的choose
方法,获取选择的服务实例。
-
执行请求:
LoadBalancerClient
使用选择的服务实例执行请求,并返回结果。
源码示例
以下是一个简单的 ServiceInstanceListSupplier
实现示例:
public class SimpleServiceInstanceListSupplier implements ServiceInstanceListSupplier {
private final List<ServiceInstance> instances;
public SimpleServiceInstanceListSupplier(List<ServiceInstance> instances) {
this.instances = instances;
}
@Override
public Flux<List<ServiceInstance>> get() {
return Flux.just(instances);
}
}
以下是一个简单的 LoadBalancer
实现示例:
public class RoundRobinLoadBalancer implements LoadBalancer<ServiceInstance> {
private final AtomicInteger position;
private final ServiceInstanceListSupplier supplier;
public RoundRobinLoadBalancer(ServiceInstanceListSupplier supplier) {
this.supplier = supplier;
this.position = new AtomicInteger(0);
}
@Override
public Mono<Response<ServiceInstance>> choose(Request request) {
return supplier.get().next().map(instances -> {
if (instances.isEmpty()) {
return new EmptyResponse();
}
int pos = Math.abs(this.position.incrementAndGet());
ServiceInstance instance = instances.get(pos % instances.size());
return new DefaultResponse(instance);
});
}
}
总结
Spring Cloud LoadBalancer 通过 ServiceInstanceListSupplier
获取服务实例列表,通过 LoadBalancer
选择服务实例,并通过 LoadBalancerClient
执行请求。其核心思想是将负载均衡逻辑从服务端移到客户端,使得每个客户端实例都可以独立地选择要调用的服务实例,从而实现更灵活和高效的负载均衡。