Backend

System Design — Bài 10: Giao tiếp giữa service: HTTP, gRPC, queue và realtime

SSite Admin
10 tháng 10, 2026 8 phút đọc 0 lượt xem

Khi một lời gọi hàm trở thành lời gọi mạng, timeout không cho biết bên nhận đã commit hay chưa. Đó là lý do lựa chọn giao tiếp phải bắt đầu từ bảo đảm nghiệp vụ, cách thử lại và khả năng quan sát, rồi mới tới kích thước payload hoặc tốc độ serialize.

Khóa học System Design: mục lục 21 bài — bạn đang đọc bài 10.

Năm tiêu chí chọn cơ chế giao tiếp

Một message bắt đầu từ logic nghiệp vụ, được serialize thành byte, đóng gói theo giao thức rồi đưa qua cơ chế I/O ra mạng. Phía nhận thực hiện quá trình ngược lại trước khi xử lý nghiệp vụ.

Khi chọn giải pháp, cân nhắc delivery guarantee, compatibility, performance, error handling và security. Các thuộc tính này trải nhiều tầng: giao thức truyền tin thành công chưa có nghĩa nghiệp vụ đã commit, và payload mã hóa trên đường truyền chưa có nghĩa người nhận được phép thực hiện thao tác.

Ví dụ, schema evolution thuộc hợp đồng dữ liệu, retry thuộc cả client lẫn nghiệp vụ, còn xác thực có thể cần ở nhiều ranh giới. Tách các bước trên để xác định đoạn đang được tối ưu hoặc bảo vệ. Đừng lấy tốc độ serialize để kết luận toàn bộ request chắc chắn nhanh hơn.

Network I/O model — ẩn dụ đun nước

Blocking I/O giống đứng chờ một ấm nước: luồng gọi chờ tới khi thao tác có kết quả. Với platform thread truyền thống, quá nhiều luồng chờ có thể tốn tài nguyên. Chi phí cụ thể thay đổi theo runtime, nên không đồng nhất mọi kiểu blocking với một OS thread đắt tiền.

Non-blocking cho phép lời gọi trả về ngay nếu chưa sẵn sàng, nhưng nếu tự kiểm tra liên tục sẽ tốn công polling. Multiplexing dùng cơ chế như selector để biết trong nhiều kết nối, kết nối nào sẵn sàng cho bước tiếp theo.

Ẩn dụ nhiều ấm giúp nhớ cách chờ, nhưng xử lý dữ liệu vẫn phải diễn ra. Một event loop hiệu quả cần tránh công việc chặn lâu; tính toán nặng hoặc lời gọi blocking phải được tổ chức phù hợp để không làm các kết nối khác cùng chờ.

Handling pattern & protocol

Trong Reactor, event loop nhận sự kiện sẵn sàng rồi chuyển cho handler. Có thể tách nhận kết nối, xử lý I/O và worker làm công việc nặng. Số loop và cách phân công tùy implementation; ý chính là không để một handler chặn đường phục vụ nhiều kết nối.

Ở tầng giao thức, HTTP/2 dùng frame nhị phân và nhiều stream trên một kết nối TCP. Multiplexing giảm nhu cầu mở nhiều kết nối, nhưng mất gói ở TCP vẫn có thể ảnh hưởng tiến độ các stream trên kết nối đó.

HPACK nén header bằng bảng tĩnh, bảng động và biểu diễn literal hoặc tham chiếu. Vì vậy header lặp có thể biểu diễn gọn; không nên nói request sau tự kế thừa mọi header không đổi. Server push là tính năng được mô tả trong giao thức, nhưng không nên mặc định client mục tiêu có hỗ trợ hoặc việc bật nó luôn có lợi.

So sánh serialization, API và message broker

JSON dễ quan sát và phổ biến; Protobuf dùng schema và biểu diễn nhị phân, thường giảm payload trong nhiều workload nhưng cần công cụ sinh code và quản lý tương thích. Không tái sử dụng field number đã bỏ, và phải kiểm tra quy tắc thay đổi từng kiểu dữ liệu.

REST phù hợp nhiều API công khai nhờ hệ sinh thái HTTP. gRPC hữu ích khi cần hợp đồng rõ, code generation và các kiểu streaming giữa những client có hỗ trợ. REST không bị buộc vào HTTP/1.1, nên đừng nhầm phong cách API với phiên bản transport.

Với bất đồng bộ, Kafka nhấn vào log giữ lại và replay, còn broker dạng queue có thể phù hợp phân phối công việc và routing. Các sản phẩm có tính năng giao nhau; cần so trên cùng độ bền, ack, payload và topology. Tên sản phẩm không thay được phân tích yêu cầu.

Bất đồng bộ trong thực tế

Queue tách nhịp producer và consumer, giúp hấp thụ một đợt tải tạm thời. Nó không tăng vô hạn tốc độ xử lý cuối cùng. Nếu đầu vào luôn cao hơn đầu ra, backlog sẽ tiếp tục tăng tới giới hạn retention hoặc dung lượng.

Với at-least-once, message có thể được xử lý lại. Consumer cần nhận diện cùng một yêu cầu và giữ tác động nghiệp vụ không bị lặp. Ghi dấu đã xử lý rồi cập nhật nghiệp vụ ở hai transaction khác nhau vẫn có cửa sổ lỗi.

Trong competing consumers, một nhóm chia công việc; với pub/sub, các nhóm quan tâm có thể nhận bản của mình. Phải xác định “một người nhận” đang nói trong phạm vi queue hay consumer group. Ngoài đường thành công, luôn vẽ thêm retry, message lỗi và cách quan sát backlog.

DLQ & Backpressure — hai van an toàn của message queue

