负载均衡算法与高可用架构

深入理解 L4/L7 负载均衡原理,掌握轮询、加权、一致性哈希等算法实现与 Nginx、Envoy 配置

负载均衡是分布式系统的核心组件,它将请求合理地分发到多个后端服务器,实现水平扩展、故障转移和高可用。

一、负载均衡层次

1.1 L4 vs L7

┌────────────────────────────────────────────┐
│              L7 负载均衡(应用层)            │
│  - 基于 URL、Host、Header、Cookie            │
│  - SSL 终止、HTTP 路由、内容缓存              │
│  - 代表:Nginx、HAProxy、Envoy、Traefik     │
├────────────────────────────────────────────┤
│              L4 负载均衡(传输层)            │
│  - 基于 IP + Port                            │
│  - TCP/UDP 流量分发,不解包应用层             │
│  - 代表:LVS、HAProxy、AWS NLB              │
├────────────────────────────────────────────┤
│              DNS 负载均衡                     │
│  - 基于地理位置、轮询返回不同 IP               │
│  - 代表:Cloudflare、Route53                 │
└────────────────────────────────────────────┘

1.2 对比

特性L4L7
性能更高(不解包)较低(解析 HTTP)
灵活性低高(路由规则丰富)
功能仅转发SSL、Rewrite、限流、缓存
延迟微秒级毫秒级
适用场景数据库、TCP 服务Web 应用、API 网关

二、负载均衡算法

2.1 轮询(Round Robin)

public class RoundRobinLoadBalancer {
    
    private final List<String> servers;
    private final AtomicInteger counter = new AtomicInteger(0);
    
    public RoundRobinLoadBalancer(List<String> servers) {
        this.servers = servers;
    }
    
    public String next() {
        int index = counter.getAndIncrement() % servers.size();
        return servers.get(index);
    }
}

// 加权轮询
public class WeightedRoundRobin {
    
    private final List<Server> servers;
    private final AtomicInteger currentIndex = new AtomicInteger(-1);
    private final AtomicInteger currentWeight = new AtomicInteger(0);
    private final int maxWeight;
    private final int gcdWeight;
    
    public String next() {
        while (true) {
            int index = currentIndex.incrementAndGet() % servers.size();
            
            if (index == 0) {
                int weight = currentWeight.decrementAndGet();
                if (weight <= 0) {
                    currentWeight.set(maxWeight);
                }
            }
            
            Server server = servers.get(index);
            if (server.weight >= currentWeight.get()) {
                return server.address;
            }
        }
    }
}

2.2 最少连接(Least Connections)

public class LeastConnectionsBalancer {
    
    private final Map<String, AtomicInteger> connections = new ConcurrentHashMap<>();
    
    public String next() {
        return connections.entrySet().stream()
            .min(Map.Entry.comparingByValue())
            .map(Map.Entry::getKey)
            .orElseThrow();
    }
    
    public void acquire(String server) {
        connections.computeIfAbsent(server, k -> new AtomicInteger()).incrementAndGet();
    }
    
    public void release(String server) {
        connections.get(server).decrementAndGet();
    }
}

2.3 一致性哈希(Consistent Hashing)

public class ConsistentHashBalancer {
    
    private final TreeMap<Long, String> ring = new TreeMap<>();
    private final int virtualNodes;  // 虚拟节点数
    
    public ConsistentHashBalancer(List<String> servers, int virtualNodes) {
        this.virtualNodes = virtualNodes;
        for (String server : servers) {
            addServer(server);
        }
    }
    
    public void addServer(String server) {
        for (int i = 0; i < virtualNodes; i++) {
            long hash = hash(server + "#" + i);
            ring.put(hash, server);
        }
    }
    
    public void removeServer(String server) {
        for (int i = 0; i < virtualNodes; i++) {
            long hash = hash(server + "#" + i);
            ring.remove(hash);
        }
    }
    
    public String getNode(String key) {
        if (ring.isEmpty()) return null;
        
        long hash = hash(key);
        Map.Entry<Long, String> entry = ring.ceilingEntry(hash);
        if (entry == null) {
            entry = ring.firstEntry();
        }
        return entry.getValue();
    }
    
    private long hash(String key) {
        // MurmurHash 或 MD5
        return Hashing.murmur3_128().hashString(key, StandardCharsets.UTF_8).asLong();
    }
}

// 使用场景:
// - 分布式缓存(Redis Cluster)
// - 分布式存储(MinIO)
// - 有状态服务路由

2.4 算法选型

算法优点缺点适用场景
轮询简单、均匀不考虑负载差异服务器性能相近
加权轮询考虑性能差异需手动配置权重异构服务器集群
最少连接动态适应需维护连接计数长连接服务
一致性哈希节点增减影响小可能不均匀缓存、有状态服务
IP 哈希会话保持热点风险需要会话粘性
随机实现简单不够均匀简单场景

