HTTP/2 — multiplexing giải quyết head-of-line blocking
HTTP/1.1 chỉ xử lý 1 request tại một thời điểm trên mỗi kết nối, dẫn đến head-of-line blocking ứng dụng. HTTP/2 dùng binary framing và stream để gửi nhiều request song song trên 1 kết nối TCP — cắt HOL ứng dụng hoàn toàn. Nhưng HOL ở tầng TCP vẫn còn, và đó là lý do HTTP/3 ra đời.
TL;DR: HTTP/1.1 chỉ gửi được 1 request tại một thời điểm trên mỗi kết nối TCP — request sau phải đợi response trước xong (head-of-line blocking ứng dụng). Workaround cũ là mở 6 kết nối song song, nhưng vừa tốn tài nguyên vừa không triệt để. HTTP/2 giải quyết bằng binary framing: chia nhỏ mọi message thành frame nhị phân, ghép nhiều stream (luồng logic) trên 1 kết nối TCP — request/response đan xen mà không cần chờ nhau. HPACK nén header loại bỏ dữ liệu lặp. Kết quả: page load nhanh hơn rõ rệt mà không cần 6 kết nối. Tuy nhiên, HOL blocking tầng TCP vẫn còn — mất 1 gói TCP chặn mọi stream — đó là bài toán HTTP/3 giải quyết.
Bạn đã biết ở bài 03 — Cookie, session & CORS rằng mỗi request HTTP mang theo header. Bài này phóng to cách transport — thứ quyết định bao nhiêu request có thể bay song song trước khi server trả lời.
1. Vấn đề: HTTP/1.1 và head-of-line blocking ứng dụng
Mở tab Chrome DevTools → Network của bất kỳ trang web nào và để ý cột Waterfall. Nếu trang dùng HTTP/1.1, bạn sẽ thấy các request xếp hàng: nhóm 6 cái chạy song song, nhóm kế đợi nhóm trước xong. Đây là dấu ấn trực tiếp của giới hạn HTTP/1.1.
Vấn đề gốc: HTTP/1.1 dùng text protocol — mỗi request là một dòng văn bản GET /path HTTP/1.1\r\n..., và sau khi gửi request, kết nối TCP bị chiếm cho tới khi nhận đủ response. Request tiếp theo không thể gửi trên cùng kết nối đó.
Ngay cả với keep-alive (HTTP/1.1 mặc định bật), kết nối được tái dùng nhưng vẫn tuần tự: gửi R1 → đợi đáp R1 → gửi R2 → đợi đáp R2. Nếu R1 chậm (query DB nặng), R2 kẹt — dù R2 là ảnh tĩnh xử lý tức thì. Đây là head-of-line blocking ở tầng ứng dụng (application-layer HOL blocking).
HTTP/1.1 có tính năng pipelining: gửi nhiều request liên tiếp mà không đợi từng response. Nhưng server phải trả response theo đúng thứ tự request — nên một response chậm vẫn chặn mọi thứ sau. Thêm vào đó nhiều server/proxy không implement đúng. Trình duyệt hiện đại đã tắt pipelining. Đây không phải giải pháp thật.
Workaround cũ — và vì sao chúng chỉ vá víu
Trước HTTP/2, dev và trình duyệt dùng 3 thủ thuật:
- 6 kết nối TCP song song: Chrome mở tối đa 6 kết nối tới cùng một origin. Workaround này tốn bộ nhớ (mỗi kết nối tốn ~40 KB buffer kernel), tốn 1 RTT handshake mỗi kết nối, và vẫn bị chặn nếu có hơn 6 request.
- Domain sharding: đặt static assets trên nhiều subdomain (
cdn1.example.com,cdn2.example.com) để khai thác thêm 6×N kết nối. Mỗi subdomain cần DNS lookup + TCP handshake riêng — tổng overhead tăng. - Bundling (gộp file): gộp 50 file CSS thành 1, gộp 30 icon thành 1 sprite — giảm số request. Nhưng làm hỏng caching (1 icon thay đổi → invalidate cả sprite), làm tăng kích thước file tải lần đầu.
Cả 3 workaround đều là hack quanh giới hạn giao thức — không giải quyết gốc. HTTP/2 giải quyết gốc.
2. HTTP/2 — binary framing và stream
HTTP/2 (RFC 9113, tiền thân là SPDY của Google) thay đổi cách truyền dữ liệu ở tầng transport, không thay đổi ngữ nghĩa HTTP: method, URL, header, status code — tất cả giữ nguyên từ góc nhìn app.
Sự thay đổi cốt lõi: không còn plain text — thay bằng binary framing layer.
Binary framing layer
Mọi thứ trong HTTP/2 được chia thành frame — đơn vị truyền tải nhỏ nhất, mỗi frame có header 9 byte cố định gồm:
- Length (3 byte): kích thước phần payload của frame.
- Type (1 byte): loại frame —
HEADERS(header HTTP),DATA(body),SETTINGS,WINDOW_UPDATE,PING,RST_STREAM,GOAWAY… - Flags (1 byte): cờ điều khiển, ví dụ
END_STREAMđánh dấu frame cuối của một message. - Stream Identifier (4 byte): frame này thuộc stream nào.
Stream — luồng logic độc lập
Stream trong HTTP/2 là một luồng request/response logic được đánh số nguyên. Client dùng stream ID lẻ (1, 3, 5…), server dùng chẵn (2, 4, 6…). Trên 1 kết nối TCP có thể tồn tại hàng trăm stream đồng thời.
Một request/response HTTP đầy đủ = chuỗi frame thuộc cùng 1 stream ID: frame HEADERS (gửi header request) → frame DATA (gửi body nếu có) → frame HEADERS (response header) → frame DATA (response body, kèm cờ END_STREAM).
Multiplexing — đan xen frame, không chờ nhau
Vì mỗi frame gắn stream ID, nhiều stream có thể đan xen frame trên cùng 1 kết nối TCP mà không cần tách ra:
sequenceDiagram
participant C as Client
participant S as Server
Note over C,S: 1 ket noi TCP duy nhat
C->>S: HEADERS stream=1 (GET /api/users)
C->>S: HEADERS stream=3 (GET /api/posts)
C->>S: HEADERS stream=5 (GET /logo.png)
S-->>C: HEADERS stream=1 (200 OK)
S-->>C: DATA stream=3 (posts data)
S-->>C: DATA stream=1 (users data, END_STREAM)
S-->>C: DATA stream=5 (image bytes)
S-->>C: DATA stream=3 (END_STREAM)
S-->>C: DATA stream=5 (END_STREAM)Frame của stream 3 và stream 5 có thể đi xen giữa frame của stream 1. Không stream nào cần đợi stream khác. Đây là multiplexing — gửi nhiều luồng dữ liệu đồng thời trên 1 kết nối đơn.
So sánh với HTTP/1.1 tuần tự:
sequenceDiagram
participant C as Client1 as Client (HTTP/1.1)
participant S as Server1 as Server
C->>S: GET /api/users
S-->>C: 200 OK + users data
C->>S: GET /api/posts
S-->>C: 200 OK + posts data
C->>S: GET /logo.png
S-->>C: 200 OK + image
Note over C,S: 3 request: phai doi tung cai - 3 RTT3. HPACK — nén header loại bỏ lặp
HTTP header chiếm một phần không nhỏ trong mỗi request. Trên một trang web, 90% header lặp lại giữa các request: User-Agent, Accept-Encoding, Cookie, Authorization — tất cả gửi nguyên văn mỗi lần trong HTTP/1.1.
HTTP/2 dùng HPACK (RFC 7541) — thuật toán nén header chuyên dụng cho HTTP:
- Static table: 61 cặp header/value hay gặp được đánh số sẵn. Ví dụ, entry 2 là
:method: GET— thay vì gửi 15 byte, client gửi 1 byte (số2). - Dynamic table: cả client lẫn server duy trì một bảng các header đã gửi trong session. Header chưa có trong static table → thêm vào dynamic table → lần sau chỉ gửi index (1-2 byte).
- Huffman coding: với header giá trị mới hoàn toàn, nén bằng Huffman code tĩnh.
Kết quả thực tế: header size giảm 85-88% trên request điển hình. Cookie dài 500 byte gửi lần đầu; lần sau chỉ tốn vài byte index.
HTTP/2 có server push: server chủ động gửi resource (CSS, JS) kèm response HTML mà không cần client request. Ý tưởng hay, nhưng thực tế browser thường đã có resource trong cache → push lãng phí. Chrome đã xoá hỗ trợ server push năm 2022. Đừng build logic dựa vào nó.
4. Pitfall quan trọng: HOL blocking TCP vẫn còn
Đây là điểm hay bị hiểu nhầm nhất về HTTP/2.
HTTP/2 giải quyết HOL blocking ở tầng ứng dụng — nhiều HTTP request không còn chặn nhau. Nhưng bên dưới, tất cả các stream vẫn đi trên 1 kết nối TCP. TCP đảm bảo thứ tự byte toàn cục — nếu 1 gói TCP bị mất, tất cả dữ liệu sau gói đó (thuộc mọi stream) phải đợi retransmit xong mới được xử lý tiếp.
Ví dụ: 3 stream S1, S2, S3 đang chạy song song. Gói TCP thứ 7 bị mất (thuộc S1). TCP phát hiện mất gói, yêu cầu retransmit. Trong thời gian đó, frame của S2 và S3 dù đã tới đầy đủ vẫn phải nằm trong buffer chờ — vì TCP phải deliver byte theo đúng thứ tự, không thể "nhảy qua" byte bị mất.
flowchart LR
A["3 streams<br/>S1 S2 S3<br/>HTTP/2"] --> B["1 TCP connection"]
B --> C["Goi TCP bi mat"]
C --> D["Moi stream bi chan<br/>cho retransmit"]
style C fill:#fee2e2,stroke:#ef4444
style D fill:#fee2e2,stroke:#ef4444Trên mạng mất gói cao (wifi yếu, 3G kém), HTTP/2 thậm chí có thể chậm hơn HTTP/1.1 với 6 kết nối song song — vì 1 kết nối HTTP/2 bị chặn toàn bộ, trong khi HTTP/1.1 chỉ bị chặn 1 trong 6 kết nối. Đây là TCP-layer HOL blocking, và đây chính xác là lý do HTTP/3 được thiết kế trên QUIC/UDP — xem bài 05 — HTTP/3 & QUIC.
HTTP/2 mang lại cải thiện rõ rệt trên mạng tốt, ít mất gói (băng thông rộng, latency thấp). Trên mạng kém, mất gói nhiều, HTTP/2 có thể kém hơn HTTP/1.1. Hiểu giới hạn này trước khi kết luận "HTTP/2 luôn nhanh hơn".
5. Tóm tắt so sánh HTTP/1.1 vs HTTP/2
| Đặc điểm | HTTP/1.1 | HTTP/2 |
|---|---|---|
| Protocol | Text | Binary (frame) |
| Request/kết nối | 1 tại một thời điểm | Nhiều stream song song |
| HOL blocking ứng dụng | Có | Không |
| HOL blocking TCP | Không áp dụng | Vẫn có |
| Header | Plain text, lặp lại | HPACK nén, ~85% nhỏ hơn |
| Số kết nối cần thiết | 6 (trình duyệt mở thêm) | 1 |
| Server push | Không | Có (đã deprecated thực tế) |
| TLS | Tuỳ chọn | Bắt buộc trên thực tế (trình duyệt) |
6. Liên hệ các bài khác
- Bài 02 — module TCP & UDP — TCP reliability: TCP đảm bảo thứ tự byte toàn cục là cơ chế đằng sau HOL blocking TCP vẫn còn trong HTTP/2 — bài này giải thích cơ chế retransmit.
- Bài 05 — HTTP/3 & QUIC: giải pháp cho TCP HOL blocking còn lại — QUIC chạy trên UDP với stream độc lập; bài này trực tiếp nối từ pitfall ở trên.
- Bài 01 — HTTP anatomy, method & status: ngữ nghĩa HTTP (method, status code) không đổi giữa HTTP/1.1 và HTTP/2 — chỉ tầng transport thay đổi.
- Bài 06 — REST, gRPC, WebSocket, SSE: gRPC dùng HTTP/2 làm transport — multiplexing chính là lý do gRPC efficient với nhiều concurrent request.
7. 📚 Deep Dive — tài liệu gốc
Đọc khi muốn đi tới tận gốc:
- RFC 9113 — HTTP/2 — bản cập nhật 2022 của HTTP/2 (trước là RFC 7540). Mục 4 (HTTP Frames), 5 (Streams and Multiplexing) là core.
- RFC 7541 — HPACK Header Compression — toàn bộ cơ chế nén header: static table (Appendix A), dynamic table, Huffman coding.
- Google SPDY White Paper — nguồn gốc HTTP/2, benchmark so sánh với HTTP/1.1 trên các điều kiện mạng khác nhau.
Ghi chú thực tế: Dùng curl --http2 -v https://example.com 2>&1 | grep "< HTTP" để kiểm tra server có hỗ trợ HTTP/2 không. Wireshark có thể decrypt TLS và hiển thị HTTP/2 frame nếu bạn cung cấp TLS session key.
8. Tóm tắt
- HTTP/1.1 bị head-of-line blocking ứng dụng: chỉ 1 request chạy tại một thời điểm trên 1 kết nối TCP — request sau đợi response trước.
- Workaround cũ (6 kết nối, domain sharding, bundling) là hack, không giải quyết gốc.
- HTTP/2 dùng binary framing: chia message thành frame nhỏ, mỗi frame gắn stream ID, nhiều stream đan xen trên 1 kết nối TCP — đây là multiplexing.
- HPACK nén header qua static table + dynamic table + Huffman: giảm ~85% kích thước header.
- Server push đã deprecated trên thực tế — Chrome xoá năm 2022.
- HOL blocking TCP vẫn còn: mất 1 gói TCP chặn mọi stream vì TCP đảm bảo thứ tự byte toàn cục — cơ sở cho HTTP/3.
- HTTP/2 nhanh hơn rõ trên mạng tốt; có thể kém hơn HTTP/1.1 trên mạng mất gói nhiều.
9. Tự kiểm tra
Q1HTTP/1.1 bị head-of-line blocking ứng dụng có nghĩa là gì? Tại sao keep-alive không giải quyết được?▸
Q2HTTP/2 dùng stream và multiplexing để tránh HOL blocking ứng dụng như thế nào?▸
Q3HPACK nén header HTTP/2 bằng cơ chế gì? Vì sao cần nén riêng thay vì dùng gzip thông thường?▸
:method: GET = entry 2, chỉ cần gửi 1 byte), dynamic table (client và server cùng duy trì bảng header đã gửi trong session — lần sau chỉ cần index), và Huffman coding cho giá trị mới. Gzip không phù hợp vì gzip cần context lớn và xử lý độc lập từng request — không tận dụng được thực tế rằng các request trong 1 session chia sẻ nhiều header giống nhau (như Authorization, Cookie). HPACK thiết kế chuyên cho HTTP, khai thác đặc điểm lặp header giữa request.Q4HTTP/2 giải quyết HOL blocking ứng dụng, nhưng HOL blocking TCP vẫn còn — nghĩa là gì?▸
Q5Trong điều kiện nào HTTP/2 có thể chậm hơn HTTP/1.1?▸
Q6Vì sao trình duyệt hiện đại thực tế đòi hỏi TLS khi dùng HTTP/2, dù spec không bắt buộc?▸
Bài tiếp theo: HTTP/3 & QUIC — vì sao chuyển khỏi TCP
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
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