DLQ, Dead-Letter Queue, giữ message không xử lý được sau chính sách thử lại. Kèm lỗi, số lần thử và định danh giúp người vận hành sửa rồi replay. Đưa message sang DLQ có thể làm mất thứ tự nghiệp vụ nếu các message sau phụ thuộc nó, nên cần quyết định theo từng loại luồng.

Backpressure phản ánh việc bên nhận không theo kịp. Ta có thể giới hạn in-flight, giảm admission, làm chậm nguồn hoặc bỏ dữ liệu được phép bỏ. Buffer phải có giới hạn và hành vi rõ khi đầy.

Consumer Kafka chủ động poll giúp điều khiển tốc độ lấy dữ liệu, nhưng không tự làm producer ngừng ghi. Vẫn phải theo dõi lag, retention và quota hoặc chính sách đầu vào. DLQ giải message xấu; backpressure giải tốc độ lệch, nên cần cả hai nếu bài toán có cả hai loại rủi ro.

Realtime tới client

WebSocket cung cấp kết nối hai chiều lâu dài, phù hợp chat và cộng tác. SSE, Server-Sent Events, phục vụ dòng sự kiện từ server tới client qua HTTP; client vẫn có thể dùng request HTTP riêng để gửi dữ liệu lên. Chọn theo hướng và kiểu tương tác thực tế.

Với thiết bị, MQTT tổ chức publish–subscribe qua broker. Trên trình duyệt có thể dùng MQTT qua WebSocket khi stack hỗ trợ. Phải xem QoS, session, kích thước thông điệp và hành vi reconnect chứ không chỉ số kết nối.

QUIC chạy trên UDP và hỗ trợ nhiều stream, nhưng không nên coi MQTT qua QUIC là tính năng mặc định của mọi client hoặc broker. Cần kiểm tra đúng sản phẩm và phiên bản. Dù chọn kênh nào, vẫn cần heartbeat, xác thực, resume và giới hạn tài nguyên khi người dùng mất mạng rồi kết nối lại hàng loạt.

Queue hấp thụ burst nhưng cần kế hoạch xả backlog

Producer: 10.000 message/s
Consumer:  8.000 message/s
Trong 60 giây: backlog tăng (10.000 - 8.000) x 60 = 120.000

Nếu đầu vào sau đó còn 4.000/s, consumer vẫn 8.000/s:
Tốc độ xả backlog = 8.000 - 4.000 = 4.000/s
Thời gian xả lý tưởng = 120.000 / 4.000 = 30 giây

Consumer -> nghiệp vụ + dedup trong cùng transaction
Lỗi tạm thời -> retry có giới hạn
Lỗi dai dẳng -> DLQ + điều tra + replay có kiểm soát

Phép tính bỏ qua retry, lỗi và thay đổi tốc độ xử lý. Cần đo lag, tuổi message lâu nhất, retention và dung lượng. Consumer poll chậm lại không tự ngăn producer tiếp tục ghi.

Thuật ngữ cần nhớ

  • HTTP — Hypertext Transfer Protocol: Giao thức trao đổi request/response của web. HTTP/1.1 và HTTP/2 là các phiên bản với cách truyền và ghép luồng khác nhau.

  • I/O — Input/Output: Vào/ra: trao đổi dữ liệu với đĩa, mạng hoặc thiết bị; I/O-bound là tải dành nhiều thời gian chờ các thao tác này.

  • DLQ — Dead-Letter Queue: Hàng đợi chứa thông điệp không xử lý được sau chính sách retry; phục vụ điều tra, sửa và phát lại có kiểm soát.

  • SSE — Server-Sent Events: Cơ chế máy chủ đẩy chuỗi sự kiện một chiều tới trình duyệt qua kết nối web; khác kênh hai chiều như WebSocket.

  • MQTT — Message Queuing Telemetry Transport (tên thường dùng); chuẩn gọi giao thức là MQTT: Giao thức thông điệp publish/subscribe nhẹ, phù hợp thiết bị và kết nối hạn chế tài nguyên; thông điệp đi qua broker theo topic.

Bài tập tự thực hành

Producer 10K message/s, consumer 8K message/s trong 60 giây.

Gợi ý kiểm tra lời giải

Backlog tăng 120K message, nếu không có yếu tố khác; cần xét dung lượng, retention và thời gian hồi phục.

Tự kiểm tra sau bài học

  • Phân biệt nhận byte, lưu message và commit nghiệp vụ.

  • Retry không tạo tác động trùng nhờ key và transaction phù hợp.

  • Buffer có giới hạn và hành vi rõ khi đầu vào vượt khả năng xử lý.

Tiếp tục lộ trình

Bài trước — Caching và Redis: chiến lược ghi, rate limit và các bẫy thường gặp

Bài tiếp — Nhất quán trong hệ phân tán: CAP, Raft và đồng hồ logic

Xem toàn bộ khóa học System Design 21 bài

S

Site Admin

Engineer and writer. Building things with TypeScript and distributed systems.

Bình luận (0)

Bạn cần đăng nhập bằng Google để bình luận.

Hãy là người bình luận đầu tiên.

Bài viết liên quan

K

Học System Design qua 21 bài: yêu cầu, capacity, database, cache, hệ phân tán và 6 bài thực hành, kèm bài tập, sơ đồ luồng và mục lục đầy đủ.

10 thg 10, 20266 phút2
S

Học cách đi từ vấn đề đến kiến trúc, phân biệt architecture với design và bảo vệ lựa chọn bằng yêu cầu, số liệu và đánh đổi.

10 thg 10, 20266 phút2
S

Đặt câu hỏi về người dùng, thao tác, tải, độ trễ và tính đúng đắn trước khi chọn database hay vẽ sơ đồ kiến trúc.

10 thg 10, 20266 phút1