三、Nginx 负载均衡配置

# HTTP 负载均衡(L7)
upstream backend {
    # 加权轮询(默认)
    server 192.168.1.10:8080 weight=5;
    server 192.168.1.11:8080 weight=3;
    server 192.168.1.12:8080 backup;  # 备用
    
    # 最少连接
    least_conn;
    
    # IP 哈希(会话保持)
    # ip_hash;
    
    # 一致性哈希(基于变量)
    # hash $request_uri consistent;
    
    # 健康检查
    server 192.168.1.10:8080 max_fails=3 fail_timeout=30s;
    
    keepalive 32;  # 连接池
}

server {
    listen 80;
    
    location /api/ {
        proxy_pass http://backend;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        
        # 超时配置
        proxy_connect_timeout 5s;
        proxy_read_timeout 30s;
        proxy_send_timeout 30s;
    }
    
    # 静态资源,基于 URL 路由
    location /static/ {
        proxy_pass http://static-backend;
    }
}
# TCP/UDP 负载均衡(L4,需 stream 模块)
stream {
    upstream redis_backend {
        server 192.168.1.10:6379;
        server 192.168.1.11:6379;
        
        # 健康检查
        check interval=3000 rise=2 fall=3 timeout=1000;
    }
    
    server {
        listen 6379;
        proxy_pass redis_backend;
        proxy_connect_timeout 3s;
        proxy_timeout 30s;
    }
}

四、健康检查

public class HealthChecker {
    
    private final List<Server> servers;
    private final ScheduledExecutorService scheduler = Executors.newScheduledThreadPool(1);
    
    public HealthChecker(List<Server> servers) {
        this.servers = servers;
        // 每 5 秒检查一次
        scheduler.scheduleAtFixedRate(this::checkAll, 5, 5, TimeUnit.SECONDS);
    }
    
    private void checkAll() {
        for (Server server : servers) {
            boolean healthy = checkServer(server);
            if (healthy != server.isHealthy()) {
                server.setHealthy(healthy);
                System.out.println("Server " + server.getAddress() + " is " + (healthy ? "UP" : "DOWN"));
            }
        }
    }
    
    private boolean checkServer(Server server) {
        try (Socket socket = new Socket()) {
            socket.connect(new InetSocketAddress(server.getHost(), server.getPort()), 3000);
            return true;
        } catch (IOException e) {
            return false;
        }
    }
}

负载均衡高可用架构设计

生产环境中,负载均衡器本身不能成为单点故障。高可用的负载均衡架构通常采用主备模式或集群模式,确保即使 LB 节点宕机,流量也能无缝切换到备用节点。

LVS + Keepalived 主备架构

         ┌─────────────┐
         │   Client    │
         └──────┬──────┘
                │
         ┌──────┴──────┐
         │   VIP       │  (Virtual IP: 10.0.0.100)
         └──────┬──────┘
                │
      ┌─────────┴─────────┐
      │                   │
┌─────┴─────┐      ┌─────┴─────┐
│  LVS-Master│      │  LVS-Backup│
│  (Active)  │◄────►│  (Standby) │
│  Keepalived│ VRRP │  Keepalived│
└─────┬─────┘      └─────┬─────┘
      │                   │
      └─────────┬─────────┘
                │
      ┌─────────┼─────────┐
      │         │         │
┌─────┴─┐  ┌────┴─┐  ┌────┴─┐
│RealServer1│ │RealServer2│ │RealServer3│
└─────────┘  └─────────┘  └─────────┘
# Keepalived 配置(Master)
# /etc/keepalived/keepalived.conf
global_defs {
    router_id LVS_MASTER
}

vrrp_instance VI_1 {
    state MASTER
    interface eth0
    virtual_router_id 51
    priority 100
    advert_int 1
    authentication {
        auth_type PASS
        auth_pass 1111
    }
    virtual_ipaddress {
        10.0.0.100/24
    }
}

virtual_server 10.0.0.100 80 {
    delay_loop 6
    lb_algo wrr
    lb_kind DR  # Direct Routing 模式
    protocol TCP

    real_server 10.0.0.11 80 {
        weight 1
        TCP_CHECK {
            connect_timeout 3
            nb_get_retry 3
            delay_before_retry 3
        }
    }
    real_server 10.0.0.12 80 {
        weight 1
        TCP_CHECK {
            connect_timeout 3
        }
    }
}

LVS 三种工作模式:

模式数据流向性能后端要求适用
NATLB 修改目的 IP,后端通过 LB 返回中无特殊要求小规模
DRLB 修改 MAC 地址,后端直接返回客户端高后端需配置 VIP(lo 别名)大规模(推荐)
TUN封装 IP 隧道,后端解封后返回高后端支持 IPIP 隧道跨机房

Anycast + BGP 全球负载均衡

