Skip to content

Zoom-in: Load Balancer

Karify98·
Cover Image for Zoom-in: Load Balancer

Một domain. Hàng triệu request mỗi ngày. Không server nào xử lý được một mình — và ngay cả khi có thể, một server duy nhất là một điểm thất bại duy nhất.

Phóng to dần vào cách traffic được phân phối.


Layer 1 — Vấn đề: single point of failure

Không có load balancer, mọi request đều đến một server.

graph LR
    U1["👤 User 1"] -->|"request"| S["🖥️ Server"]
    U2["👤 User 2"] -->|"request"| S
    U3["👤 User 3"] -->|"request"| S
    S -.->|"💥 down"| X[" "]
    style U1 fill:#1e3a5f,stroke:#3b82f6,color:#93c5fd
    style U2 fill:#1e3a5f,stroke:#3b82f6,color:#93c5fd
    style U3 fill:#1e3a5f,stroke:#3b82f6,color:#93c5fd
    style S fill:#7f1d1d,stroke:#ef4444,color:#fca5a5

Server xuống → toàn bộ service xuống. Deploy mới → downtime. Spike traffic → server quá tải. Load balancer giải quyết cả ba bằng cách đứng trước nhiều server và phân phối request.

Vấn đề còn lại: nhiều server phía sau rồi — nhưng request được gửi đến server nào? Cần thuật toán phân phối.

Layer 2 — Thuật toán phân phối

Ba thuật toán phổ biến, ba trade-off khác nhau.

graph TD
    LB["⚖️ Load Balancer"]

    LB -->|"Round-robin: lần lượt"| S1["🖥️ Server 1"]
    LB -->|"Round-robin: lần lượt"| S2["🖥️ Server 2"]
    LB -->|"Round-robin: lần lượt"| S3["🖥️ Server 3"]

    style LB fill:#3b2a1a,stroke:#f59e0b,color:#fcd34d
    style S1 fill:#1a3a2a,stroke:#22c55e,color:#86efac
    style S2 fill:#1a3a2a,stroke:#22c55e,color:#86efac
    style S3 fill:#1a3a2a,stroke:#22c55e,color:#86efac

Round-robin: mỗi request lần lượt đến server tiếp theo. Đơn giản, không cần trạng thái. Nhược điểm: không tính đến việc server 1 đang xử lý request nặng trong khi server 2 rảnh.

Least connections: request đến server đang có ít kết nối nhất. Phù hợp khi request có thời gian xử lý không đồng đều — ví dụ, một số query DB chạy lâu hơn nhiều.

IP hash: IP của client xác định server nhận request. Cùng một client luôn đến cùng một server — dùng khi cần session affinity mà không muốn dùng sticky sessions ở tầng LB.

Vấn đề còn lại: thuật toán gửi request đến server đang down thì sao? Cần biết server nào còn sống.

Layer 3 — Health check: chỉ gửi đến server còn sống

Load balancer định kỳ kiểm tra từng server backend.

sequenceDiagram
    participant LB as ⚖️ Load Balancer
    participant S1 as 🖥️ Server 1 (healthy)
    participant S2 as 🖥️ Server 2 (down)

    loop Mỗi 10 giây
        LB->>S1: GET /health
        S1-->>LB: 200 OK
        LB->>S2: GET /health
        Note over S2: timeout
        LB->>S2: GET /health (retry)
        Note over S2: timeout lần 2
        Note over LB: Đánh dấu S2 unhealthy
    end

    Note over LB: Chỉ gửi traffic đến S1

Health check có thể là TCP ping (chỉ kiểm tra port mở) hoặc HTTP request đến /health endpoint (kiểm tra app thật sự phản hồi được). HTTP health check chính xác hơn — server có thể nghe được TCP nhưng app bên trong đã crash.

Khi server hồi phục, load balancer sau vài lần health check thành công sẽ tự động đưa server trở lại pool.

Vấn đề còn lại: load balancer đang hoạt động ở tầng nào của network stack? Câu trả lời ảnh hưởng đến khả năng routing.

Layer 4 — L4 vs L7: hai tầng, hai khả năng

