轩辕李的博客 轩辕李的博客
首页
  • Java
  • Spring
  • 其他语言
  • 工具
  • JavaScript
  • TypeScript
  • Node.js
  • 前端框架
  • 前端工程化
  • 浏览器与Web API
  • 架构设计与模式
  • 代码质量管理
  • 基础
  • 操作系统
  • 计算机网络
  • 编程范式
  • 安全
  • 中间件
  • 心得
关于
  • 分类
  • 标签
  • 归档
GitHub (opens new window)

轩辕李

勇猛精进,星辰大海
首页
  • Java
  • Spring
  • 其他语言
  • 工具
  • JavaScript
  • TypeScript
  • Node.js
  • 前端框架
  • 前端工程化
  • 浏览器与Web API
  • 架构设计与模式
  • 代码质量管理
  • 基础
  • 操作系统
  • 计算机网络
  • 编程范式
  • 安全
  • 中间件
  • 心得
关于
  • 分类
  • 标签
  • 归档
GitHub (opens new window)
  • 架构设计与模式

    • 高可用-分布式基础之CAP理论
    • 高可用-服务容错与降级策略
    • 高可用-故障检测与自动恢复
    • 高可用-混沌工程实践
    • 高可用-分布式事务实战
    • 高可用-多活与容灾架构设计
    • 高性能-缓存架构设计
    • 高性能-性能优化方法论
    • 高性能-异步处理与消息队列
    • 高性能-数据库性能优化
    • 高性能-负载均衡策略详解
      • 一、引言:为什么需要负载均衡
      • 二、负载均衡的基本原理
        • 2.1 负载均衡的分层架构
        • 2.2 负载均衡的核心组件
      • 三、负载均衡核心算法
        • 3.1 轮询(Round Robin)
        • 3.2 加权轮询(Weighted Round Robin)
        • 3.3 随机(Random)
        • 3.4 加权随机(Weighted Random)
        • 3.5 最少连接(Least Connections)
        • 3.6 加权最少连接(Weighted Least Connections)
        • 3.7 IP哈希(IP Hash)
        • 3.8 一致性哈希(Consistent Hash)
        • 3.9 最短响应时间(Least Response Time)
      • 四、负载均衡算法选择指南
        • 4.1 算法对比总结
        • 4.2 选择决策树
        • 4.3 常见场景推荐
      • 五、负载均衡的实现方式
        • 5.1 软件负载均衡
        • 5.1.1 Nginx
        • 5.1.2 HAProxy
        • 5.1.3 LVS (Linux Virtual Server)
        • 5.2 硬件负载均衡
        • 5.3 云负载均衡
      • 六、健康检查与故障转移
        • 6.1 健康检查机制
        • 6.2 故障转移策略
      • 七、会话保持(Session Persistence)
        • 7.1 会话保持的必要性
        • 7.2 基于Cookie的会话保持
        • 7.3 基于IP的会话保持
        • 7.4 会话复制
      • 八、负载均衡的最佳实践
        • 8.1 监控与告警
        • 8.2 动态权重调整
        • 8.3 优雅上下线
        • 8.4 容量规划
      • 九、总结:构建高效负载均衡系统的关键要点
    • 高性能-CDN与静态资源优化
    • 高扩展-水平扩展vs垂直扩展
    • 高扩展-无状态服务设计
    • 高扩展-微服务架构演进
    • 高扩展-弹性伸缩设计
  • 代码质量管理

  • 基础

  • 操作系统

  • 计算机网络

  • AI

  • 编程范式

  • 安全

  • 中间件

  • 心得

  • 架构
  • 架构设计与模式
轩辕李
2025-04-28
目录

高性能-负载均衡策略详解

# 一、引言:为什么需要负载均衡

在现代分布式系统中,单台服务器的处理能力总是有限的。

当业务量增长、用户并发增加时,单台服务器很快就会遇到瓶颈:

  • 性能瓶颈:单台服务器CPU、内存、网络带宽打满
  • 可用性风险:单点故障会导致整个服务不可用
  • 扩展性受限:无法通过增加机器来提升系统容量

负载均衡是解决这些问题的核心技术:

用户请求
    ↓
[负载均衡器]
    ↓
服务器1  服务器2  服务器3  ...  服务器N

负载均衡的核心价值:

  1. 提升性能:将流量分散到多台服务器,充分利用集群资源
  2. 提高可用性:单台服务器故障不影响整体服务
  3. 水平扩展:通过增加服务器数量线性提升系统容量
  4. 灵活调度:可以根据服务器状态动态调整流量分配

