TCP, HTTP & Web cho Backend/Load balancer — L4 vs L7, thuật toán phân tải
20/29
Bài 20 / 29~20 phútHTTP & WebMiễn phí lượt xem

Load balancer — L4 vs L7, thuật toán phân tải

Vì sao cần load balancer, L4 (TCP) vs L7 (HTTP), thuật toán round-robin/least-conn/IP hash, health check, sticky session và vì sao session cần external store.

TL;DR: Load balancer (LB) phân phối request đến nhiều server backend, giúp scale ngang, tăng tính sẵn sàng, và hỗ trợ rolling deploy không downtime. Có hai tầng: L4 hoạt động ở tầng Transport (TCP/IP) — nhanh, không đọc nội dung HTTP; L7 hoạt động ở tầng Application (HTTP) — chậm hơn nhưng định tuyến thông minh theo URL/header/cookie. Nginx và HAProxy làm được cả hai; cloud LB (AWS ALB/NLB, GCP) phân chia rõ ràng hơn. Bài này giải thích khi nào dùng tầng nào, thuật toán phân tải nào, và pitfall lớn nhất: session state không được chia sẻ tự động giữa các instance.

bài 08 — Reverse proxy với Nginx bạn đã thấy upstream block với nhiều server — đó chính là L7 load balancing đơn giản nhất. Bài này phóng to toàn bộ bức tranh.

1. Vì sao cần load balancer

Một ứng dụng web chạy trên một server đơn có ba giới hạn cứng:

  • Giới hạn throughput: một process/server chỉ xử lý được hữu hạn request/giây. Khi traffic tăng gấp đôi, không thể "tăng tốc" server đơn lên gấp đôi mãi được (vertical scaling có trần).
  • Single point of failure (SPOF): server đơn chết → toàn bộ dịch vụ chết.
  • Deploy tốn downtime: restart app để deploy code mới → user thấy 502 trong vài giây.

Load balancer giải quyết cả ba bằng cách đứng trước một pool nhiều server backend:

Bài toánGiải pháp với LB
Throughput giới hạnThêm server mới vào pool (scale ngang / horizontal scaling)
SPOFLB detect server chết → không route vào đó nữa (health check + failover)
Deploy downtimeRolling deploy: tắt từng server, deploy, bật lại — LB route vòng qua server còn lại
Client
Client
Load Balancer
Load Balancer
App 1
App 1
App 2
App 2
App 3
App 3

LB phân phối request — thêm/bớt server mà client không hay biết

2. L4 vs L7 — hai tầng, hai triết lý

L4L7 là số tầng trong mô hình OSI: L4 là tầng Transport (TCP/UDP), L7 là tầng Application (HTTP). Bạn đã học ánh xạ OSI ↔ TCP/IP ở khoá Foundations bài 02.

L4 load balancer — định tuyến ở tầng Transport

L4 LB hoạt động ở mức IP + port + TCP. Nó nhìn vào IP đích, port, và các flag TCP — nhưng không đọc nội dung HTTP bên trong. Khi nhận một TCP connection, nó forward toàn bộ byte stream đó sang một server backend.

  • Nhanh: không cần parse HTTP, chỉ xử lý header IP/TCP nhỏ.
  • TLS passthrough: gói TLS đi qua nguyên vẹn, backend tự decrypt — LB không thấy nội dung HTTP.
  • Không thể định tuyến theo URL/header: tất cả traffic tới port 443 đi vào một pool duy nhất.
  • Ứng dụng: database connection pooling (PostgreSQL/MySQL), raw TCP service, game server.

L7 load balancer — định tuyến ở tầng Application