对于全球化部署的应用,Anycast 是一种更高级的负载均衡技术。同一个 IP 地址同时广播到多个地理位置的接入点(PoP),用户请求自动路由到最近节点。

全球 Anycast 架构:
                      ┌─────────────┐
                      │   用户 (北京) │
                      └──────┬──────┘
                             │
                    ┌────────┴────────┐
                    │   BGP 路由选择   │
                    │ 最近/最优路径    │
                    └────────┬────────┘
                             │
┌──────────┐         ┌──────┴──────┐         ┌──────────┐
│ 北京 PoP │◄───────►│  Anycast IP  │◄───────►│ 上海 PoP │
│  10ms    │         │  1.2.3.4    │         │  30ms    │
└────┬─────┘         └─────────────┘         └────┬─────┘
     │                                             │
┌────┴─────┐                               ┌────┴─────┐
│ 源站集群  │◄─────────────────────────────►│ 源站集群  │
│ 华北区   │         跨区域数据同步           │ 华东区   │
└──────────┘                               └──────────┘

Anycast 的优势:

  1. 自动容灾:某个 PoP 故障时,BGP 自动收敛,流量切换到其他 PoP(通常 < 60 秒)
  2. 就近接入:无需 DNS 地理解析,路由协议自动选择最优路径
  3. DDoS 分散:攻击流量被分散到多个 PoP,单一节点承载压力大幅降低

Anycast 的挑战:

  • 状态共享困难:同一用户的请求可能被路由到不同 PoP,需使用全局 Session 存储(如 Redis Cluster)
  • TCP 连接中断:BGP 收敛期间活跃连接可能中断(长连接应用需设计重连机制)
  • 部署复杂:需要与 ISP 协商 BGP peering,或使用 Cloudflare、AWS Global Accelerator 等托管方案

Envoy 高级功能与 Service Mesh 集成

Envoy 作为云原生时代的高性能代理,已成为 Kubernetes 和服务网格(Istio)的事实标准数据面。

Envoy 关键特性

# envoy.yaml 示例
static_resources:
  listeners:
    - name: http_listener
      address:
        socket_address:
          address: 0.0.0.0
          port_value: 8080
      filter_chains:
        - filters:
            - name: envoy.filters.network.http_connection_manager
              typed_config:
                "@type": type.googleapis.com/envoy.extensions.filters.network.http_connection_manager.v3.HttpConnectionManager
                stat_prefix: ingress_http
                codec_type: AUTO
                route_config:
                  name: local_route
                  virtual_hosts:
                    - name: backend
                      domains: ["*"]
                      routes:
                        - match:
                            prefix: "/api/v1/"
                          route:
                            cluster: api_service
                            timeout: 30s
                            retry_policy:
                              retry_on: "5xx,connect-failure"
                              num_retries: 3
                              per_try_timeout: 10s
                        - match:
                            prefix: "/"
                          route:
                            cluster: frontend_service
                            # 断路器配置
                            circuit_breakers:
                              thresholds:
                                - priority: DEFAULT
                                  max_connections: 1000
                                  max_pending_requests: 1000
                                  max_requests: 1000
                                  max_retries: 3
                http_filters:
                  - name: envoy.filters.http.cors
                  - name: envoy.filters.http.local_ratelimit
                  - name: envoy.filters.http.router

  clusters:
    - name: api_service
      connect_timeout: 5s
      type: STRICT_DNS
      lb_policy: LEAST_REQUEST
      load_assignment:
        cluster_name: api_service
        endpoints:
          - lb_endpoints:
              - endpoint:
                  address:
                    socket_address:
                      address: api.default.svc.cluster.local
                      port_value: 80
      # 主动健康检查
      health_checks:
        - timeout: 5s
          interval: 10s
          unhealthy_threshold: 3
          healthy_threshold: 2
          http_health_check:
            path: "/health"

Envoy 相比 Nginx 的优势:

特性EnvoyNginx
动态配置xDS API(无需重启)需 reload
可观察性内置 Prometheus 统计需模块扩展
热重启零停机graceful reload
断路器内置需 Lua 或第三方模块
重试/超时细粒度配置相对简单
服务网格Istio 标准数据面非原生支持

五、总结

组件层级算法场景
NginxL7/L4轮询、加权、IP 哈希、最少连接Web 入口、API 网关
LVSL4多种极高性能场景
EnvoyL7丰富的负载均衡策略云原生、Service Mesh
DNSL3地理、轮询多活架构、全球调度

负载均衡不仅是请求分发,更是系统高可用的核心保障。合理的算法选型、完善的监控告警、自动化的故障转移,才能构建真正可靠的服务架构。

继续阅读

探索更多技术文章

浏览归档,发现更多关于系统设计、工具链和工程实践的内容。

全部文章 返回首页

「network」更多文章

  1. 46. 排队论与容量估算:利特尔法则与尾延迟
  2. MPTCP 与多路径传输
  3. 流量整形与 TC/qdisc