本文将深入探讨负载均衡的核心原理、常见算法、实现方式以及最佳实践。


# 二、负载均衡的基本原理

# 2.1 负载均衡的分层架构

负载均衡可以在不同的网络层次实现:

层次 负载均衡类型 典型技术 特点
二层 数据链路层 MAC地址转发 性能极高,但灵活性差
三层 网络层 IP路由 基于IP地址,简单高效
四层 传输层 TCP/UDP端口 LVS、HAProxy(TCP模式)
七层 应用层 HTTP/HTTPS Nginx、HAProxy(HTTP模式)

四层 vs 七层负载均衡:

四层负载均衡:

  • 基于IP和端口转发,不解析应用层协议
  • 性能高,延迟低
  • 无法根据请求内容做路由决策
  • 适合高性能、流量大的场景

七层负载均衡:

  • 解析HTTP协议,可以根据URL、Header等做路由
  • 功能强大,支持内容路由、会话保持等
  • 性能相对较低,有一定延迟
  • 适合需要灵活路由策略的场景

# 2.2 负载均衡的核心组件

客户端
  ↓
[负载均衡器]
  ├─ 健康检查模块:监测后端服务器状态
  ├─ 调度算法模块:选择合适的服务器
  ├─ 会话保持模块:保证同一用户请求到同一服务器
  └─ 连接管理模块:管理与后端服务器的连接
  ↓
后端服务器集群

健康检查:

  • 定期检测后端服务器是否可用
  • 发现故障后自动摘除节点
  • 恢复后自动加回节点

调度算法:

  • 根据策略选择后端服务器
  • 是负载均衡的核心逻辑

会话保持:

  • 保证同一用户的请求发送到同一服务器
  • 解决有状态应用的一致性问题

# 三、负载均衡核心算法

# 3.1 轮询(Round Robin)

原理:按顺序依次将请求分配给每台服务器。

public class RoundRobinLoadBalancer {
    
    private List<Server> servers;
    private AtomicInteger currentIndex = new AtomicInteger(0);
    
    public Server select() {
        if (servers.isEmpty()) {
            return null;
        }
        
        int index = currentIndex.getAndIncrement() % servers.size();
        return servers.get(index);
    }
}

优点:

  • 实现简单
  • 请求分配均匀
  • 适合服务器性能相近的场景

缺点:

  • 不考虑服务器当前负载
  • 不考虑服务器性能差异
  • 可能导致性能差的服务器过载

适用场景:

  • 后端服务器性能相近
  • 请求处理时间相似
  • 无状态服务

# 3.2 加权轮询(Weighted Round Robin)

原理:根据服务器性能设置权重,性能高的服务器获得更多请求。

public class WeightedRoundRobinLoadBalancer {
    
    private List<WeightedServer> servers;
    private AtomicInteger currentWeight = new AtomicInteger(0);
    
    public Server select() {
        int totalWeight = servers.stream()
            .mapToInt(WeightedServer::getWeight)
            .sum();
        
        int targetWeight = currentWeight.getAndIncrement() % totalWeight;
        
        int currentSum = 0;
        for (WeightedServer server : servers) {
            currentSum += server.getWeight();
            if (targetWeight < currentSum) {
                return server.getServer();
            }
        }
        
        return servers.get(0).getServer();
    }
}

class WeightedServer {
    private Server server;
    private int weight;  // 权重值,如1、2、3
    
    // getters...
}

优点:

  • 可以根据服务器性能分配流量
  • 充分利用高性能服务器
  • 平滑流量分配

缺点:

  • 权重设置需要人工配置
  • 不能动态适应服务器负载变化

适用场景:

  • 服务器性能差异较大
  • 可以预估服务器处理能力
  • 需要优先使用高性能服务器

# 3.3 随机(Random)

原理:随机选择一台服务器处理请求。

public class RandomLoadBalancer {
    
    private List<Server> servers;
    private Random random = new Random();
    
    public Server select() {
        if (servers.isEmpty()) {
            return null;
        }
        
        int index = random.nextInt(servers.size());
        return servers.get(index);
    }
}

优点:

  • 实现简单
  • 无需维护状态
  • 长期看分配均匀

缺点:

  • 短期可能分配不均
  • 不考虑服务器负载

适用场景:

  • 服务器性能相近
  • 对短期分配均匀性要求不高
  • 无状态服务

# 3.4 加权随机(Weighted Random)

原理:根据权重随机选择服务器。

public class WeightedRandomLoadBalancer {
    
    private List<WeightedServer> servers;
    private Random random = new Random();
    
