HiHuo
首页
博客
手册
工具
关于
首页
博客
手册
工具
关于
  • 分布式架构模式

    • 分布式架构模式手册
    • 第1章:分布式一致性
    • 第2章:分布式锁
    • 第3章:分布式协调
    • 第4章:服务发现与注册
    • 第5章:负载均衡
    • 第6章:熔断降级
    • 第7章:DDD领域驱动设计
    • 第8章:CQRS与Event Sourcing

第5章:负载均衡

为什么需要负载均衡

单机限制

单机系统的问题:

┌────────┐      ┌──────────┐
│ Client │─────→│ Server   │
└────────┘      │ 1核2G    │
                │ Max:1000 QPS│
                └──────────┘

问题:
 性能瓶颈(单机处理能力有限)
 单点故障(服务器宕机=全部不可用)
 无法扩展(流量增长无法应对)

负载均衡的作用

多实例 + 负载均衡:

┌────────┐      ┌─────────────┐
│ Client │─────→│Load Balancer│
└────────┘      └──────┬──────┘
                   ┌───┴───┬───────┐
                   ↓       ↓       ↓
              ┌────────┬────────┬────────┐
              │Server 1│Server 2│Server 3│
              │1000 QPS│1000 QPS│1000 QPS│
              └────────┴────────┴────────┘
              总容量:3000 QPS

优势:
 提高性能(水平扩展)
 高可用(单机故障不影响整体)
 可扩展(动态增减实例)
 流量分发(合理利用资源)

负载均衡层次

1. DNS负载均衡(第一层)

原理:通过DNS解析返回不同的IP地址

用户请求:www.example.com

DNS解析:
Round 1 → 192.168.1.10
Round 2 → 192.168.1.11
Round 3 → 192.168.1.12
Round 4 → 192.168.1.10 (循环)

配置示例:

; DNS Zone文件
www.example.com.  IN  A  192.168.1.10
www.example.com.  IN  A  192.168.1.11
www.example.com.  IN  A  192.168.1.12

优点:

  • 简单易用
  • 全局负载均衡(跨地域)
  • 无单点故障

缺点:

  • DNS缓存问题(无法实时生效)
  • 无法感知服务器状态
  • 分配不均(客户端缓存)

适用场景:

  • 跨地域流量分发
  • CDN调度
  • 粗粒度负载均衡

2. 四层负载均衡(传输层)

原理:基于IP+端口转发(TCP/UDP)

Client → LVS (Layer 4 Load Balancer)
         ↓ (修改目标IP/端口)
         Server 1/2/3

特点:
- 工作在传输层(OSI第4层)
- 不解析应用层协议
- 转发速度快(性能高)

典型实现:LVS(Linux Virtual Server)

LVS三种模式:

1. NAT模式

Client → LVS (DNAT) → Real Server
                        ↓
Client ← LVS (SNAT) ← Real Server

特点:
- LVS修改源/目标IP
- 请求和响应都经过LVS
- LVS压力大(性能瓶颈)

2. DR模式(Direct Routing)

Client → LVS (修改MAC地址) → Real Server
         ↑                         ↓
Client ←─────────────────────────┘
        (直接返回,不经过LVS)

特点:
- 请求经过LVS,响应直接返回
- LVS只修改MAC地址(二层转发)
- 性能高(推荐)

3. TUN模式(IP Tunneling)

Client → LVS (IP封装) → Real Server
         ↑                    ↓
Client ←────────────────────┘
        (解封装后直接返回)

特点:
- 支持跨网段
- 性能好
- 配置复杂

优点:

  • 性能极高(100万+ QPS)
  • 支持所有TCP/UDP协议
  • 对应用透明

缺点:

  • 无法做应用层路由(如:URL路由)
  • 配置复杂

3. 七层负载均衡(应用层)

原理:基于HTTP协议内容进行路由

Client → Nginx (Layer 7 Load Balancer)
         ↓ (解析HTTP请求)
         根据URL/Header/Cookie等路由
         ↓
         Server 1/2/3

特点:
- 工作在应用层(OSI第7层)
- 可以解析HTTP内容
- 灵活的路由策略

典型实现:Nginx、HAProxy

Nginx配置示例:

