TCP, HTTP & Web cho Backend/HTTP/3 & QUIC — vì sao chuyển khỏi TCP
16/29
Bài 16 / 29~18 phútHTTP & WebMiễn phí lượt xem

HTTP/3 & QUIC — vì sao chuyển khỏi TCP

HTTP/2 vẫn bị TCP head-of-line blocking: mất 1 gói chặn mọi stream. QUIC chạy trên UDP, tự cài reliability với stream độc lập, gộp TLS 1.3 vào handshake 1-RTT/0-RTT, và cho phép connection migration khi đổi mạng. HTTP/3 là HTTP trên QUIC.

TL;DR: HTTP/2 giải quyết HOL blocking tầng ứng dụng nhưng vẫn bị TCP HOL blocking: mất 1 gói TCP chặn mọi stream vì TCP đảm bảo thứ tự byte toàn cục. QUIC (RFC 9000) được thiết kế từ đầu để tránh điều này — chạy trên UDP, tự cài reliability và congestion control ở user-space, với mỗi stream độc lập (mất gói chỉ chặn stream của nó). Thêm vào đó, QUIC gộp TLS 1.3 vào handshake — chỉ tốn 1 RTT thay vì TCP (1 RTT) + TLS (1-2 RTT). Khi resume session, 0-RTT cho phép gửi dữ liệu ngay trong gói đầu tiên. Connection migration dùng Connection ID thay 4-tuple IP/port — đổi mạng từ wifi sang 4G mà kết nối không đứt. HTTP/3 = HTTP trên QUIC thay vì HTTP trên TCP.

Bài này nối trực tiếp từ bài 04 — HTTP/2 multiplexing: bạn đã biết HTTP/2 giải quyết HOL ứng dụng nhưng HOL TCP vẫn còn. Bây giờ ta mổ xẻ vì sao vấn đề đó cơ bản đến mức đòi hỏi một giao thức transport mới, và QUIC giải quyết nó thế nào.

1. Vấn đề gốc: TCP đảm bảo thứ tự byte toàn cục

TCP hoạt động như một stream byte có thứ tự: tầng Transport đảm bảo ứng dụng đọc ra đúng byte 0, 1, 2, 3... theo thứ tự gửi. Nếu gói mang byte 100-199 bị mất, TCP không thể giao byte 200-299 dù đã nhận — vì nó phá vỡ thứ tự byte liên tục.

Trong ngữ cảnh HTTP/2 với multiplexing, điều này dẫn đến vấn đề nghiêm trọng:

sequenceDiagram
    participant C as Client
    participant N as Network
    participant S as Server

    Note over C,S: Stream 1: anh dai, Stream 2: JSON nho, Stream 3: CSS
    C->>N: TCP segment (stream 1 data, byte 0-1399)
    C->>N: TCP segment (stream 2 data, byte 1400-1599)
    C->>N: TCP segment (stream 3 data, byte 1600-1999)
    Note over N: Segment 2 (byte 1400-1599) bi mat!
    N-->>C: NACK / timeout
    Note over C,S: Stream 2 va 3 da den day du<br/>nhung TCP phai doi segment 2 retransmit<br/>truoc khi deliver bat ky byte nao cho app
    C->>N: Retransmit segment 2
    N->>S: Segment 2 (retransmit)
    Note over C,S: Chi sau do stream 2 va 3 moi duoc xu ly

Stream 2 (JSON nhỏ, đã nhận đủ) và stream 3 (CSS, đã nhận đủ) đều bị chặn chỉ vì mất dữ liệu của stream 1 mà chúng không liên quan. Đây là TCP HOL blocking không thể tránh trong khuôn khổ TCP — vì TCP là abstraction byte stream không biết gì về HTTP/2 stream.

Giải pháp duy nhất: thay transport. Và transport gần nhất có thể dùng lại là UDP.

2. Tại sao UDP — và QUIC cài gì lên trên