    public Server select() {
        int totalWeight = servers.stream()
            .mapToInt(WeightedServer::getWeight)
            .sum();
        
        int randomWeight = random.nextInt(totalWeight);
        
        int currentSum = 0;
        for (WeightedServer server : servers) {
            currentSum += server.getWeight();
            if (randomWeight < currentSum) {
                return server.getServer();
            }
        }
        
        return servers.get(0).getServer();
    }
}

# 3.5 最少连接(Least Connections)

原理:将请求分配给当前活跃连接数最少的服务器。

public class LeastConnectionsLoadBalancer {
    
    private List<ServerWithConnections> servers;
    
    public Server select() {
        return servers.stream()
            .min(Comparator.comparingInt(ServerWithConnections::getActiveConnections))
            .map(ServerWithConnections::getServer)
            .orElse(null);
    }
    
    public void onRequestStart(Server server) {
        servers.stream()
            .filter(s -> s.getServer().equals(server))
            .forEach(ServerWithConnections::incrementConnections);
    }
    
    public void onRequestEnd(Server server) {
        servers.stream()
            .filter(s -> s.getServer().equals(server))
            .forEach(ServerWithConnections::decrementConnections);
    }
}

class ServerWithConnections {
    private Server server;
    private AtomicInteger activeConnections = new AtomicInteger(0);
    
    public void incrementConnections() {
        activeConnections.incrementAndGet();
    }
    
    public void decrementConnections() {
        activeConnections.decrementAndGet();
    }
    
    public int getActiveConnections() {
        return activeConnections.get();
    }
    
    // getters...
}

优点:

  • 考虑服务器当前负载
  • 动态适应请求处理速度
  • 避免某台服务器过载

缺点:

  • 需要维护连接数状态
  • 实现相对复杂
  • 不适合短连接场景

适用场景:

  • 长连接服务(如WebSocket、数据库连接)
  • 请求处理时间差异大
  • 服务器性能有差异

# 3.6 加权最少连接(Weighted Least Connections)

原理:结合权重和连接数,计算 连接数/权重 最小的服务器。

public class WeightedLeastConnectionsLoadBalancer {
    
    private List<WeightedServerWithConnections> servers;
    
    public Server select() {
        return servers.stream()
            .min(Comparator.comparingDouble(s -> 
                (double) s.getActiveConnections() / s.getWeight()))
            .map(WeightedServerWithConnections::getServer)
            .orElse(null);
    }
}

class WeightedServerWithConnections {
    private Server server;
    private int weight;
    private AtomicInteger activeConnections = new AtomicInteger(0);
    
    // methods...
}

# 3.7 IP哈希(IP Hash)

原理:根据客户端IP计算哈希值,相同IP总是路由到同一服务器。

public class IpHashLoadBalancer {
    
    private List<Server> servers;
    
    public Server select(String clientIp) {
        if (servers.isEmpty()) {
            return null;
        }
        
        int hash = clientIp.hashCode();
        int index = Math.abs(hash) % servers.size();
        return servers.get(index);
    }
}

优点:

  • 天然实现会话保持
  • 实现简单
  • 相同客户端总是访问同一服务器

缺点:

  • 服务器数量变化时会导致大量请求重新路由
  • 可能导致负载不均(某些IP访问量大)
  • 不考虑服务器当前负载

适用场景:

  • 需要会话保持的有状态应用
  • 服务器数量相对稳定
  • 客户端IP分布均匀

# 3.8 一致性哈希(Consistent Hash)

原理:使用哈希环,将服务器和请求都映射到环上,请求路由到顺时针最近的服务器。

public class ConsistentHashLoadBalancer {
    
    private TreeMap<Integer, Server> hashRing = new TreeMap<>();
    private int virtualNodes = 150;  // 每个真实节点对应的虚拟节点数
    
    public void addServer(Server server) {
        for (int i = 0; i < virtualNodes; i++) {
            String virtualNodeName = server.getAddress() + "#" + i;
            int hash = hash(virtualNodeName);
            hashRing.put(hash, server);
        }
    }
    
    public void removeServer(Server server) {
        for (int i = 0; i < virtualNodes; i++) {
            String virtualNodeName = server.getAddress() + "#" + i;
            int hash = hash(virtualNodeName);
            hashRing.remove(hash);
        }
    }
    
    public Server select(String key) {
        if (hashRing.isEmpty()) {
            return null;
        }
        
        int hash = hash(key);
        
        // 找到顺时针方向第一个节点
        Map.Entry<Integer, Server> entry = hashRing.ceilingEntry(hash);
        if (entry == null) {
            // 如果没找到,返回环上第一个节点
            entry = hashRing.firstEntry();
        }
        
        return entry.getValue();
    }
    
