2.11 — Mở rộng và đào sâu
Những chủ đề nối tiếp Module 2, kèm mức độ ưu tiên. Hai mục nên học sớm vì ảnh hưởng trực tiếp tới công việc backend hàng ngày: caching HTTP (biết ETag và Cache-Control giúp giảm tải rõ rệt mà gần như không tốn công) và container (mọi hệ thống hiện đại đều triển khai bằng Docker). Hai mục còn lại — HTTP/2, HTTP/3 và WebSocket — hữu ích để hiểu nhưng ít khi bạn phải cấu hình trực tiếp, vì reverse proxy hoặc framework đã lo phần lớn. Và một điểm hay bị hiểu nhầm: HTTP/2 không tự động làm ứng dụng nhanh hơn — nó giải quyết một vấn đề rất cụ thể, và với API trả JSON thì vấn đề đó thường không tồn tại.
Mục tiêu bài học
Sau bài này bạn có thể:
- Biết những chủ đề tiếp theo và thứ tự ưu tiên.
- Hiểu HTTP/2 và HTTP/3 giải quyết vấn đề gì.
- Dùng caching HTTP để giảm tải.
- Biết khi nào cần WebSocket.
Nội dung bài học
2.11.1 — Caching HTTP: học sớm
Ưu tiên cao. Rẻ để làm, hiệu quả rõ ràng.
GET /api/products/123
HTTP/1.1 200 OK
Cache-Control: public, max-age=3600
ETag: "a3f8b2c1"
GET /api/products/123
If-None-Match: "a3f8b2c1"
HTTP/1.1 304 Not Modified <- KHÔNG có body, tiết kiệm băng thông
Hai cơ chế bổ sung nhau:
| Cơ chế | Cách hoạt động | Khi nào dùng |
|---|---|---|
Cache-Control | Trình duyệt không gửi request trong thời hạn | Dữ liệu ít đổi, biết trước thời hạn |
ETag | Vẫn gửi request nhưng chỉ nhận 304 nếu không đổi | Dữ liệu có thể đổi bất cứ lúc nào |
ETag tiết kiệm băng thông nhưng vẫn tốn một lượt đi về; Cache-Control tiết kiệm cả lượt đi về nhưng có rủi ro dữ liệu cũ.
Chỉ thị quan trọng nhất cần phân biệt:
Cache-Control: public # CDN và proxy được phép cache
Cache-Control: private # CHỈ trình duyệt của người dùng
Cache-Control: no-store # KHÔNG được lưu ở đâu cả
Nhầm private thành public cho dữ liệu riêng của người dùng là lỗ hổng thật: CDN có thể phục vụ dữ liệu của người này cho người khác.
ASP.NET Core có sẵn output caching (bài 9.8).
2.11.2 — Container: học sớm
Ưu tiên cao. Mọi hệ thống hiện đại triển khai bằng container.
FROM mcr.microsoft.com/dotnet/aspnet:9.0
WORKDIR /app
COPY ./publish .
ENTRYPOINT ["dotnet", "Crm.Api.dll"]
Container giải quyết một vấn đề rất cụ thể: "máy tôi chạy được mà máy anh không". Nó đóng gói ứng dụng cùng runtime và mọi phụ thuộc thành một ảnh chạy giống hệt nhau ở mọi nơi.
Ba khái niệm đủ để bắt đầu: image (bản đóng gói), container (một thực thể đang chạy của image), volume (nơi lưu dữ liệu bền, vì container mất dữ liệu khi bị xoá).
Đây là chủ đề của Module 15.
2.11.3 — HTTP/2 và HTTP/3
Ưu tiên trung bình. Hiểu để biết, ít khi phải tự cấu hình.
Vấn đề HTTP/2 giải quyết:
HTTP/1.1: mỗi kết nối TCP xử lý MỘT request tại một thời điểm
-> trình duyệt mở 6 kết nối song song
-> tải trang có 50 file phải chờ theo đợt
HTTP/2: MỘT kết nối, NHIỀU request song song (multiplexing)
+ nén header
+ server đẩy trước tài nguyên
Điểm hay bị hiểu nhầm: lợi ích của HTTP/2 chủ yếu dành cho trang web có nhiều tài nguyên nhỏ (ảnh, CSS, JavaScript). Với một API trả về JSON theo từng request riêng lẻ, vấn đề "50 file phải chờ theo đợt" không tồn tại, nên cải thiện là không đáng kể.
HTTP/3 thay TCP bằng QUIC trên UDP, giải quyết vấn đề còn lại của HTTP/2: khi một gói tin bị mất, TCP chặn mọi luồng cho tới khi gói đó được gửi lại. QUIC chỉ chặn luồng bị ảnh hưởng — lợi ích rõ trên mạng di động hay mất gói.
Thực tế: bạn hiếm khi cấu hình trực tiếp. Reverse proxy hoặc CDN nói HTTP/2 với client và HTTP/1.1 với backend, và mọi thứ hoạt động.
gRPC dùng HTTP/2 làm nền, và đó là nơi bạn gặp nó rõ nhất (bài 18.5).
2.11.4 — WebSocket
Ưu tiên trung bình. Cần khi có tính năng thời gian thực.
HTTP: Client hỏi -> Server trả lời -> kết thúc
WebSocket: Kết nối MỞ liên tục, CẢ HAI bên gửi bất cứ lúc nào
Cần khi: chat, thông báo đẩy, bảng giá cập nhật liên tục, hiển thị ai đang online.
Không cần khi dữ liệu đổi vài phút một lần — khi đó gọi lại API định kỳ đơn giản hơn nhiều và không phải lo kết nối đứt, scale nhiều instance, hay xử lý kết nối lại.
Trên .NET, SignalR bọc WebSocket và tự chuyển sang cơ chế khác nếu WebSocket không dùng được (Module 11).
Cái bẫy khi scale: với nhiều instance, tin nhắn gửi từ instance A không tới client đang kết nối vào instance B, trừ khi có backplane (bài 11.11).