UDP là giao thức "gửi-và-quên": không handshake, không sequence number, không đảm bảo thứ tự, không retransmit. Gói đến hay không, đến theo thứ tự nào — UDP không quan tâm. Nghe có vẻ ngược chiều khi muốn reliability.

Nhưng đây chính xác là điểm mạnh của QUIC: bằng cách bắt đầu từ UDP, QUIC có thể tự định nghĩa mọi cơ chế reliability ở user-space (thư viện, không phải kernel), với stream semantics hoàn toàn khác TCP.

QUIC (RFC 9000, 2021) cài lên UDP những thứ sau:

  • Reliability per-stream: mỗi stream có sequence number riêng. Mất gói chỉ trigger retransmit cho stream đó — stream khác tiến bình thường.
  • Congestion control: tương tự TCP (CUBIC, BBR) nhưng chạy ở user-space, dễ update không cần upgrade kernel.
  • Flow control: cả connection-level lẫn stream-level, giống HTTP/2 nhưng tích hợp sâu hơn.
  • TLS 1.3 gộp vào handshake: không còn hai handshake riêng (TCP rồi TLS) — QUIC kết hợp chúng thành 1.
💡 User-space stack — lợi thế lớn

TCP được implement trong kernel OS — để thay đổi hành vi TCP (thuật toán congestion control, feature mới) cần upgrade kernel, thường mất nhiều năm để deploy rộng. QUIC chạy ở user-space: Google và Cloudflare có thể cập nhật QUIC implementation của họ trong vài giờ mà không cần đợi OS update. Đây là lý do QUIC phát triển nhanh hơn TCP nhiều.

3. Stream độc lập — giải quyết TCP HOL blocking

Khác với HTTP/2 stream (là khái niệm logic trên 1 TCP byte stream), QUIC stream là đơn vị gốc của giao thức. Mỗi QUIC stream có packet number space riêng.

flowchart TD
    subgraph QUIC["QUIC tren UDP"]
        S1["Stream 1<br/>seq: 0,1,2,3..."]
        S2["Stream 2<br/>seq: 0,1,2..."]
        S3["Stream 3<br/>seq: 0,1..."]
    end
    UDP["1 UDP connection"]
    S1 --> UDP
    S2 --> UDP
    S3 --> UDP
    Lost["Goi stream 1 bi mat"]
    S1 -.->|retransmit chi stream 1| Lost
    S2 -->|"tien binh thuong"| OK2["Stream 2 OK"]
    S3 -->|"tien binh thuong"| OK3["Stream 3 OK"]
    style Lost fill:#fee2e2,stroke:#ef4444
    style OK2 fill:#d1fae5,stroke:#10b981
    style OK3 fill:#d1fae5,stroke:#10b981

Mất gói của stream 1 chỉ block stream 1. Stream 2 và 3 được deliver cho ứng dụng ngay lập tức mà không cần đợi retransmit. Đây là điều TCP về mặt cơ bản không thể làm vì nó không biết boundary giữa các stream.

4. Handshake gộp TLS 1.3 — 1-RTT và 0-RTT

Một điểm đau khác của HTTP/2 là latency khởi tạo: TCP handshake (1 RTT) + TLS 1.3 handshake (1 RTT) = 2 RTT trước byte HTTP đầu tiên. Với latency 100ms, tức là 200ms chỉ để bắt tay.

QUIC giải quyết bằng cách tích hợp TLS 1.3 vào trong QUIC handshake — không còn hai handshake tuần tự:

sequenceDiagram
    participant C as Client
    participant S as Server

    Note over C,S: TCP + TLS (HTTP/2): 2 RTT
    C->>S: TCP SYN
    S-->>C: TCP SYN-ACK
    C->>S: TCP ACK + TLS ClientHello
    S-->>C: TLS ServerHello + Certificate + Finished
    C->>S: TLS Finished
    Note over C,S: --- Tong: 2 RTT ---

    Note over C,S: QUIC (HTTP/3): 1 RTT
    C->>S: QUIC Initial (crypto handshake)
    S-->>C: QUIC Initial + Handshake (keys)
    C->>S: QUIC Handshake Finished + HTTP request
    Note over C,S: --- Tong: 1 RTT, request di kem ---

