Kỹ Sư Thực Chiến - Tư Duy Lường Trước Rủi Ro Hệ Thống Tập 2: "Cơn Lũ Bất Ngờ" (Tư duy chịu tải và nghẽn cổ chai)
Khi làm một hệ thống bình thường, request vào luồng nào ta xử lý luồng đó. Nhưng nếu thiết kế một ứng dụng đặt vé xem phim lúc công chiếu bom tấn, hay một đợt flash sale của sàn thương mại điện tử, việc để traffic đâm thẳng vào tầng xử lý lõi chẳng khác nào mở toang cửa đập thủy điện cho nước cuốn trôi mọi thứ. Thằng "chết" đầu tiên và kéo theo sự sụp đổ của toàn hệ thống luôn là Database.
Dưới đây là tư duy phòng thủ từ xa của một Kỹ sư kinh nghiệm để chặn đứng cơn lũ này.
1. Tư duy "Bộ Giảm Xóc" (Shock Absorber với Message Queue)
Người mới thường có thói quen: Nhận request -> Validate -> Mở transaction -> Ghi vào PostgreSQL -> Đợi luồng phụ (gửi email, push notification) chạy xong -> Trả về HTTP 200. Khi traffic tăng gấp 100 lần bình thường, DB sẽ cạn kiệt Connection Pool và văng lỗi Too many connections ngay lập tức.
- Góc nhìn của Senior: Không bao giờ ép Database phải chạy theo tốc độ của Client. Phải có một vùng đệm (Buffer).
Chiến thuật triển khai:
- Offload to Queue: Nhận request đặt vé, thay vì đâm thẳng xuống DB, hãy ném ngay payload đó (chứa
UserID,MovieID,SeatID) vào một Message Broker (như Kafka hoặc RabbitMQ). - Fast Response: Ngay sau khi đẩy vào Queue thành công, trả về cho Client HTTP 202 (Accepted) kèm theo một
Ticket_Booking_ID. Giao diện (App/Web) sẽ hiển thị trạng thái "Đang xử lý" và dùng cơ chế Polling hoặc WebSocket để lắng nghe kết quả cuối cùng. - Kiểm soát nhịp độ (Rate-controlled Consumption): Ở phía sau, các Worker (viết bằng Golang chẳng hạn) sẽ bình tĩnh kéo data từ Queue xuống để xử lý và ghi vào DB với tốc độ mà hệ thống chịu đựng được. Dù có 100,000 request ập tới, DB vẫn chỉ xử lý đều đặn 1,000 req/s.
2. Cuộc chiến "Tranh giành tài nguyên" (Race Condition & Locking)
Trong kịch bản bán vé, thảm họa lớn nhất không phải là server sập, mà là bán lố (Overselling): 10,000 người cùng tranh nhau 1 cái ghế cuối cùng, và code của bạn báo thành công cho... cả 10 người.
- Góc nhìn của Senior: Phải khóa chặt tài nguyên ở cấp độ nguyên tử (Atomic) khi có tranh chấp.
Chiến thuật triển khai (Phòng thủ 2 lớp):
- Lớp 1 - Khóa mềm tại Memory (Redis TTL): Khi user bấm chọn ghế, lập tức ghi một key vào Redis với thời gian sống (TTL) là 5 phút (thời gian giữ chỗ để thanh toán). Nếu user khác bấm vào ghế đó, kiểm tra thấy key Redis đang tồn tại -> Báo "Ghế đang được người khác giữ". Cơ chế này giúp cản 99% lượng traffic rác đập xuống DB.
- Lớp 2 - Khóa cứng tại Database (Pessimistic Locking): Khi user thanh toán thành công và hệ thống chốt vé, Worker phải dùng Row-level locking. Câu lệnh
SELECT * FROM seats WHERE id = X FOR UPDATEtrong PostgreSQL sẽ ép các transaction khác đang nhòm ngó cái ghế này phải đứng xếp hàng chờ transaction hiện tại commit hoặc rollback xong mới được đi tiếp. Tuyệt đối không đọc (SELECT) rồi mới ghi (UPDATE) một cách rời rạc trong môi trường đa luồng.
3. Cắt tỉa traffic ngay từ cửa (Rate Limiting & Throttling)
Khi có một cuộc tấn công DDoS, hoặc đơn giản là hệ thống đối tác gọi API của mình quá rát vì lỗi vòng lặp bên họ, mình không thể mở cửa đón hết rồi mới báo lỗi.
- Góc nhìn của Senior: Hệ thống phải biết tự bảo vệ mình bằng cách vứt bỏ bớt request trước khi chúng chạm vào Business Logic.
Chiến thuật triển khai:
- Thiết lập Rate Limit tại tầng API Gateway hoặc Ingress. Sử dụng các thuật toán như Token Bucket hoặc Leaky Bucket (lưu trạng thái giới hạn trên Redis) để giới hạn mỗi IP/User chỉ được gọi tối đa 5 requests/giây.
- Vượt quá ngưỡng này -> Trả thẳng HTTP 429 (Too Many Requests). Chặn đứng việc hệ thống bị vắt kiệt CPU/Memory chỉ để xử lý những luồng spam.
4. Đừng đoán, hãy bắn phá hệ thống (Stress Testing)
Senior không bao giờ tự tin nói "Kiến trúc này của anh chịu được 10k CCU (Concurrent Users)" nếu chưa tự tay đánh sập nó trên môi trường Test.
- Góc nhìn của Senior: Phải biết giới hạn chịu đựng (Breaking Point) của hệ thống nằm ở đâu: Do cạn RAM, do CPU chạm nóc 100%, hay do I/O của ổ cứng nghẽn?
Chiến thuật triển khai:
- Chuẩn bị kịch bản bằng các tool Load Test (như Vegeta). Bơm một lượng lớn request ảo đập thẳng vào các API lõi.
- Quan sát hệ thống đo lường. Không chỉ nhìn xem code có quăng lỗi 500 không, mà phải mở
htopxem cấp phát tài nguyên ra sao, bật Grafana lên xem metrics từ Prometheus báo về xem memory leak ở đoạn nào, thời gian phản hồi (Latency) tăng vọt bắt đầu từ ngưỡng request thứ bao nhiêu. Từ đó mới quyết định Tuning (tinh chỉnh cấu hình) hoặc Scale (thêm node).
All rights reserved