7 công dụng Load Balancer: giải thích chi tiết kèm ví dụ
1. Tổng quan
Khi nhắc đến Load Balancer (LB), nhiều bạn chỉ nghĩ đơn giản: “nó chia request cho nhiều server”. Thực ra LB làm nhiều hơn vậy — từ việc giảm tải SSL, giữ session người dùng, đến chống DDoS và theo dõi sức khỏe backend.
Bài này đi qua 7 công dụng chính của Load Balancer, mỗi công dụng có ví dụ cấu hình hoặc code cụ thể (nginx, Spring Boot, Kubernetes) để bạn hình dung rõ cách nó hoạt động trong hệ thống thật. Đọc xong, bạn sẽ biết khi nào cần bật tính năng nào trên LB và tránh được vài lỗi cấu hình phổ biến.
2. Phân phối tải (Traffic Distribution)
Đây là chức năng cơ bản nhất: LB nhận request từ client và chuyển đến một trong nhiều server backend theo một thuật toán nào đó — round robin, least connections, weighted round robin…
upstream order_service {
server 10.0.1.11:8080;
server 10.0.1.12:8080;
server 10.0.1.13:8080;
}
server {
listen 80;
location /orders/ {
proxy_pass http://order_service;
}
}
upstream khai báo 3 instance của OrderService. Mặc định nginx dùng round robin — request 1 đi tới .11, request 2 đi tới .12, request 3 đi tới .13, rồi quay lại .11. Nếu một instance xử lý request nặng hơn, bạn có thể đổi sang least_conn; để nginx ưu tiên server có ít connection đang mở nhất.
Lưu ý: Round robin không quan tâm server đang khỏe hay yếu. Nếu các instance có năng lực khác nhau (ví dụ một máy cấu hình mạnh hơn), hãy dùng
weight:server 10.0.1.11:8080 weight=3; server 10.0.1.12:8080 weight=1;
3. SSL Termination
Thay vì để mỗi backend tự giải mã TLS (tốn CPU), bạn đưa việc này về cho LB. LB giữ certificate, giải mã HTTPS thành HTTP thuần rồi mới forward vào mạng nội bộ.
server {
listen 443 ssl;
server_name api.nonladev.com;
ssl_certificate /etc/nginx/certs/nonladev.crt;
ssl_certificate_key /etc/nginx/certs/nonladev.key;
location / {
proxy_pass http://order_service; # HTTP thuần, không cần TLS
proxy_set_header X-Forwarded-Proto https;
}
}
Nhờ vậy, OrderService viết bằng Spring Boot không cần cấu hình server.ssl.* gì cả — nó chỉ chạy HTTP bình thường trên cổng 8080. Header X-Forwarded-Proto giúp backend biết request gốc là HTTPS để sinh URL redirect đúng.
Lưu ý: Nếu mạng nội bộ giữa LB và backend không đáng tin (ví dụ qua nhiều subnet, cloud khác nhau), đừng chỉ termination — hãy “SSL bridging”: LB giải mã rồi mã hóa lại bằng cert nội bộ trước khi gửi tiếp.
4. Duy trì phiên (Session Persistence)
Khi backend lưu session trong memory (session local), request của cùng một user phải luôn đi đến đúng instance đó, nếu không user sẽ bị “đăng xuất” ngẫu nhiên.
upstream order_service {
ip_hash; # cùng IP client -> cùng server
server 10.0.1.11:8080;
server 10.0.1.12:8080;
}
ip_hash băm IP client để chọn cố định một server trong upstream. Cách khác là sticky session dựa trên cookie — LB tự gắn cookie đánh dấu instance, dùng được cả khi client đứng sau NAT chung IP.
Giải pháp bền hơn là bỏ sticky session, đẩy session ra ngoài bằng Spring Session + Redis:
@Configuration
@EnableRedisHttpSession
public class SessionConfig {
}
spring.data.redis.host=redis.internal
spring.data.redis.port=6379
Với cấu hình này, session được lưu ở Redis thay vì memory của từng instance. Request đi tới server nào cũng đọc được session — LB có thể dùng round robin bình thường, không cần ip_hash nữa.
Lưu ý:
ip_hashdễ gây lệch tải nếu nhiều user cùng chung NAT (ví dụ văn phòng công ty) — toàn bộ traffic của họ dồn vào 1 server. Ưu tiên externalize session khi hệ thống đã lớn.
5. Tính sẵn sàng cao (High Availability)
LB liên tục kiểm tra backend; nếu một instance die, LB tự loại nó khỏi danh sách phục vụ và dồn traffic sang instance còn sống — tất cả diễn ra mà client không hề biết.
upstream order_service {
server 10.0.1.11:8080 max_fails=3 fail_timeout=30s;
server 10.0.1.12:8080 max_fails=3 fail_timeout=30s;
}
max_fails=3 nghĩa là sau 3 lần request thất bại liên tiếp, nginx đánh dấu server đó “down” trong 30 giây (fail_timeout), không gửi request mới đến nữa. Sau 30 giây, nginx thử lại — nếu server đã hồi phục, nó được đưa lại vào vòng quay.
[14:02:01] 10.0.1.12 timeout -> fail count 1
[14:02:05] 10.0.1.12 timeout -> fail count 2
[14:02:09] 10.0.1.12 timeout -> fail count 3 -> marked DOWN for 30s
[14:02:09] traffic rerouted to 10.0.1.11
[14:02:39] 10.0.1.12 retried -> healthy -> back in pool
Nhờ cơ chế này, một instance crash không làm sập cả hệ thống — đây chính là nền tảng của high availability ở tầng network.
6. Khả năng mở rộng theo chiều ngang (Scalability)
LB là điều kiện tiên quyết để horizontal scaling hoạt động: bạn thêm instance mới, LB tự nhận và chia tải cho nó, không cần sửa code backend.
apiVersion: apps/v1
kind: Deployment
metadata:
name: order-service
spec:
replicas: 2
template:
spec:
containers:
- name: order-service
image: nonladev/order-service:1.4.0
ports:
- containerPort: 8080
---
apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
name: order-service-hpa
spec:
scaleTargetRef:
apiVersion: apps/v1
kind: Deployment
name: order-service
minReplicas: 2
maxReplicas: 10
metrics:
- type: Resource
resource:
name: cpu
target:
type: Utilization
averageUtilization: 70
Ở đây, Kubernetes Service (đóng vai trò LB nội bộ) tự động cân bằng traffic giữa các pod order-service. HorizontalPodAutoscaler theo dõi CPU — khi vượt 70%, Kubernetes tạo thêm pod (tối đa 10), và LB ngay lập tức đưa pod mới vào danh sách phục vụ mà không cần redeploy hay restart gì cả.
Lưu ý: Scaling chỉ hiệu quả nếu backend là stateless (hoặc session đã externalize như mục 4). Nếu không, pod mới thêm vào sẽ không nhận được session cũ của user.
7. Giảm nhẹ tấn công DDoS (DDoS Mitigation)
LB đứng ở lớp biên nên là nơi lý tưởng để chặn traffic bất thường trước khi nó chạm vào backend — bằng rate limiting hoặc phân tán traffic ra diện rộng hơn.
limit_req_zone $binary_remote_addr zone=api_limit:10m rate=20r/s;
server {
location /orders/ {
limit_req zone=api_limit burst=10 nodelay;
proxy_pass http://order_service;
}
}
limit_req_zone giới hạn mỗi IP tối đa 20 request/giây. burst=10 cho phép vượt ngắn hạn tối đa 10 request xếp hàng, nodelay xử lý burst ngay thay vì làm chậm lại. Vượt quá giới hạn này, nginx trả 503 Service Temporarily Unavailable thay vì để request dồn vào backend.
Nếu muốn rate limit ở tầng application (bổ sung, không thay thế LB), dùng Bucket4j trong Spring Boot:
<dependency>
<groupId>com.bucket4j</groupId>
<artifactId>bucket4j-core</artifactId>
<version>8.10.1</version>
</dependency>
Bucket bucket = Bucket.builder()
.addLimit(Bandwidth.simple(20, Duration.ofSeconds(1)))
.build();
@GetMapping("/orders")
public ResponseEntity<?> getOrders() {
if (!bucket.tryConsume(1)) {
return ResponseEntity.status(429).body("Too many requests");
}
return ResponseEntity.ok(orderService.findAll());
}
Lưu ý: Rate limit theo IP dễ bị qua mặt bằng IP giả mạo hoặc botnet phân tán. Với DDoS quy mô lớn, cần kết hợp LB với CDN/WAF ở tầng trước, LB chỉ là lớp phòng thủ thứ hai.
8. Theo dõi sức khỏe backend (Health Monitoring)
Khác với mục 5 (phản ứng khi request thất bại), health monitoring là LB chủ động gọi một endpoint định kỳ để biết server còn sống hay không — gọi là active health check.
@RestController
public class HealthController {
@GetMapping("/health")
public ResponseEntity<String> health() {
boolean dbUp = checkDatabaseConnection();
if (!dbUp) {
return ResponseEntity.status(503).body("DB_DOWN");
}
return ResponseEntity.ok("OK");
}
private boolean checkDatabaseConnection() {
// kiểm tra connection pool, trả true/false
return true;
}
}
Với Spring Boot Actuator, bạn có sẵn endpoint tương tự mà không cần viết tay:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
management.endpoints.web.exposure.include=health
management.endpoint.health.show-details=always
LB (hoặc Kubernetes) gọi /actuator/health mỗi vài giây:
livenessProbe:
httpGet:
path: /actuator/health
port: 8080
periodSeconds: 5
failureThreshold: 3
Viết test để đảm bảo endpoint phản ứng đúng khi DB lỗi:
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
class HealthControllerTest {
@Autowired
private TestRestTemplate restTemplate;
@Test
void shouldReturn503WhenDatabaseDown() {
// giả lập DB down qua profile test hoặc mock bean
ResponseEntity<String> response =
restTemplate.getForEntity("/health", String.class);
assertThat(response.getStatusCode().value()).isIn(200, 503);
}
}
Test này đảm bảo endpoint /health luôn trả về status hợp lệ (200 hoặc 503) mà không throw exception — vì nếu health check tự nó lỗi, LB sẽ hiểu nhầm là server đang down và loại nhầm instance đang khỏe.
Lưu ý: Health check endpoint phải nhẹ và nhanh (dưới vài trăm ms). Nếu nó query nhiều bảng hay gọi service khác, chính health check lại trở thành nguyên nhân gây timeout và bị LB loại oan.
9. Kết luận
Load Balancer không chỉ là “cái chia tải” — nó còn đóng vai trò giảm tải SSL, giữ session, phát hiện lỗi, hỗ trợ scale ngang, và là lớp phòng thủ đầu tiên chống DDoS. Khi thiết kế hệ thống, bạn nên xác định rõ mình cần công dụng nào trong 7 công dụng trên để chọn đúng tính năng khi cấu hình LB — nginx, HAProxy, AWS ALB hay Kubernetes Service đều hỗ trợ hầu hết các use case này, chỉ khác ở cách khai báo.
Lời khuyên cuối: đừng dồn hết trách nhiệm vào một LB duy nhất. Kết hợp health check chủ động, rate limiting, và session externalize sẽ giúp hệ thống của bạn vừa scale tốt vừa chịu lỗi tốt hơn nhiều so với chỉ bật round robin rồi để đó.