    private int hash(String key) {
        // 使用MurmurHash或其他哈希算法
        return key.hashCode();
    }
}

优点:

  • 服务器增减时影响范围小(只影响相邻节点)
  • 实现会话保持
  • 负载分布相对均匀(通过虚拟节点)

缺点:

  • 实现相对复杂
  • 需要维护哈希环
  • 不考虑服务器实时负载

适用场景:

  • 分布式缓存(如Redis集群、Memcached集群)
  • 服务器经常扩缩容
  • 需要会话保持

# 3.9 最短响应时间(Least Response Time)

原理:选择平均响应时间最短的服务器。

public class LeastResponseTimeLoadBalancer {
    
    private List<ServerWithMetrics> servers;
    
    public Server select() {
        return servers.stream()
            .min(Comparator.comparingLong(ServerWithMetrics::getAvgResponseTime))
            .map(ServerWithMetrics::getServer)
            .orElse(null);
    }
    
    public void recordResponseTime(Server server, long responseTime) {
        servers.stream()
            .filter(s -> s.getServer().equals(server))
            .forEach(s -> s.updateResponseTime(responseTime));
    }
}

class ServerWithMetrics {
    private Server server;
    private AtomicLong totalResponseTime = new AtomicLong(0);
    private AtomicLong requestCount = new AtomicLong(0);
    
    public void updateResponseTime(long responseTime) {
        totalResponseTime.addAndGet(responseTime);
        requestCount.incrementAndGet();
    }
    
    public long getAvgResponseTime() {
        long count = requestCount.get();
        return count == 0 ? 0 : totalResponseTime.get() / count;
    }
    
    // getters...
}

优点:

  • 考虑服务器实际性能
  • 动态适应服务器负载变化
  • 优先使用响应快的服务器

缺点:

  • 需要收集和维护响应时间数据
  • 实现复杂
  • 需要定期重置统计数据

适用场景:

  • 服务器性能差异大
  • 请求处理时间波动大
  • 对响应时间敏感的业务

# 四、负载均衡算法选择指南

# 4.1 算法对比总结

算法 复杂度 会话保持 动态适应 适用场景
轮询 低 ✗ ✗ 无状态、性能相近
加权轮询 低 ✗ ✗ 性能差异大
随机 低 ✗ ✗ 简单场景
加权随机 低 ✗ ✗ 性能差异大
最少连接 中 ✗ ✓ 长连接、请求耗时差异大
加权最少连接 中 ✗ ✓ 性能差异大+长连接
IP哈希 低 ✓ ✗ 会话保持、稳定集群
一致性哈希 高 ✓ ✗ 分布式缓存、动态扩缩容
最短响应时间 高 ✗ ✓ 性能敏感、实时优化

# 4.2 选择决策树

需要会话保持?
  ├─ 是 → 服务器经常变化?
  │       ├─ 是 → 一致性哈希
  │       └─ 否 → IP哈希
  └─ 否 → 长连接服务?
          ├─ 是 → 服务器性能差异大?
          │       ├─ 是 → 加权最少连接
          │       └─ 否 → 最少连接
          └─ 否 → 服务器性能差异大?
                  ├─ 是 → 加权轮询
                  └─ 否 → 轮询

# 4.3 常见场景推荐

Web应用:

  • 无状态:轮询或加权轮询
  • 有状态:IP哈希或一致性哈希

API网关:

  • 加权轮询 + 健康检查
  • 支持动态权重调整

数据库连接池:

  • 最少连接或加权最少连接
  • 避免某个节点过载

分布式缓存:

  • 一致性哈希
  • 方便扩缩容

长连接服务(WebSocket):

  • 最少连接
  • 考虑连接生命周期

# 五、负载均衡的实现方式

# 5.1 软件负载均衡

# 5.1.1 Nginx

特点:

  • 七层负载均衡
  • 高性能,单机可处理数万并发
  • 支持多种负载均衡算法
  • 功能丰富,易于配置

配置示例:

http {
    # 定义后端服务器组
    upstream backend {
        # 负载均衡算法
        # 默认为轮询,可选: ip_hash、least_conn
        least_conn;
        
        # 服务器列表
        server 192.168.1.10:8080 weight=3 max_fails=2 fail_timeout=30s;
        server 192.168.1.11:8080 weight=2;
        server 192.168.1.12:8080 weight=1;
        
        # 备用服务器
        server 192.168.1.13:8080 backup;
        
        # 健康检查
        keepalive 32;
    }
    
    server {
        listen 80;
        server_name api.example.com;
        
        location / {
            proxy_pass http://backend;
            
            # 传递客户端真实IP
            proxy_set_header X-Real-IP $remote_addr;
            proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
            proxy_set_header Host $host;
            
            # 超时设置
            proxy_connect_timeout 5s;
            proxy_send_timeout 10s;
            proxy_read_timeout 10s;
        }
    }
}