upstream backend {
    # 负载均衡算法
    least_conn;  # 最少连接数

    # 服务器列表
    server 192.168.1.10:8080 weight=3;
    server 192.168.1.11:8080 weight=2;
    server 192.168.1.12:8080 weight=1;

    # 健康检查
    server 192.168.1.13:8080 backup;  # 备用服务器
}

server {
    listen 80;
    server_name www.example.com;

    # 基于URL路由
    location /api {
        proxy_pass http://backend;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
    }

    # 基于Header路由
    location /admin {
        if ($http_x_admin = "true") {
            proxy_pass http://admin_backend;
        }
    }

    # 会话保持(基于Cookie)
    location /login {
        proxy_pass http://backend;
        proxy_cookie_path / "/; Secure; HttpOnly";
    }
}

优点:

  • 灵活的路由策略(URL、Header、Cookie)
  • 支持会话保持
  • 可以做内容缓存
  • SSL卸载

缺点:

  • 性能低于四层(需要解析HTTP)
  • 只支持HTTP/HTTPS

负载均衡算法

1. 轮询(Round Robin)

原理:按顺序依次分配请求

Request 1 → Server 1
Request 2 → Server 2
Request 3 → Server 3
Request 4 → Server 1 (循环)

代码实现:

type RoundRobinBalancer struct {
    servers []string
    index   int
    mu      sync.Mutex
}

func (rb *RoundRobinBalancer) Next() string {
    rb.mu.Lock()
    defer rb.mu.Unlock()

    server := rb.servers[rb.index]
    rb.index = (rb.index + 1) % len(rb.servers)

    return server
}

// 使用示例
func main() {
    balancer := &RoundRobinBalancer{
        servers: []string{
            "192.168.1.10:8080",
            "192.168.1.11:8080",
            "192.168.1.12:8080",
        },
    }

    for i := 0; i < 10; i++ {
        server := balancer.Next()
        fmt.Printf("Request %d → %s\n", i+1, server)
    }
}

// 输出:
// Request 1 → 192.168.1.10:8080
// Request 2 → 192.168.1.11:8080
// Request 3 → 192.168.1.12:8080
// Request 4 → 192.168.1.10:8080
// ...

优点:

  • 简单公平
  • 实现容易

缺点:

  • 不考虑服务器性能差异
  • 不考虑服务器当前负载

适用场景:

  • 服务器性能相同
  • 请求处理时间相近

2. 加权轮询(Weighted Round Robin)

原理:根据服务器权重分配请求

Server 1: weight=3
Server 2: weight=2
Server 3: weight=1

分配比例:3:2:1
Server 1 → 3/6 = 50%
Server 2 → 2/6 = 33%
Server 3 → 1/6 = 17%

代码实现:

type WeightedRoundRobinBalancer struct {
    servers []Server
    index   int
    mu      sync.Mutex
}

type Server struct {
    Addr            string
    Weight          int
    CurrentWeight   int
    EffectiveWeight int
}

func (wrb *WeightedRoundRobinBalancer) Next() string {
    wrb.mu.Lock()
    defer wrb.mu.Unlock()

    totalWeight := 0
    var selected *Server

    // 平滑加权轮询算法(Nginx使用)
    for i := range wrb.servers {
        server := &wrb.servers[i]

        // 当前权重 += 有效权重
        server.CurrentWeight += server.EffectiveWeight
        totalWeight += server.EffectiveWeight

        // 选择当前权重最大的
        if selected == nil || server.CurrentWeight > selected.CurrentWeight {
            selected = server
        }
    }

    // 选中的服务器权重减去总权重
    if selected != nil {
        selected.CurrentWeight -= totalWeight
        return selected.Addr
    }

    return ""
}

// 使用示例
func main() {
    balancer := &WeightedRoundRobinBalancer{
        servers: []Server{
            {Addr: "192.168.1.10:8080", Weight: 3, EffectiveWeight: 3},
            {Addr: "192.168.1.11:8080", Weight: 2, EffectiveWeight: 2},
            {Addr: "192.168.1.12:8080", Weight: 1, EffectiveWeight: 1},
        },
    }

    // 统计分配次数
    count := make(map[string]int)
    for i := 0; i < 60; i++ {
        server := balancer.Next()
        count[server]++
    }

    for addr, cnt := range count {
        fmt.Printf("%s: %d (%.1f%%)\n", addr, cnt, float64(cnt)/60*100)
    }
}

