Skip to content
第 28 章 架构 ⏱ 11 分钟阅读

第 28 章:负载均衡 ​

学习目标 ​

  • 掌握 4 种负载均衡算法
  • 学会 Nginx / Spring Cloud LoadBalancer 实战
  • 配置会话保持与一致性哈希
  • 避免负载不均、热点实例的坑

一、负载均衡算法 ​

java
// 1. 轮询(Round Robin)
public class RoundRobin {
    private AtomicInteger index = new AtomicInteger(0);
    public ServiceInstance choose(List<ServiceInstance> instances) {
        return instances.get(index.getAndIncrement() % instances.size());
    }
}

// 2. 随机(Random)
public class Random {
    public ServiceInstance choose(List<ServiceInstance> instances) {
        return instances.get(ThreadLocalRandom.current().nextInt(instances.size()));
    }
}

// 3. 最小连接(Least Connections)
public class LeastConnections {
    public ServiceInstance choose(List<ServiceInstance> instances) {
        return instances.stream()
            .min(Comparator.comparingInt(i -> connectionCount.get(i.getId())))
            .orElseThrow();
    }
}

// 4. 一致性哈希(Consistent Hash)
public class ConsistentHash {
    private final TreeMap<Long, ServiceInstance> ring = new TreeMap<>();
    public ServiceInstance choose(String key) {
        long hash = hash(key);
        SortedMap<Long, ServiceInstance> tailMap = ring.tailMap(hash);
        return tailMap.isEmpty() ? ring.firstEntry().getValue() : tailMap.get(tailMap.firstKey());
    }
}
text
算法对比
├── 轮询:均匀,不考虑实例性能
├── 随机:均匀,小集群偏差
├── 最小连接:考虑负载,适合长连接
└── 一致性哈希:缓存命中率,适合会话保持

⚠️ 坑 1:所有请求都被哈希到一个实例 = 热点。加虚拟节点,让分布更均匀。

二、Nginx 负载均衡 ​

nginx
upstream order-service {
    # 1. 轮询(默认)
    server order1:8080;
    server order2:8080;
    server order3:8080;

    # 2. 加权轮询
    server order1:8080 weight=3;   # 3 倍流量
    server order2:8080 weight=2;
    server order3:8080 weight=1;

    # 3. ip_hash(会话保持)
    ip_hash;
    server order1:8080;
    server order2:8080;

    # 4. least_conn
    least_conn;
    server order1:8080;
    server order2:8080;
}

server {
    listen 80;
    location /api/order {
        proxy_pass http://order-service;
    }
}
nginx
# 健康检查
upstream order-service {
    server order1:8080 max_fails=3 fail_timeout=30s;
    server order2:8080;
    server order3:8080 backup;   # 备份
}

三、Spring Cloud LoadBalancer ​

yaml
spring:
  cloud:
    loadbalancer:
      ribbon:
        enabled: false    # 禁用 Ribbon(JDK 17 已移除)
      configurations:
        round-robin       # 轮询
java
// 1. 自定义负载均衡
@Bean
public ReactorLoadBalancer<ServiceInstance> randomLoadBalancer(
        Environment env, LoadBalancerClientFactory factory) {
    String name = env.getProperty(LoadBalancerClientFactory.PROPERTY_NAME);
    return new RandomLoadBalancer(factory.getLazyProvider(name, ServiceInstanceListSupplier.class), name);
}

// 2. 调用
@Resource
private LoadBalancerClient loadBalancerClient;

public String callOrder() {
    ServiceInstance instance = loadBalancerClient.choose("order-service");
    String url = "http://" + instance.getHost() + ":" + instance.getPort() + "/api/...";
    return restTemplate.getForObject(url, String.class);
}

四、Feign 集成 ​

java
// 自动负载均衡
@FeignClient(name = "order-service")
public interface OrderClient {
    @GetMapping("/api/order/{id}")
    Order getOrder(@PathVariable Long id);
}
yaml
# 切换负载均衡策略
spring:
  cloud:
    loadbalancer:
      client:
        name: order-service
      configurations: random

五、加权负载均衡 ​