graph LR
    subgraph "Layer 4 LB (TCP)"
        LB4["⚖️ L4 LB"] -->|"dựa trên IP:port"| S4A["🖥️ Server A"]
        LB4 -->|"dựa trên IP:port"| S4B["🖥️ Server B"]
    end

    subgraph "Layer 7 LB (HTTP)"
        LB7["⚖️ L7 LB"] -->|"/api/* → API server"| SA["🖥️ API Server"]
        LB7 -->|"/static/* → CDN origin"| SB["🖥️ Static Server"]
        LB7 -->|"Host: admin.* → admin"| SC["🖥️ Admin Server"]
    end

    style LB4 fill:#3b2a1a,stroke:#f59e0b,color:#fcd34d
    style LB7 fill:#3b2a1a,stroke:#f59e0b,color:#fcd34d
    style S4A fill:#1a3a2a,stroke:#22c55e,color:#86efac
    style S4B fill:#1a3a2a,stroke:#22c55e,color:#86efac
    style SA fill:#1a3a2a,stroke:#22c55e,color:#86efac
    style SB fill:#1a3a2a,stroke:#22c55e,color:#86efac
    style SC fill:#1a3a2a,stroke:#22c55e,color:#86efac

L4 load balancer (AWS NLB, HAProxy TCP mode): hoạt động ở tầng transport. Nhanh, ít overhead, nhưng chỉ thấy IP và port — không thể route theo URL path hay HTTP header.

L7 load balancer (AWS ALB, nginx, Traefik): hoạt động ở tầng application. Đọc được HTTP header, URL, cookie — cho phép route theo content. Có thể terminate TLS, thêm header, thực hiện A/B testing, rate limit theo path.

Hầu hết web service dùng L7 vì flexibility. L4 phù hợp khi cần latency cực thấp hoặc protocol không phải HTTP.

Vấn đề còn lại: khi app cần giữ trạng thái session ở server (WebSocket, giỏ hàng in-memory), round-robin sẽ gửi các request liên tiếp của cùng user đến server khác nhau.

Layer 5 — Sticky sessions: gắn user vào server

Đôi khi cần đảm bảo cùng một user luôn đến cùng một server.

sequenceDiagram
    participant U as 👤 User
    participant LB as ⚖️ L7 Load Balancer
    participant S1 as 🖥️ Server 1
    participant S2 as 🖥️ Server 2

    U->>LB: Request lần đầu
    LB->>S1: Forward request
    S1-->>LB: Response + Set-Cookie: SERVERID=s1
    LB-->>U: Response + cookie

    U->>LB: Request lần 2 (cookie: SERVERID=s1)
    Note over LB: Cookie → route đến S1
    LB->>S1: Forward request
    S1-->>U: Response

Sticky sessions giải quyết bài toán stateful session — nhưng tạo ra vấn đề mới: nếu S1 xuống, tất cả session gắn vào S1 bị mất. Giải pháp tốt hơn là externalize state ra Redis hoặc DB — server nào cũng đọc được session của user, không cần sticky.


Full picture

graph TD
    U1["👤 Users"] -->|"requests"| LB

    subgraph "Load Balancer"
        LB["⚖️ L7 LB\n(nginx / ALB)"]
        LB --> HC["🔍 Health Check\n(mỗi 10s)"]
    end

    LB -->|"round-robin / least-conn"| S1["🖥️ Server 1\n✓ healthy"]
    LB -->|"round-robin / least-conn"| S2["🖥️ Server 2\n✓ healthy"]
    LB -.->|"🚫 bỏ qua"| S3["🖥️ Server 3\n✗ unhealthy"]

    S1 & S2 --> DB[("💾 Shared DB\n+ Redis")]

    style LB fill:#3b2a1a,stroke:#f59e0b,color:#fcd34d
    style S1 fill:#1a3a2a,stroke:#22c55e,color:#86efac
    style S2 fill:#1a3a2a,stroke:#22c55e,color:#86efac
    style S3 fill:#7f1d1d,stroke:#ef4444,color:#fca5a5
    style DB fill:#1e3a5f,stroke:#3b82f6,color:#93c5fd

Takeaway

Load balancer không chỉ là "chia đều request". Nó là điểm quyết định: server nào còn sống, request nào đến đâu, session được xử lý thế nào. Chọn L4 hay L7 là lựa chọn kiến trúc — không chỉ là config.

Stateful session là nguyên nhân phổ biến nhất khiến horizontal scaling trở nên khó: hai request liên tiếp của cùng user có thể đến hai server khác nhau. Externalize state ra shared storage là điều kiện để load balancer hoạt động đúng nghĩa.


Bài viết được hỗ trợ bởi Amy 🌸 - AI Assistant. Nội dung đã được kiểm duyệt bởi tác giả.

Related Posts