// 输出:
// 192.168.1.10:8080: 30 (50.0%)
// 192.168.1.11:8080: 20 (33.3%)
// 192.168.1.12:8080: 10 (16.7%)

优点:

  • 考虑服务器性能差异
  • 平滑分配(Nginx算法)

缺点:

  • 权重配置需要人工调整

适用场景:

  • 服务器性能不同(高配置服务器weight更大)
  • 需要精细化流量控制

3. 最少连接(Least Connections)

原理:选择当前连接数最少的服务器

Server 1: 10 connections
Server 2: 5 connections  ← 选择
Server 3: 8 connections

下次请求:
Server 1: 10 connections
Server 2: 6 connections
Server 3: 8 connections  ← 如果Server 2的请求完成

代码实现:

type LeastConnectionBalancer struct {
    servers []ServerWithConn
    mu      sync.RWMutex
}

type ServerWithConn struct {
    Addr        string
    Connections int
}

func (lcb *LeastConnectionBalancer) Next() string {
    lcb.mu.Lock()
    defer lcb.mu.Unlock()

    // 找到连接数最少的服务器
    minIndex := 0
    minConn := lcb.servers[0].Connections

    for i := 1; i < len(lcb.servers); i++ {
        if lcb.servers[i].Connections < minConn {
            minIndex = i
            minConn = lcb.servers[i].Connections
        }
    }

    // 增加连接数
    lcb.servers[minIndex].Connections++

    return lcb.servers[minIndex].Addr
}

func (lcb *LeastConnectionBalancer) Release(addr string) {
    lcb.mu.Lock()
    defer lcb.mu.Unlock()

    for i := range lcb.servers {
        if lcb.servers[i].Addr == addr {
            lcb.servers[i].Connections--
            break
        }
    }
}

// 使用示例
func main() {
    balancer := &LeastConnectionBalancer{
        servers: []ServerWithConn{
            {Addr: "192.168.1.10:8080", Connections: 0},
            {Addr: "192.168.1.11:8080", Connections: 0},
            {Addr: "192.168.1.12:8080", Connections: 0},
        },
    }

    // 模拟请求
    for i := 0; i < 5; i++ {
        server := balancer.Next()
        fmt.Printf("Request %d → %s\n", i+1, server)

        // 模拟部分请求完成
        if i == 2 {
            balancer.Release("192.168.1.10:8080")
        }
    }
}

优点:

  • 动态感知服务器负载
  • 适合长连接场景

缺点:

  • 实现复杂(需要维护连接数)
  • 有状态(不适合无状态场景)

适用场景:

  • 长连接(WebSocket、数据库连接池)
  • 请求处理时间差异大

4. 一致性哈希(Consistent Hashing)

原理:将服务器和请求映射到同一个哈希环上

哈希环(0 ~ 2^32-1):

         Server1 (hash=100)
              ↓
    ┌─────────●─────────┐
    │                   │
Server3 ●             ● Server2
(hash=300)         (hash=200)
    │                   │
    └───────────────────┘

请求路由:
Request(key="user:123") → hash(key) = 150
→ 顺时针找到第一个服务器 → Server2

代码实现:

package main

import (
    "hash/crc32"
    "sort"
    "strconv"
    "sync"
)

type ConsistentHash struct {
    hashFunc    func([]byte) uint32
    replicas    int               // 虚拟节点数
    keys        []uint32          // 排序的哈希环
    hashMap     map[uint32]string // 哈希值 -> 真实节点
    mu          sync.RWMutex
}

func NewConsistentHash(replicas int) *ConsistentHash {
    return &ConsistentHash{
        replicas: replicas,
        hashFunc: crc32.ChecksumIEEE,
        hashMap:  make(map[uint32]string),
    }
}

// 添加节点
func (ch *ConsistentHash) Add(nodes ...string) {
    ch.mu.Lock()
    defer ch.mu.Unlock()

    for _, node := range nodes {
        // 为每个真实节点创建多个虚拟节点(提高均衡性)
        for i := 0; i < ch.replicas; i++ {
            hash := ch.hashFunc([]byte(strconv.Itoa(i) + node))
            ch.keys = append(ch.keys, hash)
            ch.hashMap[hash] = node
        }
    }

    // 排序哈希环
    sort.Slice(ch.keys, func(i, j int) bool {
        return ch.keys[i] < ch.keys[j]
    })
}