会话保持(Sticky Session):

upstream backend {
    ip_hash;  # 基于IP的会话保持
    
    server 192.168.1.10:8080;
    server 192.168.1.11:8080;
}

或使用第三方模块实现更灵活的会话保持:

upstream backend {
    # 基于Cookie的会话保持
    sticky cookie srv_id expires=1h domain=.example.com path=/;
    
    server 192.168.1.10:8080;
    server 192.168.1.11:8080;
}

# 5.1.2 HAProxy

特点:

  • 支持四层和七层负载均衡
  • 性能极高,专为负载均衡设计
  • 强大的健康检查机制
  • 丰富的统计信息

配置示例:

global
    maxconn 50000
    log 127.0.0.1 local0
    
defaults
    mode http
    timeout connect 5000ms
    timeout client 50000ms
    timeout server 50000ms
    
# 前端配置
frontend http-in
    bind *:80
    default_backend servers
    
# 后端配置
backend servers
    balance roundrobin
    
    # 健康检查
    option httpchk GET /health
    http-check expect status 200
    
    # 服务器列表
    server server1 192.168.1.10:8080 check weight 3
    server server2 192.168.1.11:8080 check weight 2
    server server3 192.168.1.12:8080 check weight 1
    server server4 192.168.1.13:8080 check backup
    
# 统计页面
listen stats
    bind *:8080
    stats enable
    stats uri /stats
    stats refresh 30s

# 5.1.3 LVS (Linux Virtual Server)

特点:

  • 四层负载均衡
  • 性能极高,可达百万并发
  • 工作在内核态
  • 配置相对复杂

工作模式:

NAT模式:

客户端 → LVS(NAT) → 真实服务器
           ↓
      修改目标IP

DR模式(Direct Routing):

客户端 → LVS(修改MAC地址) → 真实服务器
                              ↓
                        直接响应客户端

TUN模式(IP Tunneling):

客户端 → LVS(IP隧道) → 真实服务器
                          ↓
                    直接响应客户端

# 5.2 硬件负载均衡

典型产品:

  • F5 BIG-IP:业界标杆,功能全面,价格昂贵
  • Citrix NetScaler:应用交付控制器,功能强大
  • A10 Networks:性价比高,性能优秀

特点:

  • 性能极高,可达数百万并发
  • 稳定可靠,专用硬件
  • 功能全面,支持复杂场景
  • 价格昂贵,维护成本高

适用场景:

  • 超大流量场景(每秒百万级请求)
  • 对可用性要求极高的核心业务
  • 有充足预算的企业

# 5.3 云负载均衡

主流云厂商产品:

云厂商 产品名称 特点
AWS ELB/ALB/NLB 经典/应用/网络负载均衡
阿里云 SLB 支持四层和七层
腾讯云 CLB 高可用、高性能
Azure Load Balancer 区域/全局负载均衡

优势:

  • 无需购买硬件,按需付费
  • 自动扩展,弹性伸缩
  • 高可用,多地域部署
  • 与云服务深度集成

配置示例(阿里云SLB):

// 通过SDK配置SLB
LoadBalancerClient client = new LoadBalancerClient();

CreateLoadBalancerRequest request = new CreateLoadBalancerRequest();
request.setLoadBalancerName("my-lb");
request.setAddressType("internet");  // 公网
request.setLoadBalancerSpec("slb.s2.small");

CreateLoadBalancerResponse response = client.createLoadBalancer(request);
String loadBalancerId = response.getLoadBalancerId();

// 添加后端服务器
AddBackendServersRequest addRequest = new AddBackendServersRequest();
addRequest.setLoadBalancerId(loadBalancerId);
addRequest.setBackendServers([
    {"ServerId": "i-abc123", "Weight": 100},
    {"ServerId": "i-def456", "Weight": 100}
]);

client.addBackendServers(addRequest);

# 六、健康检查与故障转移

# 6.1 健康检查机制

主动健康检查:

public class HealthChecker {
    
    private ScheduledExecutorService scheduler = Executors.newScheduledThreadPool(1);
    private List<Server> servers;
    private Set<Server> healthyServers = new ConcurrentHashMap().newKeySet();
    