QUIC kết hợp transport handshake và TLS 1.3 handshake vào 1 round trip — response HTTP đầu tiên có thể đến sau đúng 1 RTT.

0-RTT resumption

Khi client đã kết nối với server trước đó, QUIC lưu session ticket (một blob mã hoá chứa thông tin về session cũ). Lần kết nối tiếp theo, client gửi luôn dữ liệu HTTP trong gói QUIC đầu tiên cùng với session ticket — không cần đợi bất kỳ round trip nào. Server giải mã ticket, verify, và xử lý request ngay.

sequenceDiagram
    participant C as Client
    participant S as Server
    Note over C: Co session ticket tu lan truoc
    C->>S: QUIC Initial + 0-RTT data (HTTP GET /home)
    S-->>C: HTTP Response + QUIC Handshake Confirm
    Note over C,S: 0 RTT cho request dau tien khi resume!
⚠️ 0-RTT và replay attack

0-RTT có một trade-off bảo mật: dữ liệu gửi trong 0-RTT có thể bị replay — kẻ tấn công capture gói 0-RTT và gửi lại. Vì server xử lý trước khi xác nhận danh tính đầy đủ, request idempotent (GET, HEAD) thường an toàn với 0-RTT, nhưng request có side-effect (POST, PUT) không nên dùng. Server có thể từ chối 0-RTT cho endpoint nhạy cảm. TLS 1.3 spec (RFC 8446 Section 8) phân tích chi tiết.

5. Connection migration — đổi mạng không đứt kết nối

TCP định danh kết nối bằng 4-tuple: (IP nguồn, port nguồn, IP đích, port đích). Khi bạn đi từ wifi vào thang máy và điện thoại chuyển sang 4G, IP nguồn thay đổi → 4-tuple thay đổi → TCP coi như kết nối mới → mọi stream bị ngắt → app phải reconnect từ đầu.

QUIC định danh kết nối bằng Connection ID — một chuỗi byte ngẫu nhiên không gắn với địa chỉ mạng. Khi IP thay đổi, client gửi gói với Connection ID cũ — server nhận ra đây là cùng 1 kết nối, xác thực lại qua cơ chế path validation, và tiếp tục mà không mất state.

TCP — đứt kết nối khi đổi mạng
wifi: 192.168.1.5:51234
↓ đổi mạng
4G: 10.0.0.1:52000
4-tuple đổi → TCP RST → reconnect
QUIC — tiếp tục khi đổi mạng
wifi: CID=a1b2c3d4
↓ đổi mạng
4G: CID=a1b2c3d4
Connection ID giữ nguyên → không đứt

Ứng dụng thực tế: video call đang chạy khi bạn đi từ văn phòng ra đường, YouTube livestream không buffer khi xe hơi đổi trạm phủ sóng — những cải thiện này đến từ connection migration.

6. HTTP/3 = HTTP trên QUIC

HTTP/3 (RFC 9114) về mặt ngữ nghĩa giống HTTP/2: method, status code, header, body — tất cả giữ nguyên. Sự khác biệt duy nhất là tầng dưới:

HTTP/2HTTP/3
TransportTCPQUIC (over UDP)
TLSRiêng (sau TCP handshake)Tích hợp trong QUIC
MultiplexingHTTP/2 streamQUIC stream (độc lập thật sự)
HOL blockingTCP-level cònKhông (stream độc lập)
Connection IDKhông (4-tuple)Có (migration được)
Handshake2 RTT (TCP + TLS)1 RTT (hoặc 0-RTT)
📌 ALPN — trình duyệt phát hiện HTTP/3 thế nào