// 移除节点
func (ch *ConsistentHash) Remove(node string) {
    ch.mu.Lock()
    defer ch.mu.Unlock()

    for i := 0; i < ch.replicas; i++ {
        hash := ch.hashFunc([]byte(strconv.Itoa(i) + node))

        // 从哈希环中删除
        idx := sort.Search(len(ch.keys), func(i int) bool {
            return ch.keys[i] >= hash
        })

        if idx < len(ch.keys) && ch.keys[idx] == hash {
            ch.keys = append(ch.keys[:idx], ch.keys[idx+1:]...)
        }

        delete(ch.hashMap, hash)
    }
}

// 获取节点
func (ch *ConsistentHash) Get(key string) string {
    ch.mu.RLock()
    defer ch.mu.RUnlock()

    if len(ch.keys) == 0 {
        return ""
    }

    hash := ch.hashFunc([]byte(key))

    // 顺时针找到第一个节点
    idx := sort.Search(len(ch.keys), func(i int) bool {
        return ch.keys[i] >= hash
    })

    // 环形,如果超出范围,回到第一个
    if idx == len(ch.keys) {
        idx = 0
    }

    return ch.hashMap[ch.keys[idx]]
}

// 使用示例
func main() {
    ch := NewConsistentHash(150) // 每个节点150个虚拟节点

    // 添加服务器
    ch.Add("192.168.1.10:8080", "192.168.1.11:8080", "192.168.1.12:8080")

    // 测试分布
    distribution := make(map[string]int)
    for i := 0; i < 10000; i++ {
        key := fmt.Sprintf("user:%d", i)
        server := ch.Get(key)
        distribution[server]++
    }

    fmt.Println("Distribution:")
    for server, count := range distribution {
        fmt.Printf("%s: %d (%.1f%%)\n", server, count, float64(count)/10000*100)
    }

    // 添加新节点(数据迁移少)
    fmt.Println("\nAdding new server...")
    ch.Add("192.168.1.13:8080")

    newDistribution := make(map[string]int)
    for i := 0; i < 10000; i++ {
        key := fmt.Sprintf("user:%d", i)
        server := ch.Get(key)
        newDistribution[server]++
    }

    fmt.Println("New distribution:")
    for server, count := range newDistribution {
        fmt.Printf("%s: %d (%.1f%%)\n", server, count, float64(count)/10000*100)
    }
}

优点:

  • 扩缩容时数据迁移少(只影响部分数据)
  • 同一个key总是路由到同一台服务器

缺点:

  • 实现复杂
  • 节点分布可能不均匀(需要虚拟节点)

适用场景:

  • 缓存系统(Redis Cluster)
  • 分布式存储(Cassandra)
  • 会话保持

客户端负载均衡

Spring Cloud Ribbon

原理:客户端集成负载均衡库,直接调用服务实例

┌────────────────┐
│   Service A    │
│  (Client)      │
│  ┌──────────┐  │
│  │  Ribbon  │  │ ①从注册中心获取实例列表
│  │(负载均衡)│←─┼─→ ②负载均衡算法选择实例
│  └──────────┘  │   ③直接调用
└────────┬───────┘
         ↓ ③
    ┌────────┐
    │Service B│
    │Instance │
    └────────┘

Go实现示例:

package main

import (
    "errors"
    "math/rand"
    "sync"
    "time"
)

// 服务实例
type ServiceInstance struct {
    Addr   string
    Weight int
    Health bool
}

// 客户端负载均衡器
type ClientLoadBalancer struct {
    discovery ServiceDiscovery
    cache     map[string][]*ServiceInstance
    strategy  LoadBalanceStrategy
    mu        sync.RWMutex
}

type LoadBalanceStrategy interface {
    Choose(instances []*ServiceInstance) *ServiceInstance
}

// 随机策略
type RandomStrategy struct{}

func (rs *RandomStrategy) Choose(instances []*ServiceInstance) *ServiceInstance {
    if len(instances) == 0 {
        return nil
    }
    return instances[rand.Intn(len(instances))]
}

// 加权随机策略
type WeightedRandomStrategy struct{}

func (wrs *WeightedRandomStrategy) Choose(instances []*ServiceInstance) *ServiceInstance {
    if len(instances) == 0 {
        return nil
    }

    // 计算总权重
    totalWeight := 0
    for _, instance := range instances {
        totalWeight += instance.Weight
    }

    // 随机一个权重
    randomWeight := rand.Intn(totalWeight)

    // 选择实例
    for _, instance := range instances {
        randomWeight -= instance.Weight
        if randomWeight < 0 {
            return instance
        }
    }

    return instances[0]
}

