赞
踩
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 则是绕不过去的必学知识。
在项目中添加 Spring Cloud OpenFeign 和注册中心如 Nacos 之后,再添加 Spring Cloud LoadBalancer 则会在进行接口调用时直接使用 Spring Cloud LoadBalancer。
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);
}
Spring Cloud LoadBalancer 内置了两种负载均衡策略
而要实现随机负载均衡策略的步骤如下:
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);
}
}
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);
}
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);
}
}
Nacos 中支持两种负载均衡器,一种是权重负载均衡器,另一种是第三方 CMDB(地域就近访问)标签负载均後器,我们可以将 Spring Cloud Loadbalancer 直接配置为 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);
}
}
@SpringBootApplication
@EnableFeignClients // 开启 Openfeign
// 设置全局的负载均衡策略
@LoadBalancerClients(defaultConfiguration =
NacosLoadBalancerConfig.class)
public class ConsumerApplication {
public static void main(String[] args) {
SpringApplication.run(ConsumerApplication.class, args);
}
}
实现自定义负载均衡策略需要以下 3步:
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);
}
}
}
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);
}
}
@SpringBootApplication
@EnableFeignClients // 开启 Openfeign
// 设置全局的负载均衡策略
@LoadBalancerClients(defaultConfiguration =
CustomLoadBalancerConfig.class)
public class ConsumerApplication {
public static void main(String[] args) {
SpringApplication.run(ConsumerApplication.class, args);
}
}
Spring Cloud LoadBalancer 在获取实例时有两种选择:
Cloud LoadBalancer 默认缓存的重要特性有两项:
loadbalancer:
cache:
enabled: true # 关闭 loadbalancer 缓存
ttl: 10 # 缓存存活时间
capacity: 1000 # 缓存存储容量
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
。下面结合源码来详细说明其执行原理。
ServiceInstanceListSupplier
ServiceInstanceListSupplier
是一个接口,用于提供服务实例列表。它的实现类负责从服务注册中心(如 Eureka、Consul 等)获取可用的服务实例列表。
public interface ServiceInstanceListSupplier {
Flux<List<ServiceInstance>> get();
}
Flux
是 Reactor 库中的一个类,表示一个异步序列。ServiceInstanceListSupplier
的 get
方法返回一个 Flux
,它会异步地提供服务实例列表。
LoadBalancerClient
LoadBalancerClient
是一个接口,定义了负载均衡客户端的基本操作。它的主要方法是 choose
,用于选择一个服务实例。
public interface LoadBalancerClient {
<T> ServiceInstance choose(String serviceId, Request<T> request);
}
choose
方法接受服务 ID 和请求信息,返回一个 ServiceInstance
对象,表示选择的服务实例。
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
执行请求。其核心思想是将负载均衡逻辑从服务端移到客户端,使得每个客户端实例都可以独立地选择要调用的服务实例,从而实现更灵活和高效的负载均衡。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。