L7 LB đọc được HTTP (URL, header, cookie, method). Nó parse request, quyết định backend, rồi tạo một kết nối HTTP mới tới backend đó — đây là lý do L7 cũng là reverse proxy.

  • Định tuyến thông minh: /api/* vào service A, /static/* vào service B, User-Agent: mobile vào server mobile.
  • TLS termination: LB decrypt HTTPS — backend nhận HTTP thuần (xem bài 08).
  • Chậm hơn L4: phải parse HTTP trước mỗi request.
  • Ứng dụng: web app, REST API, microservice routing — đây là default trong hầu hết stack.
L4 — Transport
Nhìn IP + Port + TCP flags
Không đọc HTTP
TLS passthrough
Cực nhanh, overhead thấp
Dùng cho: DB, raw TCP, game
L7 — Application
Đọc URL, header, cookie
Định tuyến theo content
TLS termination
Chậm hơn L4 một chút
Dùng cho: web, API, microservice

3. Thuật toán phân tải

Sau khi LB quyết định pool backend, nó cần chọn server nào nhận request tiếp theo. Các thuật toán phổ biến:

Round-robin (mặc định)

Phân request lần lượt: request 1 → server A, request 2 → server B, request 3 → server C, request 4 → server A, ...

Đơn giản, phân đều về số lượng request. Phù hợp khi các request có thời gian xử lý tương đương và server đồng nhau.

upstream backend_pool {
    server 10.0.0.1:8080;  # round-robin mac dinh
    server 10.0.0.2:8080;
    server 10.0.0.3:8080;
}

Weighted round-robin

Server mạnh hơn nhận nhiều request hơn tương ứng với trọng số. Server A có weight=3 nhận 3 request trên mỗi 4 request của chu kỳ.

upstream backend_pool {
    server 10.0.0.1:8080 weight=3;  # may manh hon, nhan 3x
    server 10.0.0.2:8080 weight=1;
}

Least connections

LB chọn server đang có ít connection nhất tại thời điểm đó. Hiệu quả khi request có thời gian xử lý khác nhau nhiều — server đang bận (nhiều kết nối mở) sẽ không nhận thêm cho tới khi bớt tải.

upstream backend_pool {
    least_conn;
    server 10.0.0.1:8080;
    server 10.0.0.2:8080;
}

IP hash / Consistent hashing

Hash IP (hoặc một thuộc tính khác) của client để luôn route request từ cùng client tới cùng server. Đây là nền tảng của sticky session theo IP.

upstream backend_pool {
    ip_hash;  # hash theo IP client
    server 10.0.0.1:8080;
    server 10.0.0.2:8080;
}

Consistent hashing là phiên bản nâng cao: khi thêm/bớt server, chỉ một phần nhỏ client bị "xáo" sang server mới, thay vì rehash toàn bộ. Phổ biến trong cache layer (Memcached cluster, CDN).

4. Health check — phát hiện server chết

LB phải biết server nào còn sống để không route vào đó khi chết. Có hai loại health check:

Active health check: LB định kỳ gửi probe tới backend (ví dụ HTTP GET /health) và kiểm tra response. Nếu server không trả 200 trong một số lần liên tiếp, LB đánh dấu server đó là unhealthy và ngừng route.

Passive health check: LB quan sát response thật của traffic. Nếu một server liên tục trả 5xx hoặc timeout, LB tạm loại nó ra. Nginx dùng cơ chế này với max_failsfail_timeout.

upstream backend_pool {
    server 10.0.0.1:8080 max_fails=3 fail_timeout=30s;
    server 10.0.0.2:8080 max_fails=3 fail_timeout=30s;
    # Neu server tra loi 3 lan trong 30s -> danh dau down 30s
}

Failover: khi server A được đánh dấu unhealthy, LB tự động chuyển toàn bộ traffic sang các server còn lại — không cần can thiệp thủ công. Khi server A recover (health check pass trở lại), LB đưa nó vào pool.

sequenceDiagram
    participant LB as Load Balancer
    participant A as Server A (down)
    participant B as Server B (up)
    LB->>A: Health check GET /health
    A-->>LB: Timeout / 500
    Note over LB: max_fails dat -> danh dau A unhealthy
    LB->>B: Route request (A bi bo qua)
    B-->>LB: 200 OK
    LB-->>LB: Dinh ky check A...
    LB->>A: Health check GET /health
    A-->>LB: 200 OK
    Note over LB: A recover -> them lai vao pool

5. Sticky session — khi state nằm trong server

Sticky session (hay session affinity) là kỹ thuật đảm bảo tất cả request từ cùng một user luôn được route tới cùng một server backend. LB nhìn vào một giá trị định danh (thường là cookie hoặc IP) để quyết định.

Vì sao cần? Nếu app lưu session state trong bộ nhớ của server (ví dụ Java HttpSession in-memory, Express session in-memory store), request thứ hai từ cùng user mà rơi vào server khác sẽ không tìm thấy session — user bị logout hoặc thấy lỗi.

upstream backend_pool {
    ip_hash;  # Sticky theo IP -- don gian nhat
    server 10.0.0.1:8080;
    server 10.0.0.2:8080;
}

Nginx Enterprise và HAProxy hỗ trợ sticky theo cookie (chính xác hơn IP hash vì IP có thể chia sẻ qua NAT):

# HAProxy config (sticky cookie)
backend app_servers
    balance roundrobin
    cookie SERVERID insert indirect nocache
    server app1 10.0.0.1:8080 check cookie app1
    server app2 10.0.0.2:8080 check cookie app2
⚠️ Sticky session phá cân bằng tải

Khi nhiều user bị "ghim" vào một server, server đó có thể quá tải trong khi các server khác rảnh. Sticky session là giải pháp tình thế, không phải kiến trúc tốt. Giải pháp đúng là external session store — lưu session trong Redis/Memcached thay vì in-memory server, để mọi server đều đọc được. Khi đó LB có thể dùng round-robin hoặc least-conn mà không cần sticky.

6. Liên hệ các bài khác

7. ⚠️ Pitfall — lỗi thường gặp với load balancer

Pitfall 1 — Session state in-memory + round-robin = user bị đăng xuất ngẫu nhiên

Đây là lỗi kinh điển nhất. App lưu session trong HashMap hoặc in-memory store trên mỗi server. LB round-robin gửi request 1 tới server A (tạo session), request 2 tới server B (không có session) → app trả 401 hoặc redirect về login. User báo "tôi bị đăng xuất liên tục mà không làm gì". Fix: dùng Redis/Memcached làm external session store, hoặc chuyển sang stateless JWT (không cần session server-side).

Pitfall 2 — Health check trả 200 nhưng app thật sự bị lỗi

Health check endpoint /health chỉ trả {"status":"ok"} mà không kiểm tra DB connection, cache, queue — app trông healthy nhưng không xử lý được request thật. LB vẫn route vào server đó → user thấy lỗi. Fix: health check sâu hơn — kiểm tra dependency thật (SELECT 1 tới DB, ping Redis).

Pitfall 3 — Sticky session ghim user vào server chết

Server A down, nhưng cookie sticky của user vẫn trỏ vào A. LB cần logic để fallback sang server khác khi server được ghim không available — không phải mọi LB đều xử lý đúng case này. HAProxy có option redispatch; Nginx cần cấu hình thêm.

Pitfall 4 — L4 LB không thể làm TLS termination

L4 LB không đọc HTTP, nên cũng không thể làm TLS termination (vì TLS nằm bên trong TCP). Nếu bạn cần TLS termination + routing theo URL, phải dùng L7 LB. Chọn L4 khi cần passthrough TLS tới backend tự xử lý.

Pitfall 5 — Round-robin với request nặng nhẹ không đều

Round-robin chia đều số request, không chia đều tải. Một request upload file 100MB và một request query nhỏ đều được đếm là "1 request". Server A có thể đang xử lý 5 upload nặng trong khi server B đang nhàn — nhưng LB vẫn chia đều. Fix: dùng least_conn khi request có thời gian xử lý khác biệt lớn.

8. 📚 Deep Dive — tài liệu gốc

📚 Tài liệu tham khảo

Ghi chú: HAProxy documentation section 4 (proxies) và 5 (server) là reference đầy đủ nhất về thuật toán và health check options. Đọc khi cần tune production LB.

9. Tóm tắt

  • Load balancer phân phối request vào pool backend — giải quyết scale ngang, HA, và rolling deploy.
  • L4 LB hoạt động ở tầng Transport: chỉ thấy IP/port/TCP, không đọc HTTP, nhanh, TLS passthrough. Dùng cho database, raw TCP.
  • L7 LB hoạt động ở tầng Application: đọc HTTP URL/header/cookie, định tuyến thông minh, TLS termination. Dùng cho web và API — đây là default trong stack hiện đại.
  • Thuật toán: round-robin (phân đều request), weighted (theo sức mạnh server), least-conn (chọn server ít tải nhất), IP hash (sticky theo IP).
  • Health check phát hiện server chết và loại khỏi pool (active probe hoặc passive observation); failover tự động.
  • Sticky session ghim user vào một server — giải pháp tình thế khi session in-memory. Giải pháp đúng: external session store (Redis) để mọi server dùng chung.
  • Pitfall lớn nhất: session không chia sẻ giữa instance khi dùng round-robin — biểu hiện là user bị đăng xuất ngẫu nhiên.

10. Tự kiểm tra

Tự kiểm tra
0/6 câu đã trả lời
  1. Q1
    L4 và L7 load balancer khác nhau căn bản ở điểm nào? Tại sao L7 đắt hơn về CPU?
  2. Q2
    Khi nào chọn L4, khi nào chọn L7?
  3. Q3
    Vì sao `least_conn` tốt hơn `round_robin` khi request có thời gian xử lý không đều?
  4. Q4
    Sticky session là gì và tại sao nó là giải pháp tình thế thay vì kiến trúc đúng?
  5. Q5
    Passive health check trong Nginx hoạt động thế nào? Khác gì active health check?
  6. Q6
    Tại sao cần health check kiểm tra dependency thật (DB, Redis) thay vì chỉ trả status ok?

Bài tiếp theo: Module 2 — Tổng kết & cheat sheet

Bài này đáng gửi cho bạn học cùng?

Copy link đã gắn nguồn — dán group, chat, hoặc LinkedIn.

Bài này có giúp bạn hiểu bản chất không?

Hỏi đáp về bài này

Chưa có câu hỏi

Đặt câu hỏi

Có gì chưa rõ trong bài? Đặt câu hỏi đầu tiên — câu trả lời từ cộng đồng giúp bạn (và người sau).

Đặt câu hỏi đầu tiên

Bài tiếp theo

Module 2 — Tổng kết & cheat sheet