func NewClientLoadBalancer(discovery ServiceDiscovery, strategy LoadBalanceStrategy) *ClientLoadBalancer {
    lb := &ClientLoadBalancer{
        discovery: discovery,
        cache:     make(map[string][]*ServiceInstance),
        strategy:  strategy,
    }

    // 定期刷新实例列表
    go lb.refreshLoop()

    return lb
}

func (clb *ClientLoadBalancer) refreshLoop() {
    ticker := time.NewTicker(10 * time.Second)
    defer ticker.Stop()

    for range ticker.C {
        clb.mu.Lock()
        for serviceName := range clb.cache {
            instances, _ := clb.discovery.GetInstances(serviceName)
            clb.cache[serviceName] = instances
        }
        clb.mu.Unlock()
    }
}

// 选择实例
func (clb *ClientLoadBalancer) ChooseInstance(serviceName string) (*ServiceInstance, error) {
    clb.mu.RLock()
    instances, ok := clb.cache[serviceName]
    clb.mu.RUnlock()

    if !ok {
        // 首次访问,获取实例列表
        instances, _ = clb.discovery.GetInstances(serviceName)
        clb.mu.Lock()
        clb.cache[serviceName] = instances
        clb.mu.Unlock()
    }

    // 过滤健康实例
    healthyInstances := make([]*ServiceInstance, 0)
    for _, instance := range instances {
        if instance.Health {
            healthyInstances = append(healthyInstances, instance)
        }
    }

    if len(healthyInstances) == 0 {
        return nil, errors.New("no available instances")
    }

    // 负载均衡选择
    return clb.strategy.Choose(healthyInstances), nil
}

// HTTP客户端(集成负载均衡)
type ServiceClient struct {
    lb         *ClientLoadBalancer
    httpClient *http.Client
}

func NewServiceClient(lb *ClientLoadBalancer) *ServiceClient {
    return &ServiceClient{
        lb:         lb,
        httpClient: &http.Client{Timeout: 5 * time.Second},
    }
}

func (sc *ServiceClient) Call(serviceName, path string) ([]byte, error) {
    // 选择实例
    instance, err := sc.lb.ChooseInstance(serviceName)
    if err != nil {
        return nil, err
    }

    // 发起HTTP请求
    url := fmt.Sprintf("http://%s%s", instance.Addr, path)
    resp, err := sc.httpClient.Get(url)
    if err != nil {
        return nil, err
    }
    defer resp.Body.Close()

    return ioutil.ReadAll(resp.Body)
}

// 使用示例
func main() {
    discovery := NewConsulDiscovery("localhost:8500")
    strategy := &WeightedRandomStrategy{}
    lb := NewClientLoadBalancer(discovery, strategy)

    client := NewServiceClient(lb)

    // 调用服务
    data, err := client.Call("order-service", "/api/orders")
    if err != nil {
        log.Fatal(err)
    }

    fmt.Printf("Response: %s\n", data)
}

优点:

  • 无单点故障
  • 性能高(直连)
  • 灵活(可自定义策略)

缺点:

  • 客户端逻辑复杂
  • 多语言需要多套实现

服务端负载均衡

Nginx配置实战

完整配置:

# upstream定义后端服务器组
upstream backend {
    # 负载均衡算法
    least_conn;  # 最少连接数

    # 服务器列表(加权)
    server 192.168.1.10:8080 weight=3 max_fails=3 fail_timeout=30s;
    server 192.168.1.11:8080 weight=2 max_fails=3 fail_timeout=30s;
    server 192.168.1.12:8080 weight=1 max_fails=3 fail_timeout=30s;
    server 192.168.1.13:8080 backup;  # 备用服务器(其他都故障时启用)

    # 健康检查(需要nginx_upstream_check_module)
    check interval=3000 rise=2 fall=3 timeout=1000 type=http;
    check_http_send "GET /health HTTP/1.0\r\n\r\n";
    check_http_expect_alive http_2xx http_3xx;

    # 会话保持(IP Hash)
    # ip_hash;
}