    public void startHealthCheck() {
        scheduler.scheduleAtFixedRate(() -> {
            for (Server server : servers) {
                if (isHealthy(server)) {
                    healthyServers.add(server);
                } else {
                    healthyServers.remove(server);
                }
            }
        }, 0, 5, TimeUnit.SECONDS);  // 每5秒检查一次
    }
    
    private boolean isHealthy(Server server) {
        try {
            // HTTP健康检查
            HttpClient client = HttpClient.newHttpClient();
            HttpRequest request = HttpRequest.newBuilder()
                .uri(URI.create("http://" + server.getAddress() + "/health"))
                .timeout(Duration.ofSeconds(2))
                .GET()
                .build();
            
            HttpResponse<String> response = client.send(request, 
                HttpResponse.BodyHandlers.ofString());
            
            return response.statusCode() == 200;
        } catch (Exception e) {
            return false;
        }
    }
    
    public List<Server> getHealthyServers() {
        return new ArrayList<>(healthyServers);
    }
}

被动健康检查:

public class PassiveHealthChecker {
    
    private Map<Server, FailureCounter> failureCounters = new ConcurrentHashMap<>();
    private int maxFailures = 3;
    private long failureWindow = 30000;  // 30秒
    
    public void recordFailure(Server server) {
        FailureCounter counter = failureCounters.computeIfAbsent(
            server, k -> new FailureCounter());
        
        counter.recordFailure();
        
        if (counter.getFailureCount() >= maxFailures) {
            markUnhealthy(server);
        }
    }
    
    public void recordSuccess(Server server) {
        FailureCounter counter = failureCounters.get(server);
        if (counter != null) {
            counter.reset();
        }
    }
    
    private void markUnhealthy(Server server) {
        // 将服务器标记为不健康
        server.setHealthy(false);
        
        // 启动恢复定时器
        scheduleRecovery(server);
    }
    
    private void scheduleRecovery(Server server) {
        ScheduledExecutorService scheduler = Executors.newScheduledThreadPool(1);
        scheduler.schedule(() -> {
            // 尝试恢复
            server.setHealthy(true);
            failureCounters.remove(server);
        }, failureWindow, TimeUnit.MILLISECONDS);
    }
}

class FailureCounter {
    private AtomicInteger count = new AtomicInteger(0);
    private long lastFailureTime = 0;
    
    public void recordFailure() {
        count.incrementAndGet();
        lastFailureTime = System.currentTimeMillis();
    }
    
    public void reset() {
        count.set(0);
    }
    
    public int getFailureCount() {
        return count.get();
    }
}

# 6.2 故障转移策略

快速故障转移:

public class FailoverLoadBalancer {
    
    private LoadBalancer primaryBalancer;
    private LoadBalancer backupBalancer;
    
    public Server select(String key) {
        try {
            Server server = primaryBalancer.select(key);
            if (server != null && server.isHealthy()) {
                return server;
            }
        } catch (Exception e) {
            // 主负载均衡器失败,使用备份
        }
        
        // 使用备份负载均衡器
        return backupBalancer.select(key);
    }
}

重试机制:

public class RetryableLoadBalancer {
    
    private LoadBalancer loadBalancer;
    private int maxRetries = 3;
    
    public Response execute(Request request) {
        Exception lastException = null;
        
        for (int i = 0; i < maxRetries; i++) {
            Server server = loadBalancer.select(request.getKey());
            
            try {
                Response response = sendRequest(server, request);
                return response;
            } catch (Exception e) {
                lastException = e;
                // 标记服务器故障
                markServerFailed(server);
                
                // 继续重试其他服务器
            }
        }
        
        throw new RuntimeException("All retries failed", lastException);
    }
    
    private void markServerFailed(Server server) {
        // 通知健康检查器
        healthChecker.recordFailure(server);
    }
}

# 七、会话保持(Session Persistence)

# 7.1 会话保持的必要性

问题场景:

用户第一次请求 → 服务器A → 登录成功,Session存储在A
用户第二次请求 → 服务器B → Session不存在,需要重新登录

解决方案:

  1. 无状态化:推荐方案,使用JWT等Token机制
  2. Session复制:服务器间同步Session
  3. Session集中存储:使用Redis等存储Session
  4. 会话保持:确保同一用户请求同一服务器

# 7.2 基于Cookie的会话保持

public class CookieBasedSessionLoadBalancer {
    
    private LoadBalancer loadBalancer;
    private String sessionCookieName = "JSESSIONID";
    