Trình duyệt không tự biết server hỗ trợ HTTP/3. Server dùng response header Alt-Svc: h3=":443"; ma=86400 để thông báo "tôi hỗ trợ HTTP/3 ở port 443". Lần request đầu chạy HTTP/1.1 hoặc HTTP/2 qua TCP; sau khi thấy Alt-Svc, trình duyệt thử QUIC/UDP và nếu thành công, dùng HTTP/3 cho các request tiếp theo trong session. Chrome có thể bypass bước này nếu đã cache Alt-Svc từ lần trước.

7. Đánh đổi và giới hạn

HTTP/3 và QUIC không phải không có chi phí:

UDP bị firewall chặn: nhiều mạng doanh nghiệp, trường học, và một số ISP chặn UDP (hoặc rate-limit nặng) ngoại trừ DNS (port 53). HTTP/3 chạy trên UDP/443 có thể bị block. QUIC implementation phải có fallback về TCP/HTTP/2 — các trình duyệt tự động thử QUIC và fallback nếu thất bại trong vài giây.

CPU cao hơn: TLS handshake trong TCP được offload một phần lên hardware (crypto acceleration ở kernel level). QUIC user-space chưa tận dụng đầy đủ hardware offload — CPU overhead trên server cao hơn, đặc biệt ở tải nặng. Đây là điểm các CDN lớn (Cloudflare, Google) đang tối ưu dần.

Benefit phụ thuộc vào mạng: trên mạng lý tưởng (không mất gói, latency thấp), QUIC không nhanh hơn HTTP/2 rõ rệt. Benefit lớn nhất thấy rõ trên mạng kém (nhiều mất gói, jitter cao — mobile network, wifi đông người).

8. Pitfall thường gặp

Nhầm 1: "HTTP/3 luôn nhanh hơn HTTP/2." ✅ Không đúng trong mọi trường hợp. Trên mạng tốt, hai giao thức cho hiệu năng tương đương. HTTP/3 có lợi thế rõ rệt nhất trên mạng kém, nhiều mất gói (mobile, long-distance). Đo lường thực tế trước khi kết luận.

Nhầm 2: "QUIC bỏ reliability của TCP." ✅ QUIC có reliability đầy đủ — retransmit, acknowledgment, congestion control, flow control. Chỉ khác là chạy ở user-space và stream-level thay vì byte-stream global. Ứng dụng trên QUIC nhận dữ liệu đầy đủ, đúng thứ tự trong từng stream.

Nhầm 3: "0-RTT hoàn toàn an toàn." ✅ 0-RTT dễ bị replay attack — dữ liệu gửi trong 0-RTT có thể bị kẻ tấn công gửi lại nhiều lần. Chỉ dùng 0-RTT với idempotent request; server cần có cơ chế phòng replay cho endpoint nhạy cảm.

Nhầm 4: "HTTP/3 thay thế hoàn toàn HTTP/2 rồi." ✅ HTTP/2 vẫn là giao thức chính trên internet; HTTP/3 được triển khai ở các CDN lớn (Cloudflare ~30% traffic, Google ~25%) nhưng không phải mọi server đều hỗ trợ, và fallback về HTTP/2/TCP vẫn xảy ra thường xuyên.

9. Liên hệ các bài khác

  • Bài 04 — HTTP/2 multiplexing: nền tảng trực tiếp của bài này — HOL blocking TCP là vấn đề HTTP/3 giải quyết; stream và multiplexing trong HTTP/2 là tiền đề để hiểu stream độc lập trong QUIC.
  • Bài module 1 — TCP reliability: giải thích cơ chế TCP sequence/ACK/retransmit — chính những cơ chế này, khi áp dụng global cho toàn connection, tạo ra HOL blocking TCP.
  • Bài module 1 — UDP và QUIC: nếu bạn muốn hiểu sâu hơn tại sao UDP là nền tảng tốt cho QUIC — không overhead, không state, để QUIC tự định nghĩa mọi thứ.
  • Module 3 bài TLS 1.3: 0-RTT và session resumption trong QUIC dựa trên cơ chế TLS 1.3 Pre-Shared Key — đọc thêm để hiểu security model đầy đủ.

