SpringCloud--负载均衡
一、背景
在微服务架构中,服务通常有多个实例,负载均衡的作用是将请求分发到多个服务实例上,以达到高可用和扩展性的目的。Spring Cloud 提供了多种负载均衡的实现方式,主要分为客户端负载均衡和服务端负载均衡。在Spring Cloud中,我们通常使用的是客户端负载均衡,即Ribbon(虽然Ribbon已经进入维护模式,但很多项目仍在用)以及Spring Cloud LoadBalancer(Spring官方推出的替代方案)。
-
服务端负载均衡
- 概念:在服务消费者和服务提供者之间有一个独立的负载均衡服务器(如Nginx、HAProxy、F5)。
- 工作流程:消费者直接请求负载均衡器,由负载均衡器将请求转发给后端的某个服务实例。
- 优点:对消费者透明,集中管理。
- 缺点:可能成为性能瓶颈和单点故障点。
-
客户端负载均衡
- 概念:负载均衡的逻辑集成在服务消费者内部。
- 工作流程:
- 服务消费者从服务注册中心(如Nacos、Eureka)获取所有提供者的地址列表。
- 消费者内置的负载均衡器(如Ribbon、Spring Cloud LoadBalancer)根据特定算法(如轮询、随机、最小连接数等),直接从列表中选择一个实例。
- 消费者直接向选中的实例发起请求。
- 优点:
- 去中心化:没有中间代理,性能更高,避免了单点瓶颈。
- 更智能:客户端可以结合本地信息(如服务健康状况、区域距离)做出更灵活的路由决策。
Spring Cloud中负载均衡主要有两种实现:Ribbon和Spring Cloud LoadBalancer。
二、核心概念
- ServiceInstance: 代表一个服务实例,包含实例的元数据,如主机名、端口、是否安全等。
- ServiceInstanceListSupplier: 这是 服务实例列表提供者。它的职责是从服务注册中心(如 Nacos)获取指定服务的所有健康实例列表。这是负载均衡的数据来源。
- LoadBalancerClient: 负载均衡客户端,用于从服务实例列表中选择一个实例,并执行请求。
- ReactorLoadBalancer: 这是响应式编程模型下的负载均衡器接口,也是当前 LoadBalancerClient 的默认实现(BlockingLoadBalancerClient 内部委托给它)。它定义了选择实例的核心方法。
- LoadBalancerRequestFactory: 用于创建 LoadBalancerRequest,这个请求对象封装了实际的调用逻辑。
- LoadBalancerRequestTransformer: 请求转换器,可以在实际请求发出前对请求(如 RestTemplate 的 HttpRequest 或 WebClient 的 ClientRequest)进行修改,例如添加特定的 Header。
三、实现原理
以最经典的 @LoadBalanced + RestTemplate 为例,详细拆解其工作流程:
1.启动阶段:装配与初始化
- 注解启用: 在配置类上使用 @LoadBalanced 注解修饰一个 RestTemplate 的 Bean。这个注解引入了一个 LoadBalancerAutoConfiguration 配置类。
- 自动配置: Spring Cloud 的自动配置机制会为这个特殊的 RestTemplate 添加一个 LoadBalancerInterceptor 拦截器。
- 组件装配: Spring Cloud 会根据你的依赖(例如 spring-cloud-starter-loadbalancer 和 spring-cloud-starter-alibaba-nacos-discovery)自动装配好 LoadBalancerClient(通常是 BlockingLoadBalancerClient)、ServiceInstanceListSupplier(如 NacosServiceInstanceListSupplier)和 ReactorLoadBalancer(如 RoundRobinLoadBalancer)等组件。
2.调用阶段:拦截与执行
当你使用 @LoadBalanced 的 RestTemplate 发起一个包含服务名的请求时(例如 restTemplate.getForObject(“http://user-service/api/v1/user/1”, User.class)),整个流程如下:
- 拦截请求:LoadBalancerInterceptor 会拦截这次 HTTP 调用。它从请求的 URI 中提取出服务名 user-service。
- 调用 LoadBalancerClient:拦截器调用 LoadBalancerClient.execute(serviceId, request) 方法。
- 选择服务实例:BlockingLoadBalancerClient 内部会委托给 ReactorLoadBalancer 进行实例选择。
- 获取实例列表: ReactorLoadBalancer 调用 ServiceInstanceListSupplier.get() 方法。ServiceInstanceListSupplier 会与 Nacos Server 通信(或从本地缓存中),获取所有健康的、名为 user-service 的服务实例列表,例如:[192.168.1.101:8080, 192.168.1.102:8080, 192.168.1.103:8080]。
- 应用负载均衡算法: ReactorLoadBalancer 接收到实例列表后,会应用配置的负载均衡算法(如轮询),从列表中选出一个实例(例如 192.168.1.102:8080)。
- 重构 URI:LoadBalancerClient 使用选中的 ServiceInstance 信息,将原始的 URI http://user-service/api/v1/user/1 重构为具体的实例地址 http://192.168.1.102:8080/api/v1/user/1。
- 发起实际调用:LoadBalancerClient 使用 LoadBalancerRequestFactory 创建的 LoadBalancerRequest 对象,通过 ClientHttpRequestInterceptor 链(其中可能包括你配置的 LoadBalancerRequestTransformer)来最终发起一个真正的 HTTP 请求到 http://192.168.1.102:8080/api/v1/user/1。
- 处理响应:将得到的响应返回给 RestTemplate,最终返回给调用者。
四、Spring Cloud 中实现负载均衡的方式
在Spring Cloud中,实现负载均衡的方式主要有以下几种:
使用Ribbon(已进入维护模式,但仍在广泛使用)
- 使用步骤:
- 添加依赖:在服务消费者项目的pom.xml中添加Ribbon依赖(通常通过引入spring-cloud-starter-netflix-ribbon)。
<dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-ribbon</artifactId> </dependency> - 启用负载均衡:在配置类中,创建一个RestTemplate的bean,并加上@LoadBalanced注解。
@Configuration public class RestTemplateConfig { @Bean @LoadBalanced // 关键注解:启用负载均衡 public RestTemplate restTemplate() { return new RestTemplate(); } } - 配置Ribbon的负载均衡策略:在application.yml中配置。
service-provider: ribbon: NFLoadBalancerRuleClassName: com.netflix.loadbalancer.RandomRule - 通过服务名调用:在需要调用其他服务的地方,注入RestTemplate,然后使用服务名(而不是具体的IP和端口)进行调用。
@Service public class MyService { @Autowired private RestTemplate restTemplate; public String callOtherService() { // 使用服务名(例如"service-provider")代替具体的地址 String result = restTemplate.getForObject("http://service-provider/endpoint", String.class); return result; } }
- 添加依赖:在服务消费者项目的pom.xml中添加Ribbon依赖(通常通过引入spring-cloud-starter-netflix-ribbon)。
使用Spring Cloud LoadBalancer(Spring官方推出的替代方案)
- 使用步骤:
- 添加依赖:在服务消费者项目的pom.xml中添加Spring Cloud LoadBalancer依赖。
<dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-loadbalancer</artifactId> </dependency> - 配置负载均衡策略:Spring Cloud LoadBalancer默认提供了RoundRobinLoadBalancer(轮询)和RandomLoadBalancer(随机)两种策略。可以通过配置选择策略,也可以自定义策略。
@Configuration public class LoadBalancerConfig { @Bean public ReactorLoadBalancer<ServiceInstance> randomLoadBalancer(Environment environment, LoadBalancerClientFactory loadBalancerClientFactory) { String name = environment.getProperty(LoadBalancerClientFactory.PROPERTY_NAME); return new RandomLoadBalancer(loadBalancerClientFactory.getLazyProvider(name, ServiceInstanceListSupplier.class), name); } } - 使用LoadBalancerClient:可以直接注入LoadBalancerClient来手动选择服务实例。
@Service public class MyService { @Autowired private LoadBalancerClient loadBalancerClient; public void doSomething() { ServiceInstance instance = loadBalancerClient.choose("service-provider"); String url = instance.getUri() + "/endpoint"; // 然后使用RestTemplate或WebClient等发送请求 } }
- 添加依赖:在服务消费者项目的pom.xml中添加Spring Cloud LoadBalancer依赖。
使用OpenFeign(声明式的REST客户端,默认集成了负载均衡)
- 使用步骤:
- 添加依赖:在pom文件中添加OpenFeign的依赖。
<dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-openfeign</artifactId> </dependency> - 启用Feign客户端:在启动类上添加@EnableFeignClients注解。
@SpringBootApplication @EnableFeignClients public class Application { public static void main(String[] args) { SpringApplication.run(Application.class, args); } } - 定义Feign客户端接口:定义一个接口,使用@FeignClient注解,并指定服务名。
@FeignClient(name = "service-provider") public interface ServiceProviderClient { @GetMapping("/endpoint") String callEndpoint(); } - 使用Feign客户端:在需要的地方注入这个接口,并调用方法。
@Service public class MyService { @Autowired private ServiceProviderClient serviceProviderClient; public String callOtherService() { return serviceProviderClient.callEndpoint(); } }
- 添加依赖:在pom文件中添加OpenFeign的依赖。
五、负载均衡算法
Spring Cloud 提供了多种负载均衡算法,主要通过 Ribbon 和 Spring Cloud LoadBalancer 两个组件来实现。
5.1 Ribbon提供了以下常见的内置策略:
- RoundRobinRule:轮询算法,按顺序依次选择服务实例。
- RandomRule:随机算法,随机选择服务实例。
- WeightedResponseTimeRule:权重算法,根据响应时间计算权重,响应时间越短,权重越大,被选中的概率越高。
- BestAvailableRule:最小连接数算法,先过滤掉故障实例,然后选择并发请求最小的实例。
- RetryRule:重试算法,先按照轮询算法选择实例,如果选择实例失败,则在指定时间内重试,选择可用的实例。
- AvailabilityFilteringRule:先过滤掉故障实例和并发请求超过阈值的实例,然后轮询选择。
- ZoneAvoidanceRule:默认规则,综合判断服务器所在区域的性能和服务的可用性,轮询选择。
配置Ribbon负载均衡算法:
可以通过新建配置类或者application.yml配置文件进行负载均衡器的设置。
-
通过配置类设置:
@Configuration public class RibbonLoadBalancerConfig { /** * 全局负载均衡配置 */ @Bean public IRule ribbonRule() { // 返回自定义的负载均衡规则 return new WeightedResponseTimeRule(); } /** * 特定服务的负载均衡配置 */ @RibbonClient(name = "user-service", configuration = UserServiceRibbonConfig.class) public class UserServiceRibbonConfig { @Bean public IRule ribbonRule() { // 为用户服务配置随机算法 return new RandomRule(); } } @RibbonClient(name = "product-service", configuration = ProductServiceRibbonConfig.class) public class ProductServiceRibbonConfig { @Bean public IRule ribbonRule() { // 为商品服务配置轮询算法 return new RoundRobinRule(); } } } -
通过配置文件设置:在application.yml中配置:对于名为user-service的服务,配置随机算法:
user-service: ribbon: # 负载均衡算法配置 NFLoadBalancerRuleClassName: com.netflix.loadbalancer.RoundRobinRule # NFLoadBalancerRuleClassName: com.netflix.loadbalancer.RandomRule # NFLoadBalancerRuleClassName: com.netflix.loadbalancer.WeightedResponseTimeRule # NFLoadBalancerRuleClassName: com.netflix.loadbalancer.BestAvailableRule # 连接超时 ConnectTimeout: 2000 # 读取超时 ReadTimeout: 5000 # 最大重试次数 MaxAutoRetries: 1 # 最大重试下一个服务次数 MaxAutoRetriesNextServer: 1 # 是否对所有操作重试 OkToRetryOnAllOperations: false
5.2 Spring Cloud LoadBalancer 负载均衡算法
Spring Cloud LoadBalancer默认提供了两种负载均衡策略:
- RoundRobinLoadBalancer:轮询算法
- RandomLoadBalancer:随机算法
配置Spring Cloud LoadBalancer负载均衡算法:
可以通过新建配置类或者application.yml配置文件进行负载均衡器的设置。
- 通过配置类设置:全局的随机负载均衡策略
@Configuration @LoadBalancerClient(name = "user-service", configuration = LoadBalancerConfiguration.class) public class LoadBalancerConfiguration { @Bean public ReactorLoadBalancer<ServiceInstance> randomLoadBalancer(Environment environment, LoadBalancerClientFactory loadBalancerClientFactory) { String name = environment.getProperty(LoadBalancerClientFactory.PROPERTY_NAME); // 返回随机负载均衡策略 return new RandomLoadBalancer(loadBalancerClientFactory.getLazyProvider(name, ServiceInstanceListSupplier.class), name); } } - 通过配置文件设置
spring: cloud: loadbalancer: # 全局配置 cache: enabled: true capacity: 256 health-check: initial-delay: 2s interval: 30s # 特定服务配置 configurations: user-service: # 使用随机算法 name: random product-service: # 使用轮询算法 name: round_robin
5.3 自定义负载均衡策略
如果内置的算法不满足需求,我们可以自定义负载均衡算法。以Spring Cloud LoadBalancer为例,自定义一个权重负载均衡算法:
- 实现ReactorLoadBalancer接口
- 在配置中注册自定义的负载均衡器
示例:自定义权重负载均衡算法
public class WeightedLoadBalancer implements ReactorLoadBalancer<ServiceInstance> {
private final ObjectProvider<ServiceInstanceListSupplier> serviceInstanceListSupplierProvider;
private final String serviceId;
public WeightedLoadBalancer(ObjectProvider<ServiceInstanceListSupplier> serviceInstanceListSupplierProvider, String serviceId) {
this.serviceInstanceListSupplierProvider = serviceInstanceListSupplierProvider;
this.serviceId = serviceId;
}
@Override
public Mono<Response<ServiceInstance>> choose(Request request) {
ServiceInstanceListSupplier supplier = serviceInstanceListSupplierProvider.getIfAvailable();
return supplier.get().next().map(serviceInstances -> {
// 根据权重选择实例
ServiceInstance instance = chooseInstance(serviceInstances);
if (instance != null) {
return new DefaultResponse(instance);
}
return new EmptyResponse();
});
}
private ServiceInstance chooseInstance(List<ServiceInstance> instances) {
// 计算总权重
int totalWeight = 0;
for (ServiceInstance instance : instances) {
int weight = getWeight(instance);
totalWeight += weight;
}
// 随机一个数
int random = new Random().nextInt(totalWeight);
int current = 0;
for (ServiceInstance instance : instances) {
current += getWeight(instance);
if (random < current) {
return instance;
}
}
return instances.get(0);
}
private int getWeight(ServiceInstance instance) {
// 从元数据中获取权重,默认权重为1
String weightStr = instance.getMetadata().get("weight");
if (weightStr != null) {
return Integer.parseInt(weightStr);
}
return 1;
}
}
然后在配置类中注册这个负载均衡器:
@Configuration
@LoadBalancerClient(name = "user-service", configuration = CustomLoadBalancerConfiguration.class)
public class CustomLoadBalancerConfiguration {
@Bean
public ReactorLoadBalancer<ServiceInstance> weightedLoadBalancer(Environment environment, LoadBalancerClientFactory loadBalancerClientFactory) {
String name = environment.getProperty(LoadBalancerClientFactory.PROPERTY_NAME);
// 返回自定义的负载均衡器
return new WeightedLoadBalancer(loadBalancerClientFactory.getLazyProvider(name, ServiceInstanceListSupplier.class), name);
}
}
六、与其他组件集成
-
与 OpenFeign 的集成
OpenFeign 是一个声明式的 HTTP 客户端,它与 Spring Cloud LoadBalancer 的集成是 无缝且默认的。- 当你声明一个 Feign 客户端(如 @FeignClient(“user-service”))时,Spring Cloud 会自动为其配置一个负载均衡器。
- Feign 在底层会调用 LoadBalancerClient.choose(“user-service”) 来选取实例,然后构造并发送 HTTP 请求。
- 其内部原理与 RestTemplate 类似,但实现细节被 Feign 的框架所封装。
-
与 Spring Cloud Gateway 的集成
在 API 网关中,负载均衡同样至关重要。当在 Gateway 的路由配置中使用了 lb://user-service 这样的 URI 时:spring: cloud: gateway: routes: - id: user-service-route uri: lb://user-service # 使用 lb 协议,表示需要负载均衡 predicates: - Path=/api/v1/user/**- Gateway 会识别 lb 协议。
- 它内部的 ReactiveLoadBalancerClientFilter 会拦截请求。
- 该过滤器调用 ReactorLoadBalancer 来为 user-service 选择一个实例。
- 然后将请求转发到选中的具体实例上。
更多推荐
所有评论(0)