    public Server select(HttpRequest request) {
        // 尝试从Cookie中获取服务器标识
        String serverId = extractServerIdFromCookie(request);
        
        if (serverId != null) {
            Server server = findServerById(serverId);
            if (server != null && server.isHealthy()) {
                return server;
            }
        }
        
        // 没有绑定服务器,使用负载均衡算法选择
        Server server = loadBalancer.select(request.getKey());
        
        // 设置Cookie绑定服务器
        setServerIdCookie(request.getResponse(), server.getId());
        
        return server;
    }
    
    private String extractServerIdFromCookie(HttpRequest request) {
        Cookie[] cookies = request.getCookies();
        for (Cookie cookie : cookies) {
            if (sessionCookieName.equals(cookie.getName())) {
                return cookie.getValue();
            }
        }
        return null;
    }
    
    private void setServerIdCookie(HttpResponse response, String serverId) {
        Cookie cookie = new Cookie(sessionCookieName, serverId);
        cookie.setMaxAge(3600);  // 1小时
        cookie.setPath("/");
        response.addCookie(cookie);
    }
}

# 7.3 基于IP的会话保持

前面已介绍IP哈希算法,这里不再赘述。

# 7.4 会话复制

// 使用Spring Session + Redis实现Session共享
@Configuration
@EnableRedisHttpSession
public class SessionConfig {
    
    @Bean
    public LettuceConnectionFactory connectionFactory() {
        return new LettuceConnectionFactory();
    }
}

配置后,所有服务器共享Redis中的Session,无需会话保持。


# 八、负载均衡的最佳实践

# 8.1 监控与告警

关键指标:

public class LoadBalancerMetrics {
    
    // 请求分布
    private Map<Server, AtomicLong> requestCounts = new ConcurrentHashMap<>();
    
    // 响应时间
    private Map<Server, AtomicLong> responseTimes = new ConcurrentHashMap<>();
    
    // 错误率
    private Map<Server, AtomicLong> errorCounts = new ConcurrentHashMap<>();
    
    // 健康状态
    private Map<Server, Boolean> healthStatus = new ConcurrentHashMap<>();
    
    public void recordRequest(Server server, long responseTime, boolean success) {
        requestCounts.computeIfAbsent(server, k -> new AtomicLong())
            .incrementAndGet();
        
        responseTimes.computeIfAbsent(server, k -> new AtomicLong())
            .addAndGet(responseTime);
        
        if (!success) {
            errorCounts.computeIfAbsent(server, k -> new AtomicLong())
                .incrementAndGet();
        }
    }
    
    public LoadBalancerStats getStats() {
        LoadBalancerStats stats = new LoadBalancerStats();
        
        for (Server server : requestCounts.keySet()) {
            ServerStats serverStats = new ServerStats();
            serverStats.setServer(server);
            serverStats.setRequestCount(requestCounts.get(server).get());
            serverStats.setErrorCount(errorCounts.getOrDefault(server, 
                new AtomicLong()).get());
            
            long totalTime = responseTimes.get(server).get();
            long count = requestCounts.get(server).get();
            serverStats.setAvgResponseTime(count > 0 ? totalTime / count : 0);
            
            stats.addServerStats(serverStats);
        }
        
        return stats;
    }
}

# 8.2 动态权重调整

public class DynamicWeightAdjuster {
    
    private ScheduledExecutorService scheduler = Executors.newScheduledThreadPool(1);
    private LoadBalancerMetrics metrics;
    private WeightedLoadBalancer loadBalancer;
    
    public void startAdjustment() {
        scheduler.scheduleAtFixedRate(() -> {
            adjustWeights();
        }, 0, 60, TimeUnit.SECONDS);  // 每分钟调整一次
    }
    
    private void adjustWeights() {
        LoadBalancerStats stats = metrics.getStats();
        
        for (ServerStats serverStats : stats.getServerStats()) {
            int newWeight = calculateWeight(serverStats);
            loadBalancer.updateWeight(serverStats.getServer(), newWeight);
        }
    }
    
    private int calculateWeight(ServerStats stats) {
        // 基于响应时间和错误率计算权重
        long avgResponseTime = stats.getAvgResponseTime();
        double errorRate = stats.getErrorRate();
        
        // 响应时间越短,权重越高
        int timeWeight = (int) (1000 / Math.max(avgResponseTime, 1));
        
        // 错误率越低,权重越高
        int errorWeight = (int) ((1 - errorRate) * 100);
        
        // 综合权重
        return Math.max((timeWeight + errorWeight) / 2, 1);
    }
}

# 8.3 优雅上下线

优雅下线:

