Bài trước kết ở một giới hạn: keep-alive của HTTP/1.1 tái dùng kết nối được, nhưng vẫn tuần tự — request sau chờ response trước xong. HTTP/2 sinh ra để phá đúng nút thắt đó. Nhiều người nghĩ HTTP/2 chỉ là "phiên bản mới nhanh hơn"; thực ra nó thay đổi cách đóng gói dữ liệu trên dây một cách căn bản: từ text tuần tự sang frame nhị phân đan xen. Bài này dựng nginx nói HTTP/2 và đo thật bằng curl 7.88.

Giới hạn của HTTP/1.1: một kết nối là một hàng đợi

Trên một kết nối HTTP/1.1 (kể cả keep-alive), các request phải đi tuần tự: req1 → resp1 → req2 → resp2 → .... Nếu resp1 chậm (server xử lý lâu, file lớn), mọi response sau nó phải chờ — gọi là head-of-line (HoL) blocking. Trình duyệt lách bằng cách mở tới 6 kết nối song song cho mỗi host, nhưng mỗi kết nối lại trả giá handshake riêng (bài trước), và 6 vẫn là trần cứng khi trang có hàng chục tài nguyên.

HTTP/1.1 trên 1 kết nối:  req1 -> resp1 -> req2 -> resp2 ...   (HoL blocking)

HTTP/2: frame nhị phân + multiplexing

HTTP/2 chia mỗi message thành các frame nhị phân, mỗi frame gắn một stream-id. Nhiều stream có thể đan xen trên một kết nối TCP: client gửi req của stream 1, 2, 3 liền nhau; server trả frame của stream nào xong trước tùy ý. Đó là multiplexing — song song thật sự trên một kết nối, không cần mở 6 kết nối như HTTP/1.1.

HTTP/2 trên 1 kết nối:  [s1]req [s2]req [s3]req ... [s2]resp [s1]resp [s3]resp

Kèm theo là HPACK (nén header: bỏ phần lặp lại như User-Agent, cookie giữa các request — vốn tốn byte vì HTTP/1.1 gửi text lặp), ưu tiên stream, và server push (nay ít dùng).

curl --http2                  # thử nâng cấp lên h2 nếu server hỗ trợ
curl --http2-prior-knowledge  # giả định server nói h2 ngay (h2c cleartext)
curl --http1.1                # ép HTTP/1.1

Ảnh chụp đoạn mã nền tối giải thích HTTP/1.1 vs HTTP/2 một kết nối nhiều luồng song song, giới hạn của HTTP/1.1 một kết nối là một hàng đợi trên một kết nối keep-alive request phải đi tuần tự req1 resp1 req2 resp2 head-of-line blocking resp1 chậm mọi cái sau chờ trình duyệt lách bằng cách mở tới 6 kết nối song song mỗi host tốn 6 lần handshake, HTTP/2 nhị phân cộng multiplexing trên một kết nối chia mỗi message thành frame nhị phân gắn stream-id nhiều stream đan xen trên một kết nối song song thật sự cộng HPACK nén header bỏ lặp User-Agent cookie cộng server push cộng ưu tiên stream, thương lượng giao thức HTTPS ALPN trong bắt tay TLS chọn h2 hay http 1.1 cleartext h2c client phải biết trước curl --http2 thử nâng cấp curl --http2-prior-knowledge giả định server nói h2 ngay curl --http1.1 ép HTTP 1.1

Hình 1: HTTP/1.1 trên một kết nối là hàng đợi tuần tự (HoL blocking); HTTP/2 chia thành frame nhị phân đan xen nhiều stream trên một kết nối, kèm nén header HPACK. Giao thức được thương lượng qua ALPN (HTTPS) hoặc prior-knowledge (h2c).

Đo thật: nginx nói HTTP/2

Dựng nginx với chỉ thị http2 phục vụ 10 file tĩnh, rồi dùng curl đo:

Ảnh chụp bảng kết quả đo thật nền tối chạy curl 7.88 và nginx, nginx http2 h2c cleartext 10 file tĩnh, phần một thương lượng cùng server chọn giao thức curl --http1.1 ra HTTP 1.1 curl --http2-prior-knowledge ra HTTP 2, phần hai server xác nhận nginx access log 22 dòng HTTP 1.1 và 3 dòng HTTP 2.0 nginx thật sự nói cả hai giao thức, phần ba lấy 10 file trên 1 kết nối trung bình 5 lần HTTP 1.1 tuần tự trên 1 kết nối 0.0086s HTTP 2 multiplex song song 0.0052s nhanh khoảng 1.65 lần ngay trên localhost gần như không độ trễ localhost không có RTT nên head-of-line blocking ít lộ trên mạng thật mỗi RTT hàng chục ms HTTP 2 bỏ xa HTTP 1.1 khi tải nhiều tài nguyên