10. 📚 Deep Dive — tài liệu gốc

📚 RFC & spec chính thức

Đọc khi muốn đi tới tận gốc:

Thực hành: curl --http3 -v https://cloudflare.com (nếu curl build với QUIC support) để thấy HTTP/3 handshake. Chrome DevTools → Network → Protocol column hiển thị h3 khi dùng HTTP/3.

11. Tóm tắt

  • TCP HOL blocking: mất 1 gói TCP block mọi HTTP/2 stream vì TCP đảm bảo thứ tự byte toàn cục — không thể fix trong khuôn khổ TCP.
  • QUIC (RFC 9000) chạy trên UDP, tự cài reliability ở user-space với stream độc lập: mất gói chỉ block stream đó, không ảnh hưởng stream khác.
  • Handshake gộp: QUIC tích hợp TLS 1.3 → 1 RTT thay vì 2 RTT (TCP + TLS). 0-RTT khi có session ticket — dữ liệu gửi ngay gói đầu, nhưng dễ bị replay.
  • Connection migration: QUIC dùng Connection ID (không phải 4-tuple IP/port) → đổi mạng wifi sang 4G mà kết nối không đứt.
  • HTTP/3 = HTTP trên QUIC: ngữ nghĩa HTTP giữ nguyên, chỉ thay transport.
  • Đánh đổi: UDP đôi khi bị firewall chặn (cần fallback TCP), CPU overhead cao hơn ở server, benefit rõ nhất trên mạng kém/nhiều mất gói.

12. Tự kiểm tra