server {
    listen 80;
    server_name api.example.com;

    # 日志
    access_log /var/log/nginx/api.access.log;
    error_log /var/log/nginx/api.error.log;

    # 限流(每秒10个请求)
    limit_req_zone $binary_remote_addr zone=api_limit:10m rate=10r/s;
    limit_req zone=api_limit burst=20 nodelay;

    location / {
        proxy_pass http://backend;

        # 传递真实IP
        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_set_header X-Forwarded-Proto $scheme;

        # 超时设置
        proxy_connect_timeout 5s;
        proxy_send_timeout 60s;
        proxy_read_timeout 60s;

        # 重试
        proxy_next_upstream error timeout http_500 http_502 http_503;
        proxy_next_upstream_tries 2;
    }

    # 健康检查端点
    location /nginx_status {
        stub_status on;
        access_log off;
        allow 127.0.0.1;
        deny all;
    }
}

实战优化

1. 会话保持(Session Persistence)

问题:用户登录后,Session存储在Server1,下次请求被路由到Server2,Session丢失

解决方案:

方案1: IP Hash

upstream backend {
    ip_hash;  # 同一个IP总是路由到同一台服务器
    server 192.168.1.10:8080;
    server 192.168.1.11:8080;
}

方案2: Cookie Hash

upstream backend {
    hash $cookie_jsessionid;  # 根据Cookie哈希
    server 192.168.1.10:8080;
    server 192.168.1.11:8080;
}

方案3: Redis集中存储

// 推荐:Session存储到Redis(共享)
type SessionStore struct {
    redis *redis.Client
}

func (ss *SessionStore) Set(sessionID string, data interface{}, ttl time.Duration) error {
    jsonData, _ := json.Marshal(data)
    return ss.redis.Set(context.Background(), "session:"+sessionID, jsonData, ttl).Err()
}

func (ss *SessionStore) Get(sessionID string) (interface{}, error) {
    data, err := ss.redis.Get(context.Background(), "session:"+sessionID).Bytes()
    if err != nil {
        return nil, err
    }

    var result interface{}
    json.Unmarshal(data, &result)
    return result, nil
}

2. 健康检查

主动健康检查(Nginx):

upstream backend {
    server 192.168.1.10:8080;

    check interval=3000 rise=2 fall=3 timeout=1000 type=http;
    check_http_send "GET /health HTTP/1.0\r\n\r\n";
    check_http_expect_alive http_2xx;
}

被动健康检查(失败自动摘除):

server 192.168.1.10:8080 max_fails=3 fail_timeout=30s;
# 30秒内失败3次 → 摘除30秒

3. 动态权重调整

场景:服务器负载变化,动态调整权重

type DynamicWeightBalancer struct {
    servers     []*Server
    monitor     *ResourceMonitor
    mu          sync.RWMutex
}

type Server struct {
    Addr          string
    Weight        int
    DynamicWeight float64
    CPU           float64
    Memory        float64
}

func (dwb *DynamicWeightBalancer) adjustWeights() {
    ticker := time.NewTicker(10 * time.Second)
    defer ticker.Stop()

    for range ticker.C {
        dwb.mu.Lock()

        for _, server := range dwb.servers {
            // 获取服务器资源使用率
            cpu, mem := dwb.monitor.GetResourceUsage(server.Addr)

            // 根据负载调整权重
            // 负载越高,权重越低
            loadFactor := 1.0 - (cpu*0.7 + mem*0.3)
            server.DynamicWeight = float64(server.Weight) * loadFactor

            log.Printf("Server %s: CPU=%.1f%%, Mem=%.1f%%, Weight=%.2f",
                server.Addr, cpu*100, mem*100, server.DynamicWeight)
        }

        dwb.mu.Unlock()
    }
}

面试问答

四层负载均衡和七层负载均衡有什么区别?

答案:

对比维度四层负载均衡七层负载均衡
工作层次传输层(TCP/UDP)应用层(HTTP)
性能极高(100万+ QPS)较高(10万+ QPS)
路由能力IP+端口URL、Header、Cookie
协议支持所有TCP/UDPHTTP/HTTPS
典型代表LVS、F5Nginx、HAProxy
适用场景高性能、透明代理灵活路由、内容缓存

选择建议:

高性能要求、非HTTP协议 → 四层(LVS)
需要基于内容路由、HTTP协议 → 七层(Nginx)

一致性哈希如何解决节点变化时的数据迁移问题?