Hình 2: curl thương lượng đúng giao thức (--http1.1 → HTTP/1.1, --http2-prior-knowledge → HTTP/2); nginx access log xác nhận cả hai được dùng thật; và lấy 10 file qua HTTP/2 (0.0052s) nhanh hơn HTTP/1.1 (0.0086s) ~1.65 lần ngay trên localhost.

Ba lớp bằng chứng:

  • Thương lượng thật: cùng server, curl --http1.1 được phục vụ bằng HTTP/1.1, curl --http2-prior-knowledge bằng HTTP/2. curl in ra %{http_version} là 1.1 và 2.
  • Server xác nhận: nginx access log ghi rõ HTTP/1.1 và HTTP/2.0 cho các request tương ứng — không phải curl "tự nhận", mà server thật sự nói giao thức đó.
  • Multiplexing nhanh hơn: lấy 10 file trên một kết nối, HTTP/2 (multiplex song song) trung bình 0.0052s so với HTTP/1.1 (tuần tự) 0.0086s — nhanh ~1.65 lần.

Nói thẳng con số: 1.65 lần là khiêm tốn, và đó là sự thật của môi trường đo — localhost gần như không có độ trễ mạng, nên HoL blocking ít bộc lộ (mỗi response về gần như tức thì, chờ tuần tự cũng không tốn bao nhiêu). Trên mạng thật, mỗi request HTTP/1.1 tuần tự phải chờ trọn một RTT (hàng chục tới hàng trăm ms) trước khi gửi cái tiếp; HTTP/2 gửi cả 10 cùng lúc nên chỉ tốn ~1 RTT cho toàn bộ. Khi đó khoảng cách không còn 1.65 lần mà có thể vài lần — càng nhiều tài nguyên và độ trễ càng cao, HTTP/2 càng thắng đậm.

Đánh đổi và lưu ý

HTTP/2 vẫn dính HoL blocking ở tầng TCP. Multiplexing xóa HoL blocking ở tầng HTTP, nhưng tất cả stream vẫn chạy trên một kết nối TCP — nếu một gói TCP mất, TCP giữ lại mọi stream cho tới khi truyền lại xong (TCP HoL blocking). Đây chính là lý do HTTP/3 chuyển sang QUIC (trên UDP), nơi mỗi stream độc lập ở tầng vận chuyển.

Gần như luôn đi kèm HTTPS. Trên thực tế trình duyệt chỉ dùng HTTP/2 qua TLS (thương lượng bằng ALPN trong bắt tay TLS). h2c (cleartext) như demo này hầu như chỉ dùng nội bộ/thử nghiệm. Nghĩa là bật HTTP/2 thường đi cùng việc cấu hình TLS đúng (bài sau).

Đừng còn "gộp file" như thời HTTP/1.1. Vì HTTP/1.1 tốn kém mỗi request, người ta gộp CSS/JS (bundling), nối sprite ảnh, chia domain (domain sharding) để lách trần 6 kết nối. Với HTTP/2, nhiều request nhỏ trên một kết nối là rẻ, nên các mẹo đó thành phản tác dụng (domain sharding còn làm tăng số kết nối và phá HPACK). Cấu hình lại tư duy khi lên HTTP/2.

Ba ý mang về

  1. HTTP/1.1 tuần tự trên mỗi kết nối (head-of-line blocking) nên trình duyệt phải mở tới 6 kết nối/host để lách; HTTP/2 chia frame nhị phân và multiplex nhiều stream trên một kết nối — đo thật, lấy 10 file nhanh hơn ~1.65 lần ngay trên localhost, và bỏ xa hơn nhiều khi có độ trễ mạng.
  2. Thương lượng giao thức là thật và kiểm được: curl --http1.1/--http2-prior-knowledge cho HTTP/1.1 và HTTP/2, nginx access log xác nhận cả hai — HTTPS dùng ALPN, cleartext (h2c) cần prior-knowledge.
  3. Biết giới hạn và đổi tư duy: HTTP/2 vẫn dính TCP HoL blocking (lý do có HTTP/3 trên QUIC), gần như luôn đi với HTTPS, và các mẹo tối ưu thời HTTP/1.1 (bundling, domain sharding) thành phản tác dụng.

Nguồn

Phần sau ta chuyển sang chủ đề tiết kiệm băng thông theo cách khác: cache HTTP — Cache-Control, ETag, và cú 304 Not Modified giúp client dùng lại bản đã có mà không tải lại.