Spring Cloud 负载均衡详解
一、知识概述
负载均衡是微服务架构中的关键技术,它将请求分发到多个服务实例上,提高系统的吞吐量和可用性。Spring Cloud 提供了多种负载均衡解决方案,包括 Ribbon(已维护模式)和 Spring Cloud LoadBalancer。
负载均衡的核心功能:
- 请求分发:将请求分发到多个实例
- 实例选择:根据策略选择合适的实例
- 健康检查:剔除不健康的实例
- 重试机制:失败后重试其他实例
理解负载均衡的原理和配置,是构建高可用微服务系统的重要技能。
二、知识点详细讲解
2.1 负载均衡分类
服务端负载均衡
客户端 → 负载均衡器(Nginx/F5) → 服务实例1
→ 服务实例2
→ 服务实例3
特点:
- 集中管理
- 需要独立部署
- 有单点故障风险
客户端负载均衡
客户端(内置负载均衡)→ 服务实例1
→ 服务实例2
→ 服务实例3
特点:
- 去中心化
- 无需独立部署
- 客户端逻辑复杂
2.2 负载均衡算法
| 算法 | 说明 | 适用场景 |
|---|---|---|
| 轮询 | 依次访问每个实例 | 实例性能相近 |
| 随机 | 随机选择实例 | 实例性能相近 |
| 加权轮询 | 按权重轮询 | 实例性能不同 |
| 加权随机 | 按权重随机 | 实例性能不同 |
| 最少连接 | 选择连接数最少的 | 长连接场景 |
| 一致性哈希 | 相同请求路由到相同实例 | 缓存场景 |
| 响应时间加权 | 响应时间短权重高 | 性能敏感场景 |
2.3 Spring Cloud LoadBalancer
Spring Cloud LoadBalancer 是 Spring Cloud 官方推荐的负载均衡器,用于替代 Ribbon。
核心组件
- ServiceInstanceListSupplier:服务实例列表提供者
- ReactorServiceInstanceLoadBalancer:负载均衡器
- LoadBalancerClient:负载均衡客户端
- LoadBalancerClientFactory:负载均衡客户端工厂
2.4 Ribbon 核心组件
虽然 Ribbon 已进入维护模式,但很多项目仍在使用:
- ServerList:服务器列表
- IRule:负载均衡规则
- IPing:健康检查
- ServerListFilter:服务器过滤
2.5 负载均衡配置
spring:
cloud:
loadbalancer:
ribbon:
enabled: false # 禁用 Ribbon
cache:
enabled: true
ttl: 35s
health-check:
initial-delay: 0
interval: 25s
三、代码示例
3.1 RestTemplate + 负载均衡
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.loadbalancer.LoadBalanced;
import org.springframework.context.annotation.Bean;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.client.RestTemplate;
@SpringBootApplication
@RestController
public class LoadBalancerApplication {
public static void main(String[] args) {
SpringApplication.run(LoadBalancerApplication.class, args);
}
@Bean
@LoadBalanced // 启用负载均衡
public RestTemplate restTemplate() {
return new RestTemplate();
}
@Autowired
private RestTemplate restTemplate;
@GetMapping("/call")
public String callService() {
// 使用服务名调用,自动负载均衡
return restTemplate.getForObject(
"http://user-service/api/users/1",
String.class
);
}
}
3.2 WebClient + 负载均衡
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.loadbalancer.LoadBalanced;
import org.springframework.context.annotation.Bean;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.reactive.function.client.WebClient;
@SpringBootApplication
@RestController
public class ReactiveLoadBalancerApplication {
public static void main(String[] args) {
SpringApplication.run(ReactiveLoadBalancerApplication.class, args);
}
@Bean
@LoadBalanced
public WebClient.Builder webClientBuilder() {
return WebClient.builder();
}
@Autowired
private WebClient.Builder webClientBuilder;
@GetMapping("/reactive")
public Mono<String> reactiveCall() {
return webClientBuilder.build()
.get()
.uri("http://user-service/api/users/1")
.retrieve()
.bodyToMono(String.class);
}
}
3.3 自定义负载均衡策略
import org.springframework.cloud.client.ServiceInstance;
import org.springframework.cloud.loadbalancer.core.*;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import reactor.core.publisher.Mono;
import java.util.*;
import java.util.concurrent.atomic.AtomicInteger;
@Configuration
public class CustomLoadBalancerConfig {
// 自定义轮询负载均衡器
@Bean
ReactorServiceInstanceLoadBalancer customLoadBalancer(
ServiceInstanceListSupplier serviceInstanceListSupplier) {
return new CustomRoundRobinLoadBalancer(serviceInstanceListSupplier);
}
}
class CustomRoundRobinLoadBalancer implements ReactorServiceInstanceLoadBalancer {
private final ServiceInstanceListSupplier serviceInstanceListSupplier;
private final AtomicInteger position = new AtomicInteger(0);
public CustomRoundRobinLoadBalancer(ServiceInstanceListSupplier serviceInstanceListSupplier) {
this.serviceInstanceListSupplier = serviceInstanceListSupplier;
}
@Override
public Mono<Response<ServiceInstance>> choose(Request request) {
return serviceInstanceListSupplier.get()
.next()
.map(instances -> {
if (instances.isEmpty()) {
return new EmptyResponse();
}
int pos = Math.abs(position.incrementAndGet() % instances.size());
ServiceInstance instance = instances.get(pos);
return new DefaultResponse(instance);
});
}
}
3.4 加权负载均衡
import org.springframework.cloud.client.ServiceInstance;
import org.springframework.cloud.loadbalancer.core.*;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import reactor.core.publisher.Mono;
import java.util.*;
import java.util.concurrent.ThreadLocalRandom;
@Configuration
public class WeightedLoadBalancerConfig {
@Bean
ReactorServiceInstanceLoadBalancer weightedLoadBalancer(
ServiceInstanceListSupplier supplier) {
return new WeightedLoadBalancer(supplier);
}
}
class WeightedLoadBalancer implements ReactorServiceInstanceLoadBalancer {
private final ServiceInstanceListSupplier supplier;
public WeightedLoadBalancer(ServiceInstanceListSupplier supplier) {
this.supplier = supplier;
}
@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(this::getWeight)
.sum();
// 加权随机选择
int random = ThreadLocalRandom.current().nextInt(totalWeight);
int currentWeight = 0;
for (ServiceInstance instance : instances) {
currentWeight += getWeight(instance);
if (random < currentWeight) {
return new DefaultResponse(instance);
}
}
return new DefaultResponse(instances.get(0));
});
}
private int getWeight(ServiceInstance instance) {
Map<String, String> metadata = instance.getMetadata();
if (metadata != null && metadata.containsKey("weight")) {
try {
return Integer.parseInt(metadata.get("weight"));
} catch (NumberFormatException e) {
return 1;
}
}
return 1;
}
}
3.5 一致性哈希负载均衡
import org.springframework.cloud.client.ServiceInstance;
import org.springframework.cloud.loadbalancer.core.*;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import reactor.core.publisher.Mono;
import java.util.*;
@Configuration
public class ConsistentHashLoadBalancerConfig {
@Bean
ReactorServiceInstanceLoadBalancer consistentHashLoadBalancer(
ServiceInstanceListSupplier supplier) {
return new ConsistentHashLoadBalancer(supplier);
}
}
class ConsistentHashLoadBalancer implements ReactorServiceInstanceLoadBalancer {
private final ServiceInstanceListSupplier supplier;
private final SortedMap<Integer, ServiceInstance> circle = new TreeMap<>();
private static final int VIRTUAL_NODES = 150;
public ConsistentHashLoadBalancer(ServiceInstanceListSupplier supplier) {
this.supplier = supplier;
}
@Override
public Mono<Response<ServiceInstance>> choose(Request request) {
return supplier.get()
.next()
.map(instances -> {
if (instances.isEmpty()) {
return new EmptyResponse();
}
// 构建哈希环
buildCircle(instances);
// 获取哈希键
String hashKey = getHashKey(request);
int hash = hash(hashKey);
// 顺时针查找
SortedMap<Integer, ServiceInstance> tailMap = circle.tailMap(hash);
Integer key = tailMap.isEmpty() ? circle.firstKey() : tailMap.firstKey();
return new DefaultResponse(circle.get(key));
});
}
private void buildCircle(List<ServiceInstance> instances) {
circle.clear();
for (ServiceInstance instance : instances) {
for (int i = 0; i < VIRTUAL_NODES; i++) {
String virtualNode = instance.getHost() + ":" + instance.getPort() + "#" + i;
int hash = hash(virtualNode);
circle.put(hash, instance);
}
}
}
private String getHashKey(Request request) {
// 从请求中获取哈希键(如用户ID、请求ID等)
if (request instanceof DefaultRequest) {
Object context = ((DefaultRequest<?>) request).getContext();
if (context instanceof HttpRequestData) {
// 可以从请求头、参数等获取
return ((HttpRequestData) context).getPath();
}
}
return String.valueOf(System.currentTimeMillis());
}
private int hash(String key) {
return key.hashCode();
}
}
3.6 服务实例过滤器
import org.springframework.cloud.client.ServiceInstance;
import org.springframework.cloud.loadbalancer.core.ServiceInstanceListSupplier;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import reactor.core.publisher.Flux;
import java.util.*;
import java.util.stream.Collectors;
@Configuration
public class ServiceInstanceFilterConfig {
@Bean
public ServiceInstanceListSupplier healthyInstanceSupplier(
ServiceInstanceListSupplier delegate) {
return new HealthyServiceInstanceSupplier(delegate);
}
}
class HealthyServiceInstanceSupplier implements ServiceInstanceListSupplier {
private final ServiceInstanceListSupplier delegate;
public HealthyServiceInstanceSupplier(ServiceInstanceListSupplier delegate) {
this.delegate = delegate;
}
@Override
public String getServiceId() {
return delegate.getServiceId();
}
@Override
public Flux<List<ServiceInstance>> get() {
return delegate.get()
.map(instances -> instances.stream()
.filter(this::isHealthy)
.collect(Collectors.toList()));
}
private boolean isHealthy(ServiceInstance instance) {
// 检查实例是否健康
Map<String, String> metadata = instance.getMetadata();
if (metadata != null) {
String status = metadata.get("status");
return status == null || "UP".equalsIgnoreCase(status);
}
return true;
}
}
// 版本过滤
class VersionServiceInstanceSupplier implements ServiceInstanceListSupplier {
private final ServiceInstanceListSupplier delegate;
private final String targetVersion;
public VersionServiceInstanceSupplier(ServiceInstanceListSupplier delegate,
String targetVersion) {
this.delegate = delegate;
this.targetVersion = targetVersion;
}
@Override
public String getServiceId() {
return delegate.getServiceId();
}
@Override
public Flux<List<ServiceInstance>> get() {
return delegate.get()
.map(instances -> instances.stream()
.filter(instance -> {
Map<String, String> metadata = instance.getMetadata();
if (metadata != null && metadata.containsKey("version")) {
return targetVersion.equals(metadata.get("version"));
}
return true;
})
.collect(Collectors.toList()));
}
}
3.7 重试机制
# application.yml
spring:
cloud:
loadbalancer:
retry:
enabled: true
retry-on-all-operations: false # 只对 GET 请求重试
max-retries-on-next-service-instance: 1
max-retries-on-same-service-instance: 0
import org.springframework.cloud.client.loadbalancer.*;
import org.springframework.cloud.loadbalancer.annotation.LoadBalancerClient;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.retry.annotation.Retryable;
import org.springframework.stereotype.Service;
@Configuration
@LoadBalancerClient(name = "user-service", configuration = RetryConfig.class)
public class RetryConfig {
@Bean
public ServiceInstanceListSupplier retryAwareSupplier(
ServiceInstanceListSupplier delegate) {
return new RetryAwareServiceInstanceSupplier(delegate);
}
}
@Service
public class RetryService {
@Autowired
private RestTemplate restTemplate;
@Retryable(
value = {Exception.class},
maxAttempts = 3,
backoff = @Backoff(delay = 1000, multiplier = 2)
)
public String callWithRetry() {
return restTemplate.getForObject(
"http://user-service/api/users/1",
String.class
);
}
}
3.8 负载均衡指标
import io.micrometer.core.instrument.*;
import org.springframework.cloud.client.ServiceInstance;
import org.springframework.cloud.loadbalancer.core.*;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import reactor.core.publisher.Mono;
@Configuration
public class LoadBalancerMetricsConfig {
@Bean
ReactorServiceInstanceLoadBalancer meteredLoadBalancer(
ServiceInstanceListSupplier supplier,
MeterRegistry meterRegistry) {
return new MeteredLoadBalancer(supplier, meterRegistry);
}
}
class MeteredLoadBalancer implements ReactorServiceInstanceLoadBalancer {
private final ServiceInstanceListSupplier supplier;
private final MeterRegistry meterRegistry;
private final Counter chooseCounter;
private final Timer chooseTimer;
public MeteredLoadBalancer(ServiceInstanceListSupplier supplier,
MeterRegistry meterRegistry) {
this.supplier = supplier;
this.meterRegistry = meterRegistry;
this.chooseCounter = Counter.builder("loadbalancer.choose.total")
.tag("service", supplier.getServiceId())
.register(meterRegistry);
this.chooseTimer = Timer.builder("loadbalancer.choose.duration")
.tag("service", supplier.getServiceId())
.register(meterRegistry);
}
@Override
public Mono<Response<ServiceInstance>> choose(Request request) {
return chooseTimer.record(() -> {
chooseCounter.increment();
return supplier.get()
.next()
.map(instances -> {
if (instances.isEmpty()) {
return new EmptyResponse();
}
// 简单轮询
int index = (int) (chooseCounter.count() % instances.size());
ServiceInstance instance = instances.get(index);
// 记录选择的实例
Tags tags = Tags.of(
"service", supplier.getServiceId(),
"instance", instance.getHost() + ":" + instance.getPort()
);
meterRegistry.counter("loadbalancer.instance.selected", tags)
.increment();
return new DefaultResponse(instance);
});
});
}
}
四、实战应用场景
4.1 灰度发布
import org.springframework.cloud.client.ServiceInstance;
import org.springframework.cloud.loadbalancer.core.*;
import org.springframework.web.bind.annotation.*;
import reactor.core.publisher.Mono;
import java.util.*;
import java.util.stream.Collectors;
@RestController
public class GrayReleaseController {
@Autowired
private LoadBalancerClientFactory loadBalancerClientFactory;
@GetMapping("/gray/{version}")
public Mono<String> grayRelease(
@PathVariable String version,
@RequestHeader(value = "X-User-Id", required = false) String userId) {
// 获取灰度负载均衡器
ReactorLoadBalancer<ServiceInstance> loadBalancer =
loadBalancerClientFactory.getInstance("user-service",
GrayLoadBalancer.class);
// 根据用户ID和版本选择实例
GrayRequest request = new GrayRequest(version, userId);
return Mono.from(loadBalancer.choose(request))
.map(response -> {
ServiceInstance instance = response.getServer();
return "Routed to: " + instance.getHost() + ":" + instance.getPort();
});
}
}
class GrayLoadBalancer implements ReactorServiceInstanceLoadBalancer {
private final ServiceInstanceListSupplier supplier;
public GrayLoadBalancer(ServiceInstanceListSupplier supplier) {
this.supplier = supplier;
}
@Override
public Mono<Response<ServiceInstance>> choose(Request request) {
return supplier.get()
.next()
.map(instances -> {
if (instances.isEmpty()) {
return new EmptyResponse();
}
GrayRequest grayRequest = (GrayRequest) request;
// 根据版本过滤实例
List<ServiceInstance> filtered = instances.stream()
.filter(instance -> {
Map<String, String> metadata = instance.getMetadata();
if (metadata != null) {
String instanceVersion = metadata.get("version");
return grayRequest.version.equals(instanceVersion);
}
return false;
})
.collect(Collectors.toList());
// 如果没有匹配的灰度实例,使用默认实例
if (filtered.isEmpty()) {
filtered = instances.stream()
.filter(instance -> {
Map<String, String> metadata = instance.getMetadata();
return metadata == null ||
!"gray".equals(metadata.get("type"));
})
.collect(Collectors.toList());
}
if (filtered.isEmpty()) {
return new DefaultResponse(instances.get(0));
}
// 随机选择
int index = new Random().nextInt(filtered.size());
return new DefaultResponse(filtered.get(index));
});
}
}
class GrayRequest extends DefaultRequest<String> {
private final String version;
private final String userId;
public GrayRequest(String version, String userId) {
super(userId);
this.version = version;
this.userId = userId;
}
}
4.2 区域感知负载均衡
import org.springframework.cloud.client.ServiceInstance;
import org.springframework.cloud.loadbalancer.core.*;
import reactor.core.publisher.Mono;
import java.util.*;
import java.util.stream.Collectors;
@Configuration
public class ZoneAwareLoadBalancerConfig {
@Bean
ReactorServiceInstanceLoadBalancer zoneAwareLoadBalancer(
ServiceInstanceListSupplier supplier) {
return new ZoneAwareLoadBalancer(supplier);
}
}
class ZoneAwareLoadBalancer implements ReactorServiceInstanceLoadBalancer {
private final ServiceInstanceListSupplier supplier;
private final String currentZone;
public ZoneAwareLoadBalancer(ServiceInstanceListSupplier supplier) {
this.supplier = supplier;
this.currentZone = System.getProperty("zone", "default");
}
@Override
public Mono<Response<ServiceInstance>> choose(Request request) {
return supplier.get()
.next()
.map(instances -> {
if (instances.isEmpty()) {
return new EmptyResponse();
}
// 优先选择同区域的实例
List<ServiceInstance> sameZone = instances.stream()
.filter(instance -> {
Map<String, String> metadata = instance.getMetadata();
if (metadata != null) {
return currentZone.equals(metadata.get("zone"));
}
return false;
})
.collect(Collectors.toList());
if (!sameZone.isEmpty()) {
// 同区域实例随机选择
int index = new Random().nextInt(sameZone.size());
return new DefaultResponse(sameZone.get(index));
}
// 没有同区域实例,使用其他区域
int index = new Random().nextInt(instances.size());
return new DefaultResponse(instances.get(index));
});
}
}
五、总结与最佳实践
负载均衡策略选择
| 场景 | 推荐策略 |
|---|---|
| 实例性能相近 | 轮询 |
| 实例性能差异大 | 加权轮询 |
| 有状态服务 | 一致性哈希 |
| 灰度发布 | 标签路由 |
| 多区域部署 | 区域感知 |
最佳实践
- 健康检查:配置合理的健康检查机制
- 重试机制:设置合理的重试次数
- 超时配置:避免长时间等待
- 指标监控:监控负载均衡效果
负载均衡是微服务架构的核心技术,选择合适的负载均衡策略,配置合理的参数,能够显著提高系统的性能和可用性。