在微服务架构中,服务的高可用性和动态扩展能力是核心需求。随着服务实例数量的增加,如何高效地分配流量、避免单点过载,成为系统设计的关键挑战。负载均衡(Load Balancing) 技术通过将请求合理分发到多个服务实例,成为解决这一问题的核心手段。
Spring Cloud Alibaba 生态中的 Nacos(Naming and Configuration Service)不仅提供了服务注册与发现、动态配置管理等能力,还深度集成了负载均衡功能。Nacos 的负载均衡机制基于 Spring Cloud LoadBalancer(替代了早期的 Ribbon),支持多种默认策略(如轮询、随机、权重),同时也允许开发者根据业务需求自定义负载均衡逻辑。
Nacos 的核心功能之一是服务注册与发现。其工作流程如下:
Spring Cloud 的负载均衡分为两个层级:
本文聚焦于 客户端负载均衡,其优势在于减少中间层开销,提升响应速度。
Spring Cloud LoadBalancer 是 Spring Cloud 官方提供的负载均衡器,替代了 Netflix Ribbon,其核心组件包括:
ServiceInstanceListSupplier
:负责从服务注册中心(如 Nacos)获取服务实例列表。ReactorLoadBalancer
:基于 Reactor 的负载均衡器,支持异步选择实例。LoadBalancerClient
:客户端接口,用于执行负载均衡请求。Spring Cloud LoadBalancer 默认支持以下策略:
在 application.yml
中指定全局负载均衡策略:
spring:
cloud:
loadbalancer:
configurations: round-robin # 可选:random, weighted
通过 @LoadBalancerClient
注解为特定服务指定策略:
@Configuration
@LoadBalancerClient(
name = "service-provider",
configuration = CustomLoadBalancerConfig.class
)
public class LoadBalancerConfig {
}
假设业务需要实现以下自定义策略:
实现 ReactorLoadBalancer
接口,自定义实例选择逻辑:
public class LatencyBasedLoadBalancer implements ReactorLoadBalancer<ServiceInstance> {
private final String serviceId;
private final ServiceInstanceListSupplier supplier;
private final Map<ServiceInstance, Long> latencyMap = new ConcurrentHashMap<>();
public LatencyBasedLoadBalancer(ServiceInstanceListSupplier supplier, String serviceId) {
this.supplier = supplier;
this.serviceId = serviceId;
}
@Override
public Mono<Response<ServiceInstance>> choose(Request request) {
return supplier.get().next()
.map(instances -> {
if (instances.isEmpty()) {
return new EmptyResponse();
}
// 计算延迟最低的实例
ServiceInstance bestInstance = instances.stream()
.min(Comparator.comparingLong(instance ->
latencyMap.getOrDefault(instance, Long.MAX_VALUE)))
.orElseThrow();
return new DefaultResponse(bestInstance);
});
}
// 更新实例的延迟数据(可通过拦截器或过滤器实现)
public void updateLatency(ServiceInstance instance, long latency) {
latencyMap.put(instance, latency);
}
}
通过 @Bean
注入自定义实现:
@Configuration
public class LoadBalancerConfiguration {
@Bean
public ReactorLoadBalancer<ServiceInstance> customLoadBalancer(
Environment environment,
LoadBalancerClientFactory clientFactory) {
String serviceId = environment.getProperty(LoadBalancerClientFactory.PROPERTY_NAME);
return new LatencyBasedLoadBalancer(
clientFactory.getLazyProvider(serviceId, ServiceInstanceListSupplier.class),
serviceId);
}
}
通过 RestTemplate
拦截器记录请求延迟:
@Bean
public RestTemplate restTemplate() {
RestTemplate restTemplate = new RestTemplate();
restTemplate.getInterceptors().add((request, body, execution) -> {
long startTime = System.currentTimeMillis();
ClientHttpResponse response = execution.execute(request, body);
long latency = System.currentTimeMillis() - startTime;
// 获取目标实例并更新延迟
URI uri = request.getURI();
ServiceInstance instance = new DefaultServiceInstance(
uri.getHost(), uri.getHost(), uri.getPort(), false);
LoadBalancer loadBalancer = applicationContext.getBean(LoadBalancer.class);
loadBalancer.updateLatency(instance, latency);
return response;
});
return restTemplate;
}
在 application.yml
中指定使用自定义负载均衡器:
spring:
cloud:
loadbalancer:
enabled: true
clients:
service-provider:
configuration: custom-loadbalancer-config
在 Nacos 控制台或通过 API 为实例设置权重(0~1):
curl -X PUT "http://localhost:8848/nacos/v1/ns/instance?serviceName=service-provider&ip=192.168.1.1&port=8081&weight=0.7"
在自定义负载均衡器中,结合实例权重分配流量:
public class WeightedLoadBalancer implements ReactorLoadBalancer<ServiceInstance> {
@Override
public Mono<Response<ServiceInstance>> choose(Request request) {
return supplier.get().next()
.map(instances -> {
double totalWeight = instances.stream()
.mapToDouble(instance -> instance.getMetadata().getOrDefault("weight", "1.0"))
.sum();
double random = Math.random() * totalWeight;
double current = 0;
for (ServiceInstance instance : instances) {
double weight = Double.parseDouble(instance.getMetadata().getOrDefault("weight", "1.0"));
current += weight;
if (random < current) {
return new DefaultResponse(instance);
}
}
return new EmptyResponse();
});
}
}
在服务注册时,通过 Nacos 元数据标记实例所属环境:
# 本地环境服务配置 (application-local.yml)
spring:
cloud:
nacos:
discovery:
metadata:
environment: local # 环境标识
weight: 5 # 权重值(本地最高)
# 开发环境服务配置 (application-dev.yml)
spring:
cloud:
nacos:
discovery:
metadata:
environment: dev
weight: 3
# 生产环境服务配置 (application-prod.yml)
spring:
cloud:
nacos:
discovery:
metadata:
environment: prod
weight: 1
实现基于环境权重的负载均衡逻辑:
public class EnvironmentWeightedLoadBalancer implements ReactorLoadBalancer<ServiceInstance> {
// 环境权重映射(本地 > 开发 > 生产)
private static final Map<String, Integer> ENVIRONMENT_WEIGHTS = Map.of(
"local", 5,
"dev", 3,
"prod", 1
);
private final String serviceId;
private final ServiceInstanceListSupplier supplier;
public EnvironmentWeightedLoadBalancer(ServiceInstanceListSupplier supplier, String serviceId) {
this.supplier = supplier;
this.serviceId = serviceId;
}
@Override
public Mono<Response<ServiceInstance>> choose(Request request) {
return supplier.get().next()
.map(instances -> {
if (instances.isEmpty()) {
return new EmptyResponse();
}
// 计算总权重
int totalWeight = instances.stream()
.mapToInt(instance ->
ENVIRONMENT_WEIGHTS.getOrDefault(
instance.getMetadata().get("environment"),
1 // 默认权重
)
)
.sum();
// 按权重随机选择
int randomWeight = new Random().nextInt(totalWeight);
int current = 0;
for (ServiceInstance instance : instances) {
int weight = ENVIRONMENT_WEIGHTS.getOrDefault(
instance.getMetadata().get("environment"),
1
);
current += weight;
if (randomWeight < current) {
return new DefaultResponse(instance);
}
}
return new EmptyResponse();
});
}
}
通过配置类绑定策略到目标服务:
@Configuration
@LoadBalancerClient(
name = "target-service", // 目标服务名
configuration = EnvironmentLoadBalancerConfig.class
)
public class EnvironmentLoadBalancerConfig {
@Bean
public ReactorLoadBalancer<ServiceInstance> reactorLoadBalancer(
Environment environment,
LoadBalancerClientFactory clientFactory
) {
String serviceId = environment.getProperty(LoadBalancerClientFactory.PROPERTY_NAME);
return new EnvironmentWeightedLoadBalancer(
clientFactory.getLazyProvider(serviceId, ServiceInstanceListSupplier.class),
serviceId
);
}
}
@LoadBalanced
注解已添加到 RestTemplate
或 WebClient
。@LoadBalancerClient
的 configuration
属性是否指向正确配置类。