public class GracefulShutdown {
    
    private LoadBalancer loadBalancer;
    private Server server;
    
    public void shutdown() {
        // 1. 从负载均衡器中移除服务器
        loadBalancer.removeServer(server);
        
        // 2. 等待已有请求处理完成
        waitForRequestsToComplete();
        
        // 3. 关闭服务器
        server.stop();
    }
    
    private void waitForRequestsToComplete() {
        long timeout = 30000;  // 30秒超时
        long start = System.currentTimeMillis();
        
        while (server.getActiveConnections() > 0) {
            if (System.currentTimeMillis() - start > timeout) {
                break;
            }
            
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
                break;
            }
        }
    }
}

优雅上线:

public class GracefulStartup {
    
    private LoadBalancer loadBalancer;
    private Server server;
    private HealthChecker healthChecker;
    
    public void startup() {
        // 1. 启动服务器
        server.start();
        
        // 2. 预热服务器
        warmup();
        
        // 3. 健康检查通过后加入负载均衡器
        if (healthChecker.isHealthy(server)) {
            // 初始权重设低,逐步提升
            loadBalancer.addServer(server, 1);
            
            // 逐步提升权重
            graduallyIncreaseWeight();
        }
    }
    
    private void warmup() {
        // 发送预热请求,让JIT编译、缓存等生效
        for (int i = 0; i < 100; i++) {
            sendWarmupRequest(server);
        }
    }
    
    private void graduallyIncreaseWeight() {
        ScheduledExecutorService scheduler = Executors.newScheduledThreadPool(1);
        
        scheduler.scheduleAtFixedRate(() -> {
            int currentWeight = loadBalancer.getWeight(server);
            int targetWeight = 10;
            
            if (currentWeight < targetWeight) {
                loadBalancer.updateWeight(server, currentWeight + 1);
            } else {
                scheduler.shutdown();
            }
        }, 0, 10, TimeUnit.SECONDS);
    }
}

# 8.4 容量规划

评估公式:

单台服务器容量 = 单台QPS × 平均响应时间
所需服务器数量 = 峰值QPS / 单台服务器容量 × 冗余系数(1.5-2)

示例:

假设:
- 峰值QPS: 10000
- 单台服务器QPS: 1000
- 平均响应时间: 100ms
- 冗余系数: 1.5

所需服务器数量 = 10000 / 1000 × 1.5 = 15台

# 九、总结:构建高效负载均衡系统的关键要点

负载均衡是现代分布式系统的基础设施,是实现高可用、高性能的关键技术。

核心要点回顾:

  1. 选择合适的算法:根据业务特点选择负载均衡算法

    • 无状态服务:轮询、加权轮询
    • 长连接服务:最少连接
    • 有状态服务:IP哈希、一致性哈希
    • 分布式缓存:一致性哈希
  2. 健康检查必不可少:主动+被动健康检查,快速发现故障

  3. 会话保持要权衡:优先无状态化,必要时使用会话保持

  4. 监控告警很重要:实时监控负载分布、响应时间、错误率

  5. 优雅上下线:避免请求失败,保证服务质量

实践建议:

  • 从简单开始:优先使用轮询等简单算法,根据需要优化
  • 选型要务实:软件负载均衡满足大多数场景,硬件负载均衡仅用于极端场景
  • 云原生优先:云环境下优先使用云厂商负载均衡服务
  • 动态调整:根据监控数据动态调整权重和策略
  • 定期演练:定期进行故障演练,验证故障转移机制

进阶方向:

  • Service Mesh:使用Istio、Linkerd等实现更智能的流量管理
  • 全局负载均衡:跨地域、跨云的全局流量调度
  • 智能负载均衡:基于机器学习的流量预测和调度

当你和你的团队掌握了负载均衡的核心原理和最佳实践,你们将能够:

  • 构建高可用、高性能的分布式系统
  • 从容应对流量增长和服务器故障
  • 实现系统的水平扩展和弹性伸缩
  • 为用户提供稳定、快速的服务体验

祝你变得更强!

编辑 (opens new window)
#性能优化#高性能#负载均衡#架构设计
上次更新: 2025/12/17
高性能-数据库性能优化
高性能-CDN与静态资源优化

← 高性能-数据库性能优化 高性能-CDN与静态资源优化→

最近更新
01
AI编程时代的一些心得
09-11
02
Claude Code 最佳实践(个人版)
08-01
03
高扩展-弹性伸缩设计
06-05
更多文章>
Theme by Vdoing | Copyright © 2018-2025 京ICP备2021021832号-2 | MIT License
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式