1.什么是 LoadBalancer?
负载均衡器(LoadBalancer)是一种用于在计算机网络或服务器集群中分配工作负载的设备或服务。就是确保每个服务器都能够有效地处理请求,防止某个服务器过载而导致性能下降或服务中断。
- 服务器端的负载均衡。
- 客户端负载均衡,如
SpringCloudLoadBalancer
等。
1.1 负载均衡的策略
不管是服务器端还是客户端,它们的负载均衡策略都是相同的,下面是一些常见的策略。
- 轮询:按照顺序将每个新的请求分发给后端服务器,依次循环。这种策略适用于服务器性能相近,且请求处理时间相近的情况。
- 最小连接数:将新的请求分发到当前连接数最少的服务器上,以确保负载更均衡。
- 随机选择:随机选择一个后端服务器来处理每个新的请求。这种策略适用于服务器性能相似,且请求处理时间相近的情况,但不保证请求的分发是均匀的。
- 加权轮询:根据服务器的不同处理能力,给每个服务器分配不同的权重,使其能够接受相应权重数的请求。这种策略能确保高性能的服务器得到更多的使用率,避免低性能的服务器负载过重。
- 最短响应时间:将请求发送到具有最短响应时间的服务器上,以提高整体性能。
- IP 哈希:它使用客户端的 IP 地址信息来决定将请求分发到哪个服务器。对于给定的 IP 地址,通过哈希函数计算得到一个数值,然后根据这个数值将请求发送到相应的服务器上。
2.LoadBalancer默认的均衡策略
LoadBalancer
的负载均衡策略默认的是 轮询策略。
2.1 代码演示
-
创建
comsumer
模块和一个provider
模块,开启OpenFeign
并连接Nacos
【详细步骤:Nacos + OpenFeign:微服务中的服务注册、发现与调用 - 掘金 (juejin.cn)】。- provider模块:
java@RequestMapping("/user") @RestController public class UserController { @Autowired private ServletWebServerApplicationContext context; @RequestMapping("/getname") public String getName(@RequestParam("id")Integer id){ //返回当前的端口号 + id return "Port:" + context.getWebServer().getPort() + " Provider-id: " + id; } }
- comsumer模块:
java@RestController public class CallController { @Autowired private UserService userService; @RequestMapping("/getname") public String getName(Integer id){ return userService.getName(id); } }
java@Service @FeignClient("loadbalancer-service") public interface UserService { @RequestMapping("/user/getname") //调用 provider 模块的服务 String getName(@RequestParam("id")Integer id); }
- 多复制几个
provider
模块的配置:
全部启动,然后访问comsumer
。
刷新多次,就可以体现出轮询的策略。
2.2 源码分析
从 Spring Cloud LoadBalancer
的配置类 LoadBalancerClientConfiguration
中可以看到它的默认策略:
这个方法就是用来配置策略的,来看看ReactorLoadBalancer
有哪些实现(Ctrl+H):
所以,Spring Cloud LoadBalancer
内置了两种负载均衡策略:轮询负载均衡策略(默认) 、随机负载均衡策略 。NacosLoadBalancer
是基于权重负载均衡策略 ,是由alibaba
提供的。
3.设置随机负载策略
- 创建随机负载均衡器:这里跟
LoadBalancerClientConfiguration
中的源码一样,就是返回的类型变成了RandomLoadBalancer
。
java
//注意,这里不能加 @Configuration !!!
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);
}
}
- 设置随机负载均衡器 (局部设置) :设置位置在
OpenFeign
接口定义上。
java
@Service
@FeignClient("loadbalancer-service")
//name 为应用程序的名称(在 application.yml 配置文件中设置过)
//configuration 为自定义配置类的 Class 类
@LoadBalancerClient(name = "loadbalancer-service",configuration = RandomLoadBalancerConfig.class)
public interface UserService {
@RequestMapping("/user/getname")
String getName(@RequestParam("id")Integer id);
}
- 设置随机负载均衡器 (全局设置) :在启动类里面设置。
java
@SpringBootApplication
@EnableFeignClients
//全局设置
@LoadBalancerClients(defaultConfiguration = RandomLoadBalancerConfig.class)
public class ComsumerApplication {
public static void main(String[] args) {
SpringApplication.run(ComsumerApplication.class, args);
}
}
局部与全局的区别:局部负载均衡策略是指仅对特定的服务或服务实例应用负载均衡策略。全局负载均衡策略是指将负载均衡策略应用于所有服务或服务实例。
4.设置权重负载均衡策略(Nacos负载均衡器)
- 创建
Nacos
负载均衡器
java
@LoadBalancerClients(defaultConfiguration = NacosLoadBalancerConfig.class)
public class NacosLoadBalancerConfig {
@Resource
private NacosDiscoveryProperties nacosDiscoveryProperties; //这个类主要包含了与 Nacos 相关的配置信息
@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); //这里多了一个参数
}
}
- 设置随机负载均衡器 (局部设置):
java
@Service
@FeignClient("loadbalancer-service")
@LoadBalancerClient(name = "loadbalancer-service",configuration = NacosLoadBalancerConfig.class)
public interface UserService {
@RequestMapping("/user/getname")
String getName(@RequestParam("id")Integer id);
}
- 设置随机负载均衡器 (全局设置) :在启动类里面设置。
java
@SpringBootApplication
@EnableFeignClients
@LoadBalancerClients(defaultConfiguration = NacosLoadBalancerConfig.class)
public class ComsumerApplication {
public static void main(String[] args) {
SpringApplication.run(ComsumerApplication.class, args);
}
}
5.自定义负载均衡器
5.1 从零创建自定义负载均衡器
自定义负载均衡器可能很复杂,但是我们可以照猫画虎,这里模拟一个 IP 哈希策略,可以借鉴RandomLoadBalancer
等均衡器来实现。我们先看看RandomLoadBalancer
源码是怎么写的。
RandomLoadBalancer
实现了ReactorServiceInstanceLoadBalancer
接口,这个接口要实现choose
方法:
这个choose
方法就是用来实现选择服务实例的逻辑 了。这里就不实现了,直接把RandomLoadBalancer
中的所有代码复制过去(看源码的时候可以借助GPT):
java
public class HashLoadBalancer implements ReactorServiceInstanceLoadBalancer {
private static final Log log = LogFactory.getLog(RandomLoadBalancer.class);
private final String serviceId;
private ObjectProvider<ServiceInstanceListSupplier> serviceInstanceListSupplierProvider;
public HashLoadBalancer(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) -> {
// 处理选择的服务实例,返回一个 Response 对象
return this.processInstanceResponse(supplier, serviceInstances);
});
}
private Response<ServiceInstance> processInstanceResponse(ServiceInstanceListSupplier supplier, List<ServiceInstance> serviceInstances) {
// 通过服务实例列表获取 Response 对象
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 {
// 从服务实例列表中随机选择一个实例【这是核心,更改这里我们可以实现自定义负载策略】
//int index = ThreadLocalRandom.current().nextInt(instances.size());
//ServiceInstance instance = (ServiceInstance)instances.get(index);
// 返回一个包含选定服务实例的响应对象
return new DefaultResponse(instance);
}
}
}
可以看出来,getInstanceResponse
是实现策略的逻辑,我们只需要更改getInstanceResponse
方法就行了。
java
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(); //ip地址
int hash = ipAddress.hashCode();
int index = hash % instances.size();//计算下标
ServiceInstance instance = instances.get(index);//获取对应的实例
// 返回一个包含选定服务实例的响应对象
return new DefaultResponse(instance);
}
}
完整的代码:
java
public class HashLoadBalancer implements ReactorServiceInstanceLoadBalancer {
private static final Log log = LogFactory.getLog(RandomLoadBalancer.class);
private final String serviceId;
private ObjectProvider<ServiceInstanceListSupplier> serviceInstanceListSupplierProvider;
public HashLoadBalancer(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) -> {
// 处理选择的服务实例,返回一个 Response 对象
return this.processInstanceResponse(supplier, serviceInstances);
});
}
private Response<ServiceInstance> processInstanceResponse(ServiceInstanceListSupplier supplier, List<ServiceInstance> serviceInstances) {
// 通过服务实例列表获取 Response 对象
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(); //ip地址
int hash = ipAddress.hashCode();
int index = hash % instances.size();//计算下标
ServiceInstance instance = instances.get(index);//获取对应的实例
// 返回一个包含选定服务实例的响应对象
return new DefaultResponse(instance);
}
}
}
5.2 设置自定义负载均衡器
同样的道理,直接复制。
java
public class HashLoadBalancerConfig {
@Bean
public ReactorLoadBalancer<ServiceInstance> hashLoadBalancer(Environment environment, LoadBalancerClientFactory loadBalancerClientFactory) {
String name = environment.getProperty("loadbalancer.client.name");
return new HashLoadBalancer(loadBalancerClientFactory.getLazyProvider(name, ServiceInstanceListSupplier.class), name);
}
}
这里只演示局部设置,全局设置与上面是一样的。
java
@Service
@FeignClient("loadbalancer-service")
@LoadBalancerClient(name = "loadbalancer-service",configuration = HashLoadBalancerConfig.class)
public interface UserService {
@RequestMapping("/user/getname")
String getName(@RequestParam("id")Integer id);
}
6.设置 LoadBalancer 缓存
LoadBalancer
缓存指的是负载均衡器在运行时缓存的服务实例信息,包括服务的主机名、端口、状态、权重等。会缓存负载均衡器已知的服务实例列表,这些实例可以是同一服务的不同节点,用于分发请求。
LoadBalancer
默认是开启了缓存服务列表的功能的,其中过期时间默认为 35s(每35s向注册中心获取列表),最大缓存个数为 256个。
设置缓存配置:
yml
spring:
cloud:
loadbalancer:
cache:
enabled: false #关闭缓存(为 true 时,表示开启,默认为 true)
ttl: 20 # 缓存存活时间
capacity: 300 # 缓存的最大个数