Mở đầu — vì sao bài này quan trọng
Ở Bài 6 và Bài 7, bạn đã làm quen với mô hình client-server, HTTP và cách thiết kế REST API. Đó là nền móng vững chắc cho phần lớn sản phẩm web hiện nay. Nhưng nếu bạn chỉ biết REST/JSON, bạn sẽ giống một PM chỉ có một cây búa — và rồi mọi vấn đề trông đều giống một cái đinh.
Thực tế là có những bài toán mà REST/HTTP cổ điển làm rất tệ. Khi Tiki cần cập nhật trạng thái đơn hàng "shipper đang đến" theo thời gian thực trên app, khi Shopee Live cần đẩy bình luận và lượt tim của hàng chục nghìn người xem cùng lúc, khi một hệ thống microservices của ngân hàng cần hàng trăm service gọi nhau với độ trễ tính bằng mili-giây — REST/JSON bắt đầu lộ ra điểm yếu: nó chậm, nó tốn băng thông, và quan trọng nhất, nó là mô hình "hỏi-đáp" một chiều (client hỏi, server mới trả lời), trong khi nhiều bài toán cần server chủ động đẩy dữ liệu xuống client.
Là một Technical PM, bạn không cần viết code gRPC hay cấu hình WebSocket server. Nhưng bạn PHẢI hiểu đủ sâu để: (1) nhận ra khi nào một feature cần real-time và đề xuất đúng protocol, (2) tranh luận có lý với engineer khi họ muốn dùng gRPC cho internal service, và (3) đánh giá đúng chi phí — vì các protocol này có trade-off rất khác nhau về infrastructure, độ phức tạp và khả năng debug. Chọn sai protocol có thể khiến team mất hàng tháng làm lại, hoặc khiến chi phí server tăng gấp đôi mà trải nghiệm vẫn giật lag.
Khái niệm cốt lõi
gRPC — giao tiếp service-to-service tốc độ cao
gRPC là một framework giao tiếp do Google phát triển, xây dựng trên hai nền tảng kỹ thuật: HTTP/2 và Protocol Buffers (thường gọi tắt là protobuf).
Hãy hiểu từng phần. HTTP/2 là phiên bản nâng cấp của HTTP mà bạn đã học, cho phép gửi nhiều request song song trên cùng một kết nối (multiplexing), nén header, và quan trọng nhất là hỗ trợ streaming hai chiều. Protocol Buffers là cách mã hóa dữ liệu dưới dạng nhị phân (binary) thay vì text như JSON.
Sự khác biệt nằm ở đây. JSON là text dễ đọc với con người: {"user_id": 12345, "name": "Nguyen Van A"}. Còn protobuf nén cùng dữ liệu đó thành chuỗi byte mà mắt người không đọc được, nhưng nhỏ hơn 3–10 lần và máy parse nhanh hơn rất nhiều. Đây là lý do gRPC thường nhanh hơn REST/JSON khoảng 5–10 lần cho giao tiếp giữa các service nội bộ.
Điểm mạnh thứ hai của gRPC là strong typing (định kiểu chặt chẽ) thông qua file .proto. Bạn định nghĩa cấu trúc dữ liệu và các hàm một lần trong file này:
service OrderService {
rpc GetOrder(OrderRequest) returns (OrderResponse);
}
message OrderRequest {
int64 order_id = 1;
}
Từ file .proto này, gRPC tự sinh code cho cả client lẫn server, bằng nhiều ngôn ngữ (Go, Java, Python, Node...). Nghĩa là nếu team Backend đổi tên một field, team gọi tới sẽ biết ngay khi compile, không phải đợi đến lúc chạy production mới phát hiện. Đây là khác biệt lớn so với REST/JSON, nơi "hợp đồng" giữa hai bên thường chỉ là tài liệu mà ai cũng có thể đọc sai.
gRPC còn hỗ trợ 4 kiểu giao tiếp: unary (1 request – 1 response, giống REST), server streaming (1 request – nhiều response), client streaming (nhiều request – 1 response), và bidirectional streaming (cả hai cùng stream). Tính năng streaming này khiến gRPC mạnh hơn REST rất nhiều cho các luồng dữ liệu liên tục.
Nhược điểm: gRPC không chạy được trực tiếp trên trình duyệt (browser không cho phép kiểm soát HTTP/2 ở mức gRPC cần), nên nó hiếm khi dùng cho giao tiếp giữa web app và server — phải dùng thêm lớp gRPC-Web hoặc proxy. Vì dữ liệu là binary nên cũng khó debug hơn: bạn không thể mở Chrome DevTools và đọc payload như JSON. Do đó, gRPC chủ yếu được dùng cho giao tiếp nội bộ giữa các microservices trong backend.
WebSocket — kênh hai chiều luôn mở
Với HTTP/REST truyền thống, mỗi lần client muốn biết thông tin mới, nó phải tự hỏi server (gọi là polling). Muốn cập nhật real-time, nhiều app phải "hỏi lại mỗi 2 giây" — vừa tốn tài nguyên, vừa trễ.
WebSocket giải quyết triệt để vấn đề này. Nó mở một kết nối duy nhất, hai chiều, luôn được giữ mở giữa client và server. Sau khi "bắt tay" (handshake) ban đầu qua HTTP, kết nối được nâng cấp (upgrade) thành WebSocket, và từ đó cả client lẫn server đều có thể chủ động gửi dữ liệu cho nhau bất cứ lúc nào, không cần hỏi trước.
Đây là protocol nền tảng cho mọi tính năng real-time mà bạn thấy hàng ngày: chat trong app, thông báo đẩy ngay lập tức, bình luận live stream, cập nhật giá chứng khoán/crypto theo từng giây, vị trí tài xế trên bản đồ Grab/Be di chuyển mượt mà.
Trade-off của WebSocket: vì kết nối luôn mở, mỗi người dùng online chiếm một kết nối thường trực trên server. Nếu bạn có 100.000 người xem live cùng lúc, server phải duy trì 100.000 kết nối. Điều này đòi hỏi infrastructure khác hẳn REST (vốn xử lý xong request là đóng kết nối ngay), và việc scale theo chiều ngang phức tạp hơn nhiều.
Các lựa chọn real-time khác: SSE và Long Polling
WebSocket không phải lựa chọn duy nhất cho real-time. Có hai "người anh em" đơn giản hơn mà PM nên biết:
Server-Sent Events (SSE): kênh một chiều từ server xuống client, chạy trên HTTP thông thường. Khi bạn chỉ cần server đẩy dữ liệu xuống (ví dụ: feed tin tức, thông báo, hiển thị câu trả lời của AI chatbot "gõ" dần ra từng chữ), SSE đơn giản hơn WebSocket nhiều, dễ scale hơn, và không cần infrastructure đặc biệt. ChatGPT và nhiều app AI dùng SSE để stream câu trả lời.
Long Polling: kỹ thuật "cũ kỹ" nhưng vẫn hữu dụng — client gửi request, server giữ request đó mở cho đến khi có dữ liệu mới rồi mới trả lời. Tương thích với mọi hạ tầng HTTP cũ, là phương án dự phòng khi WebSocket bị firewall chặn.
Nguyên tắc vàng cho PM: đừng mặc định chọn cái phức tạp nhất. Nếu chỉ cần đẩy dữ liệu một chiều, SSE thường là đủ và rẻ hơn WebSocket rất nhiều.
Tình huống thực tế
Ví dụ 1: Grab và bài toán theo dõi vị trí tài xế
Hãy tưởng tượng team Grab đang xây tính năng hiển thị xe của tài xế di chuyển trên bản đồ trong lúc bạn chờ. Phiên bản đầu, team dùng REST: app gọi API /driver/location mỗi 3 giây để lấy toạ độ mới. Kết quả? Chấm xe trên bản đồ "nhảy" giật cục, và với hàng trăm nghìn chuyến đi đồng thời ở Đông Nam Á, số lượng request khổng lồ này khiến chi phí server tăng vọt — phần lớn request trả về "vị trí không đổi", lãng phí hoàn toàn.
Team chuyển sang WebSocket. Mỗi chuyến đi mở một kết nối, server đẩy toạ độ tài xế xuống ngay khi có cập nhật (khoảng mỗi giây). Chấm xe di chuyển mượt mà, độ trễ giảm xuống dưới 1 giây, và quan trọng là server không còn xử lý hàng triệu request "rỗng" nữa.
Bài học rút ra: Khi dữ liệu thay đổi liên tục và người dùng cần thấy ngay, polling qua REST vừa cho trải nghiệm tệ vừa đắt. Là PM, nếu bạn thấy spec ghi "cập nhật mỗi X giây", đó là tín hiệu nên ngồi lại với engineer bàn về real-time protocol.
Ví dụ 2: Một fintech Việt và lựa chọn gRPC cho lõi thanh toán
Một công ty fintech tại TP.HCM (gọi là PayViet) tách hệ thống thành nhiều microservices: service tài khoản, service ví, service hạn mức, service chống gian lận. Mỗi giao dịch chuyển tiền phải đi qua 5–6 service gọi lẫn nhau. Ban đầu họ dùng REST/JSON nội bộ, và đo được mỗi giao dịch mất khoảng 800ms — phần lớn thời gian "đốt" vào việc serialize/deserialize JSON qua lại giữa các service.
Khi chuyển các kết nối nội bộ này sang gRPC, thời gian xử lý một giao dịch giảm còn khoảng 250ms. Ngoài tốc độ, lợi ích lớn không kém là strong typing: trước đây, một lần engineer team Ví đổi kiểu field amount từ số nguyên (đơn vị đồng) sang số thập phân khiến team Hạn mức tính sai và lỗi chỉ lộ ra ở production. Với file .proto làm "hợp đồng" chung, kiểu thay đổi như vậy bị bắt ngay lúc build.
Bài học rút ra: gRPC toả sáng ở giao tiếp service-to-service trong backend, nơi tốc độ và tính nhất quán dữ liệu quan trọng hơn khả năng debug bằng mắt. Nhưng lưu ý: PayViet vẫn giữ REST cho API mà app mobile và web gọi vào, vì gRPC khó dùng trực tiếp từ trình duyệt. Đây là kiến trúc rất phổ biến: REST ở rìa (edge), gRPC ở lõi (core).
Ví dụ 3: Shopee Live và bài toán scale WebSocket
Đội Shopee Live làm tính năng bình luận và thả tim trong các phiên live bán hàng. Một phiên live "hot" có thể có 50.000–200.000 người xem đồng thời. Họ chọn WebSocket để đẩy bình luận xuống real-time — đúng protocol. Nhưng vấn đề mới phát sinh: một server không thể giữ 200.000 kết nối, nên phải dùng nhiều server. Khi đó, người dùng A kết nối vào server 1 gửi bình luận, nhưng người dùng B đang ở server 2 — làm sao B nhận được bình luận của A?
Lời giải là thêm một lớp message broker (như Redis Pub/Sub hoặc Kafka) ngồi giữa các WebSocket server để phát tán tin nhắn tới mọi server. Đây là độ phức tạp infrastructure mà REST không bao giờ phải lo.
Bài học rút ra: Khi PM viết spec real-time, đừng chỉ nghĩ "dùng WebSocket là xong". Hãy hỏi engineer về chi phí scale: cần bao nhiêu server cho lượng concurrent users dự kiến, có cần message broker không, chi phí hạ tầng tăng thế nào. Một feature live tưởng nhỏ có thể kéo theo cả một mảng infrastructure mới.
Hướng dẫn từng bước
Đây là khung quyết định bạn có thể dùng khi đối mặt một feature có yếu tố giao tiếp/real-time:
Bước 1 — Xác định hướng dữ liệu. Hỏi: dữ liệu đi một chiều (server → client) hay hai chiều? Client chỉ cần nhận cập nhật, hay cũng cần gửi liên tục?
Bước 2 — Xác định client là ai. Là trình duyệt/app mobile gọi vào, hay là service backend gọi service backend? Nếu là internal service-to-service và cần tốc độ cao → nghiêng về gRPC. Nếu là client cuối → loại gRPC ra.
Bước 3 — Đánh giá nhu cầu real-time thật sự. Người dùng có cần thấy dữ liệu cập nhật trong vòng 1–2 giây không, hay 30 giây vẫn ổn? Nếu không thực sự cần real-time, REST + polling nhẹ có khi đủ và rẻ hơn nhiều.
Bước 4 — Chọn protocol theo nhu cầu:
- Hai chiều, độ trễ thấp (chat, game, vị trí) → WebSocket
- Một chiều server đẩy xuống (feed, thông báo, stream AI) → SSE
- Internal backend tốc độ cao → gRPC
- Đơn giản, một-hỏi-một-đáp, không cần real-time → giữ REST
Bước 6 — Ghi rõ trong Technical Spec. Nêu protocol đã chọn, lý do, và các trade-off đã cân nhắc, để mọi người sau này hiểu vì sao quyết định như vậy.
Lỗi thường gặp & mẹo
Lỗi 1 — Mặc định REST cho mọi thứ. Nhiều PM (và cả engineer) chọn REST theo quán tính, rồi "vá" nhu cầu real-time bằng polling dày đặc. Kết quả là server quá tải và trải nghiệm vẫn giật. Mẹo: hễ thấy chữ "real-time", "live", "cập nhật ngay", hãy dừng lại và xét đến WebSocket/SSE.
Lỗi 2 — Dùng gRPC cho API hướng ra ngoài. gRPC không thân thiện với trình duyệt và làm khó các đối tác tích hợp (họ quen REST). Đừng đề xuất gRPC cho public API mà bên thứ ba sẽ dùng — đó là lúc REST hoặc GraphQL phù hợp hơn. Hãy nhớ mô hình "REST ở rìa, gRPC ở lõi".
Lỗi 3 — Chọn WebSocket khi SSE là đủ. WebSocket phức tạp và tốn kém hơn để vận hành. Nếu dữ liệu chỉ đi một chiều từ server xuống (ví dụ luồng thông báo, hay câu trả lời AI gõ dần), SSE đơn giản và rẻ hơn nhiều. Đừng "dùng dao mổ trâu để giết gà".
Lỗi 4 — Quên chi phí kết nối thường trực. WebSocket giữ kết nối mở liên tục, nghĩa là chi phí và độ phức tạp tỷ lệ với số người dùng đồng thời, không phải số request. Một feature live cho 100.000 người xem là một bài toán infrastructure nghiêm túc.
Mẹo cho PM: Khi engineer đề xuất một protocol, hãy hỏi ba câu: "Client gọi vào là ai?", "Dữ liệu đi một chiều hay hai chiều?", và "Chi phí scale khi người dùng tăng 10 lần là gì?". Ba câu này đủ để bạn tham gia tranh luận một cách có trọng lượng.
Bài tập thực hành
- Phân loại protocol. Với mỗi feature sau, hãy chọn protocol phù hợp nhất (REST, gRPC, WebSocket, hay SSE) và viết 1–2 câu giải thích:
- Phản biện một quyết định. Một engineer đề xuất dùng WebSocket cho tính năng "thông báo hệ thống" (chỉ server đẩy thông báo xuống user, user không gửi gì lên). Hãy viết một đoạn ngắn phản biện: protocol nào nhẹ hơn sẽ phù hợp hơn, và tại sao?
- Ước lượng chi phí. Một feature live stream dự kiến đạt đỉnh 80.000 người xem đồng thời. Hãy liệt kê 3 câu hỏi về infrastructure/chi phí mà bạn sẽ đặt ra cho team engineering trước khi duyệt feature này.
Tóm tắt
- HTTP/REST không phải lựa chọn duy nhất. Nhiều bài toán cần protocol khác để có tốc độ hoặc real-time.
- gRPC dùng HTTP/2 + Protocol Buffers (binary), nhanh hơn REST/JSON 5–10 lần, có strong typing qua file
.proto. Hợp nhất cho giao tiếp service-to-service trong backend; khó dùng trực tiếp từ trình duyệt và khó debug. - WebSocket mở kênh hai chiều luôn-mở, là nền tảng cho mọi tính năng real-time (chat, vị trí, live). Trade-off lớn là chi phí và độ phức tạp scale theo số người dùng đồng thời.
- SSE là lựa chọn nhẹ hơn cho luồng dữ liệu một chiều từ server xuống — đừng dùng WebSocket khi SSE đã đủ.
- Mô hình kiến trúc phổ biến: REST/GraphQL ở rìa (cho client và đối tác), gRPC ở lõi (giữa các service).
- Vai trò của PM không phải chọn protocol thay engineer, mà là đặt đúng câu hỏi: client là ai, dữ liệu đi mấy chiều, chi phí scale ra sao — và đảm bảo trade-off được cân nhắc và ghi lại rõ ràng.