答案:

传统哈希:

节点数从3变为4:
hash(key) % 3 → hash(key) % 4
几乎所有key的映射都会改变 

一致性哈希:

哈希环:
Server1(100) → Server2(200) → Server3(300)

添加Server4(150):
只有[100, 150)区间的key需要迁移到Server4
其他key不受影响 

数据迁移量 = 1/N(N为节点数)

虚拟节点解决分布不均:

Server1 → 150个虚拟节点
Server2 → 150个虚拟节点
Server3 → 150个虚拟节点

虚拟节点均匀分布在哈希环上
→ 实际节点负载均衡

如何实现会话保持?各种方案的优缺点是什么?

答案:

方案实现优点缺点
IP Hash同IP路由到同服务器简单负载不均、NAT问题
Cookie Hash根据Cookie哈希准确依赖Cookie
Session复制服务器间同步Session高可用性能差、复杂
集中存储Redis存储Session推荐依赖Redis

推荐方案:

// Redis集中存储
type SessionManager struct {
    redis *redis.Client
}

func (sm *SessionManager) Set(sessionID string, data map[string]interface{}) error {
    jsonData, _ := json.Marshal(data)
    return sm.redis.Set(
        context.Background(),
        "session:"+sessionID,
        jsonData,
        30*time.Minute,  // TTL
    ).Err()
}

func (sm *SessionManager) Get(sessionID string) (map[string]interface{}, error) {
    data, err := sm.redis.Get(context.Background(), "session:"+sessionID).Bytes()
    if err != nil {
        return nil, err
    }

    var result map[string]interface{}
    json.Unmarshal(data, &result)
    return result, nil
}

// 优势:
//  所有服务器共享Session
//  支持水平扩展
//  高可用(Redis Cluster)

负载均衡器如何检测后端服务器的健康状态?

答案:

1. 主动健康检查

# Nginx配置
upstream backend {
    check interval=3000 rise=2 fall=3 timeout=1000 type=http;
    check_http_send "GET /health HTTP/1.0\r\n\r\n";
    check_http_expect_alive http_2xx;
}

参数说明:
- interval: 检查间隔(3秒)
- rise: 连续成功2次标记为UP
- fall: 连续失败3次标记为DOWN
- timeout: 超时时间(1秒)

2. 被动健康检查

server 192.168.1.10:8080 max_fails=3 fail_timeout=30s;

# 实际请求失败时触发:
# 30秒内失败3次 → 摘除30秒
# 30秒后自动恢复,重新检查

3. 健康检查端点

// 服务端实现
func healthCheckHandler(w http.ResponseWriter, r *http.Request) {
    // 检查数据库
    if err := db.Ping(); err != nil {
        w.WriteHeader(http.StatusServiceUnavailable)
        json.NewEncoder(w).Encode(map[string]string{
            "status": "DOWN",
            "reason": "database unreachable",
        })
        return
    }

    // 检查Redis
    if err := redis.Ping(context.Background()).Err(); err != nil {
        w.WriteHeader(http.StatusServiceUnavailable)
        json.NewEncoder(w).Encode(map[string]string{
            "status": "DOWN",
            "reason": "redis unreachable",
        })
        return
    }

    w.WriteHeader(http.StatusOK)
    json.NewEncoder(w).Encode(map[string]string{
        "status": "UP",
    })
}

如何选择合适的负载均衡算法?

答案:

┌─────────────────────────────────────────┐
│           选择负载均衡算法              │
└─────────────────────────────────────────┘
                  ↓
        服务器性能是否相同?
         /              \
       是                否
       ↓                 ↓
   轮询(RR)        加权轮询(WRR)
                        ↓
                  请求是否长连接?
                   /          \
                 是            否
                 ↓              ↓
           最少连接(LC)    加权轮询(WRR)
                                ↓
                          是否需要会话保持?
                           /          \
                         是            否
                         ↓              ↓
                   一致性哈希      随机/轮询
                   (Consistent Hash)

具体场景:

场景推荐算法原因
短连接HTTP轮询/加权轮询简单高效
长连接(WebSocket)最少连接动态负载均衡
缓存系统一致性哈希减少数据迁移
需要会话保持IP Hash / 一致性哈希同一用户固定服务器
服务器性能不同加权轮询按性能分配

Prev
第4章:服务发现与注册
Next
第6章:熔断降级