java
// 实例元数据
eureka:
  instance:
    metadata-map:
      weight: 100      # 权重 100
java
// 自定义权重负载均衡
public class WeightLoadBalancer implements ReactorServiceInstanceLoadBalancer {
    @Override
    public Mono<Response<ServiceInstance>> choose(Request request) {
        return supplier.get().next().map(instances -> {
            // 总权重
            int totalWeight = instances.stream()
                .mapToInt(i -> Integer.parseInt(i.getMetadata().getOrDefault("weight", "100")))
                .sum();
            // 随机
            int random = ThreadLocalRandom.current().nextInt(totalWeight);
            int current = 0;
            for (ServiceInstance instance : instances) {
                int weight = Integer.parseInt(instance.getMetadata().getOrDefault("weight", "100"));
                current += weight;
                if (random < current) {
                    return new DefaultResponse(instance);
                }
            }
            return new DefaultResponse(instances.get(0));
        });
    }
}

⚠️ 坑 2:权重分发在 client,Nginx 一层再做一次 → 权重叠加,流量不平衡。只在一层做权重。

六、负载均衡 + 限流 ​

java
// 实例级限流:每个实例 100 QPS
// 3 个实例 → 总 300 QPS

// 集群限流:总 QPS 100(Nacos/Lua 统一管理)
public class ClusterRateLimiter {
    public boolean tryAcquire(String service, int permits) {
        String lua = "return redis.call('INCRBY', KEYS[1], " + permits + ")";
        Long count = redisTemplate.execute(new DefaultRedisScript<>(lua, Long.class), List.of("rl:" + service));
        return count <= maxCount;
    }
}

七、负载均衡 + 健康检查 ​

yaml
# 1. 实例不健康自动剔除
eureka:
  instance:
    lease-renewal-interval-in-seconds: 10
    lease-expiration-duration-in-seconds: 30

# 2. 主动健康检查
health-check:
  enabled: true
  interval: 5000
  timeout: 3000
  unhealthy-threshold: 3
java
// 自定义健康检查
@Bean
public HealthIndicator customHealth() {
    return () -> checkDatabase() && checkRedis()
        ? Health.up().build()
        : Health.down().build();
}

八、负载均衡 + 重试 ​

java
// 失败时换实例重试
@Retryable(retryFor = Exception.class, maxAttempts = 3)
public String callOrder(Long orderId) {
    return orderClient.getOrder(orderId);
}
yaml
# Spring Cloud Retry
spring:
  cloud:
    loadbalancer:
      retry:
        enabled: true
        max-retries: 3
        retry-on-all-exceptions: true

⚠️ 坑 3:重试导致请求放大,1 个请求变 3 个,后端压力 3 倍。重试 + 熔断配合。

九、负载均衡策略对比 ​

策略适用场景
轮询集群性能一致
加权轮询集群性能不一致
最小连接长连接服务
一致性哈希缓存、会话
随机简单场景

十、压测与监控 ​

promql
# 各实例请求数
sum(rate(http_server_requests_seconds_count[1m])) by (instance)

# 各实例 P99 延迟
histogram_quantile(0.99, sum(rate(http_server_requests_seconds_bucket[1m])) by (instance, le))

# 各实例错误率
sum(rate(http_server_requests_seconds_count{status=~"5.."}[1m])) by (instance) /
sum(rate(http_server_requests_seconds_count[1m])) by (instance)
text
观察指标
├── 请求分布:各实例均匀 ±10%
├── 错误率:各实例 < 0.1%
└── 延迟:各实例 P99 < 100ms

本章小结 ​

工具适用
Nginx入口负载均衡
Spring Cloud LB微服务间
自研特殊策略
关键点建议
权重一层做
健康检查5-10s
重试+ 熔断
监控各实例均衡

动手练习 ​

  1. Nginx 轮询:配置 3 个后端,观察流量分布
  2. 加权负载:用权重 metadata,验证高权重实例流量大
  3. 一致性哈希:写一个一致性哈希 LB,验证缓存命中率
  4. 健康检查:故意停一个实例,观察流量转移

下一章:第 29 章:数据一致性 →

本站基于 VitePress 构建 · 由 StackHub 团队维护