Load balancer: chia tải và health check
Từ một server lên ba server: ai đứng ra chia việc?
Ở module trước, bạn đã chốt hai điều: scale ngang bằng cách thêm server, và server phải stateless để session nằm ở nơi khác (Redis, database). Nhưng thêm server xong thì phát sinh câu hỏi mới: ai là người quyết định request nào vào server nào? Không ai cả thì user vẫn gõ đúng một địa chỉ IP, và server đó vẫn sập còn hai server mới thì ngồi chơi. Load balancer chính là mảnh ghép còn thiếu đó.
Sau bài này, bạn sẽ:
- Giải thích được load balancer nằm ở đâu trong luồng request và nó làm gì với từng request.
- Phân biệt được ba thuật toán chia tải thường gặp và biết khi nào chọn cái nào.
- Mô tả được health check hoạt động thế nào, và vì sao một server "chết một nửa" lại nguy hiểm hơn server chết hẳn.
- Chỉ ra được vì sao load balancer chỉ phát huy tác dụng khi server stateless.
Load balancer làm gì trong một giây tải cao
flowchart TD
U1["👤 User"] --> LB
U2["👤 User"] --> LB
U3["👤 User"] --> LB
LB["⚖️<br/>Load balancer"] --> S1["🖥️<br/>App 1"]
LB --> S2["🖥️<br/>App 2"]
LB --> S3["🖥️<br/>App 3"]
S1 --> DB[("🗄️<br/>Database")]
S2 --> DB
S3 --> DB
Có hai việc, và chỉ hai việc thôi: chia tải (request vào server nào) và loại server lỗi (health check). Mọi thứ phức tạp hơn đều xoay quanh hai việc này.
Chia tải: ba cách phổ biến
| Thuật toán | Cách chạy | Hợp với |
|---|---|---|
| Round robin | Lần lượt S1, S2, S3, S1, S2, S3... | Request đồng đều, xử lý nhanh |
| Least connections | Đẩy về server đang ít connection nhất | Request có thời gian xử lý chênh lệch (upload, query nặng) |
| IP hash | Cùng một IP luôn về cùng một server | Khi buộc phải giữ state, dùng tạm |
Round robin là mặc định của hầu hết hệ thống mới vì nó dễ hiểu nhất: đếm vòng. Nhưng nó giả định mọi server xử lý nhanh như nhau. Nếu một request mất 2 giây và ba request khác mất 50ms, round robin vẫn chia đều số request, không chia đều thời gian bận. Lúc đó least connections mới đúng.
Worked example: 900 RPS và một server chết
Giả sử bạn có 3 app server, mỗi server chịu được 400 RPS. Traffic hiện tại là 900 RPS.
- Bình thường: round robin chia đều, mỗi server nhận 300 RPS. Còn 100 RPS dư biên mỗi máy. Ổn.
- Server 2 chết. Health check phát hiện sau 10 giây. Trong 10 giây đó, khoảng 1000 request vẫn được đẩy vào server 2 và trả lỗi 502.
- Sau khi loại server 2: 900 RPS dồn cho 2 server, tức 450 RPS mỗi máy. Vượt ngưỡng 400 RPS. Server 1 quá tải, chậm lại, health check bắt đầu fail, rồi server 3 cũng gánh phần của server 1. Toàn hệ thống sập theo hiệu ứng domino.
Bài học ở đây không phải "đừng để server chết", mà là: dung lượng dư phải tính theo trường hợp xấu nhất. Với 3 server, muốn chịu được 1 máy chết thì mỗi máy phải gánh được 450 RPS, không phải 300 RPS. Con số đó là thứ quyết định bạn cần 3 hay 4 server, không phải cảm giác.
Health check: loại server lỗi trước khi user thấy lỗi
stateDiagram-v2
[*] --> Healthy
Healthy --> Unhealthy: 3 lần fail liên tiếp
Unhealthy --> Healthy: 2 lần pass liên tiếp
Có hai kiểu:
- Active health check: load balancer chủ động gọi
GET /healthmỗi 5 giây. Server trả 200 thì tính là pass, timeout hoặc 5xx thì fail. - Passive health check: không gọi riêng, chỉ nhìn kết quả các request thật. Nếu server trả 5 lỗi liên tiếp thì tạm loại.
Hầu hết hệ thống dùng cả hai: active để bắt server chết hẳn, passive để bắt server lỗi theo tình huống thực tế.
Điểm quan trọng: ngưỡng fail phải lớn hơn 1. Nếu loại server chỉ sau một lần fail, một lần timeout mạng thoáng qua sẽ đá cả server khỏe ra khỏi vòng, và tải dồn sang các máy còn lại. Quy tắc thường dùng: 3 lần fail liên tiếp thì đánh dấu unhealthy, 2 lần pass liên tiếp thì đưa lại vào vòng.
Và cái bẫy tinh vi hơn: server chết một nửa. Ví dụ server vẫn trả 200 cho /health nhưng connection pool database của nó đã cạn, nên mọi request thật đều treo. Health check chỉ kiểm tra "process còn sống" thì sẽ báo xanh, và load balancer vẫn đẩy tải vào đó. Vì vậy /health phải kiểm tra cả dependency quan trọng, không chỉ return 200.
Vì sao load balancer cần server stateless
Nếu app lưu session trong memory từng server, chuyện này xảy ra: user đăng nhập vào S1, request sau round robin đẩy sang S2, S2 không biết user là ai, user bị đăng xuất. Cách chữa thường thấy là bật sticky session, ép user luôn về S1. Nhưng sticky session phá cả hai việc của load balancer: tải không còn đều (một máy có thể gánh session của 60% user), và khi S1 chết thì đúng 60% user đó mất session cùng lúc.
Đó là lý do bài trước kết luận session phải nằm ở Redis hoặc database. Có stateless rồi thì load balancer mới tự do đẩy request đi bất kỳ đâu, và mới dám loại server ra khỏi vòng mà không ai bị ảnh hưởng.
Nút thắt tiếp theo nằm ở đâu
Load balancer giải quyết tầng app, nhưng nó không giải quyết tầng dữ liệu. Ba app server nhân ba lưu lượng query, trong khi database vẫn chỉ có một. Đó là lý do bài tiếp theo đi thẳng vào connection pool: chỗ mà load balancer vừa "chia đều" xong lại trở thành điểm nghẽn mới.
Ba cái bẫy khi mới dựng load balancer
- Đặt health check quá nhạy. Fail sau 1 lần, interval 1 giây: mỗi lần mạng chập chờn bạn mất một server khỏe. Hãy để ngưỡng 3 lần, interval 5 đến 10 giây.
- Health check chỉ trả 200 vô điều kiện. Một endpoint
/healthkhông kiểm tra database, không kiểm tra queue, là một endpoint nói dối. Nó báo xanh cho một server đã hỏng thật. - Tính dung lượng theo ngày thường. Đừng hỏi "mỗi server chịu được bao nhiêu", hãy hỏi "mỗi server phải chịu bao nhiêu khi mất một máy". Con số thứ hai mới quyết định bạn cần bao nhiêu server.
Mẹo đọc nhanh: khi một sự cố xảy ra, câu hỏi đầu tiên luôn là "server nào bị loại khỏi vòng, và lúc đó các server còn lại đang chạy ở bao nhiêu phần trăm công suất".
Chốt lại
- Load balancer chỉ làm hai việc: chia tải và loại server lỗi. Mọi cấu hình đều phục vụ hai việc đó.
- Round robin chia đều số request, không chia đều thời gian bận. Least connections dùng khi request có thời gian xử lý lệch nhau.
- Health check cần ngưỡng fail lớn hơn 1, và phải kiểm tra dependency thật, không chỉ kiểm tra process còn sống.
- Dung lượng dư phải tính theo kịch bản mất một server, không theo ngày thường.
- Sticky session là dấu hiệu app chưa stateless, và nó làm load balancer mất đi lợi ích chính.
Bài tiếp theo: Connection pool database, vì sao cạn connection. Chúng ta sẽ xem điều gì xảy ra khi load balancer chia đều tải vào ba server, và cả ba cùng mở connection tới một database có giới hạn cứng.
Front: Load balancer làm hai việc gì? Back: Chia tải request vào các server và loại server lỗi bằng health check.
Front: Round robin hoạt động thế nào? Back: Chia request lần lượt theo vòng: S1, S2, S3, rồi quay lại S1.
Front: Khi nào nên dùng least connections thay round robin? Back: Khi thời gian xử lý của các request chênh lệch lớn, ví dụ upload hoặc query nặng.
Front: IP hash dùng để làm gì? Back: Ép cùng một IP luôn về cùng một server, dùng tạm khi buộc phải giữ state.
Front: Active health check là gì? Back: Load balancer chủ động gọi endpoint health theo chu kỳ, ví dụ GET /health mỗi 5 giây.
Front: Passive health check là gì? Back: Đánh giá server dựa trên kết quả các request thật, ví dụ 5 lỗi liên tiếp thì tạm loại.
Front: Vì sao ngưỡng fail của health check phải lớn hơn 1? Back: Để một lần timeout thoáng qua không đá server khỏe ra khỏi vòng chia tải.
Front: Server chết một nửa là gì? Back: Server vẫn trả 200 cho /health nhưng request thật đều treo, ví dụ vì connection pool database đã cạn.
Front: Với 3 server chịu 400 RPS mỗi máy và traffic 900 RPS, mỗi máy phải gánh bao nhiêu nếu mất một máy? Back: 450 RPS, vượt ngưỡng 400 RPS, nên phải thêm server hoặc tăng dung lượng từng máy.
Front: Vì sao sticky session là dấu hiệu xấu? Back: Vì app chưa stateless: tải bị lệch và một server chết là mất session của cả nhóm user đó.
Front: Session nên lưu ở đâu để load balancer phát huy tác dụng? Back: Ở nơi dùng chung như Redis hoặc database, để server nào cũng đọc được.
Front: Vì sao load balancer không giải quyết được nút thắt database? Back: Ba app server nhân ba lưu lượng query, còn database vẫn chỉ có một.
Front: Health check interval hợp lý khi mới dựng hệ thống là bao nhiêu? Back: Khoảng 5 đến 10 giây, với ngưỡng 3 lần fail liên tiếp.
Điền nhanh để tự kiểm tra:
- Load balancer dùng _____ để loại server lỗi khỏi vòng chia tải.
- Với _____, request lần lượt đi qua từng server theo vòng.
- Thuật toán _____ đẩy request mới về server đang có ít kết nối nhất.
- Nếu session lưu trong memory từng server, app buộc phải bật _____.
- Health check dạng _____ nghĩa là load balancer chủ động gọi endpoint health theo chu kỳ.
- Dung lượng dư phải tính theo kịch bản _____, không phải theo ngày thường.