Tự kiểm tra
Q1
Tại sao TCP HOL blocking không thể giải quyết trong khuôn khổ HTTP/2 mà phải thay transport?
TCP là abstraction byte stream liên tục có thứ tự — tầng TCP không biết gì về HTTP/2 stream. Khi 1 gói TCP bị mất, tất cả byte sau (dù thuộc stream HTTP/2 nào) phải nằm trong buffer kernel cho tới khi gói mất được retransmit và nhận thành công. HTTP/2 không thể nói với TCP "hãy deliver byte của stream 2 và 3 trước đi, tôi đợi stream 1 sau" — đó không phải API mà TCP expose. Vì vậy, giải quyết TCP HOL blocking đòi hỏi thay transport layer hoàn toàn — đây là lý do QUIC được xây dựng.
Q2
QUIC dùng stream độc lập để tránh HOL blocking như thế nào? So sánh với HTTP/2 stream.
Trong HTTP/2, "stream" là khái niệm logic trên 1 TCP byte stream — mọi stream vẫn đi qua cùng 1 chuỗi byte TCP, nên mất gói TCP block tất cả. Trong QUIC, mỗi stream có packet number space riêng: mất gói ở stream 1 chỉ trigger retransmit cho stream 1, trong khi QUIC có thể deliver dữ liệu của stream 2 và 3 cho ứng dụng ngay lập tức. Stream là đơn vị gốc của QUIC, không phải lớp abstraction thêm vào trên một transport khác.
Q3
QUIC handshake gộp TLS 1.3 tiết kiệm latency thế nào so với TCP + TLS riêng?
TCP + TLS 1.3 cần 2 RTT: 1 RTT cho TCP handshake (SYN/SYN-ACK/ACK), sau đó 1 RTT cho TLS 1.3 handshake. Trong khi đó, QUIC tích hợp TLS 1.3 vào trong handshake của nó — transport handshake và crypto handshake xảy ra đồng thời trong 1 RTT. Với 0-RTT (khi có session ticket từ lần kết nối trước), client gửi dữ liệu HTTP ngay trong gói QUIC đầu tiên — 0 RTT chờ trước request đầu tiên.
Q4
Connection migration trong QUIC hoạt động thế nào? Vì sao TCP không làm được?
TCP định danh kết nối bằng 4-tuple (IP nguồn, port nguồn, IP đích, port đích). Khi điện thoại đổi từ wifi sang 4G, IP nguồn thay đổi → 4-tuple không còn match → TCP coi đây là kết nối mới, kết nối cũ bị RST. QUIC định danh kết nối bằng Connection ID — chuỗi ngẫu nhiên không gắn với địa chỉ mạng. Khi IP đổi, client gửi gói với Connection ID cũ, server nhận ra đây là cùng kết nối, validate path mới, và tiếp tục — không mất state, không cần reconnect.
Q5
0-RTT trong QUIC có rủi ro bảo mật gì? Loại request nào không nên dùng 0-RTT?
0-RTT dễ bị replay attack: kẻ tấn công capture gói QUIC chứa 0-RTT data và gửi lại nhiều lần — server xử lý lại request gốc. Với request idempotent (GET, HEAD — kết quả giống nhau dù gửi nhiều lần), replay thường vô hại. Với request có side-effect (POST tạo order, PUT update balance, DELETE xóa dữ liệu), replay có thể gây thiệt hại nghiêm trọng (tạo nhiều order, trừ tiền nhiều lần). Guideline: 0-RTT chỉ cho idempotent request; server cần có anti-replay mechanism (nonce, session token) cho endpoint nhạy cảm.
Q6
HTTP/3 thay đổi gì trong ngữ nghĩa HTTP so với HTTP/2? Ứng dụng cần thay đổi gì để hỗ trợ HTTP/3?
HTTP/3 không thay đổi ngữ nghĩa HTTP: method (GET, POST, PUT...), status code (200, 404, 500...), header (Authorization, Content-Type...), body — tất cả giữ nguyên từ góc nhìn ứng dụng. Sự thay đổi chỉ ở tầng transport (QUIC thay TCP). Ứng dụng thường không cần thay đổi code — web framework và HTTP library lo phần này. Công việc chính là cấu hình web server/CDN hỗ trợ QUIC (nginx-quic, Caddy, Cloudflare) và thêm header Alt-Svc: h3=":443" để trình duyệt biết thử HTTP/3.
Q7
Trong trường hợp nào HTTP/3 có thể không cải thiện, thậm chí giảm hiệu năng?
HTTP/3 kém lợi trên mạng tốt, ít mất gói (fiber, LAN): benefit stream độc lập không phát huy vì gói hiếm khi mất; nhưng overhead QUIC (crypto user-space, UDP header) vẫn tồn tại. Thêm vào đó, trên server tải cao, QUIC CPU overhead cao hơn (chưa tận dụng đủ hardware crypto offload). Cuối cùng, nếu UDP bị firewall hoặc ISP chặn/throttle, QUIC fallback về TCP/HTTP/2 — chi phí detect + fallback tốn thêm thời gian. Đo trên điều kiện mạng thực tế của người dùng mục tiêu trước khi kết luận HTTP/3 có lợi.

Bài tiếp theo: REST vs gRPC vs WebSocket vs SSE — chọn cái nào

Bài này đáng gửi cho bạn học cùng?

Copy link đã gắn nguồn — dán group, chat, hoặc LinkedIn.

Bài này có giúp bạn hiểu bản chất không?

Hỏi đáp về bài này

Chưa có câu hỏi

Đặt câu hỏi

Có gì chưa rõ trong bài? Đặt câu hỏi đầu tiên — câu trả lời từ cộng đồng giúp bạn (và người sau).

Đặt câu hỏi đầu tiên

Bài tiếp theo

REST vs gRPC vs WebSocket vs SSE — chọn cái nào