Giới thiệu về HTTP/2

HTTP/2 sẽ giúp các ứng dụng của chúng ta nhanh hơn, đơn giản hơn và mạnh mẽ hơn – một sự kết hợp hiếm gặp – bằng cách cho phép chúng ta huỷ nhiều giải pháp HTTP/1.1 đã thực hiện trước đây trong các ứng dụng và giải quyết những mối lo ngại này ngay trong lớp truyền tải. Hơn nữa, việc này còn mở ra một số cơ hội hoàn toàn mới để tối ưu hoá ứng dụng và cải thiện hiệu suất!

Mục tiêu chính của HTTP/2 là giảm độ trễ bằng cách bật tính năng ghép kênh phản hồi và yêu cầu đầy đủ, giảm thiểu mức hao tổn giao thức thông qua tính năng nén hiệu quả các trường tiêu đề HTTP, đồng thời hỗ trợ thêm tính năng ưu tiên yêu cầu và đẩy máy chủ. Để triển khai các yêu cầu này, chúng tôi cung cấp một số lượng lớn các tính năng nâng cao khác về giao thức, chẳng hạn như cơ chế kiểm soát luồng mới, cơ chế xử lý lỗi và nâng cấp. Tuy nhiên, đây là những tính năng quan trọng nhất mà mọi nhà phát triển web đều nên hiểu rõ và tận dụng trong ứng dụng của mình.

HTTP/2 không sửa đổi ngữ nghĩa ứng dụng của HTTP theo bất kỳ cách nào. Mọi khái niệm chính, chẳng hạn như phương thức HTTP, mã trạng thái, URI và trường tiêu đề, đều giữ nguyên. Thay vào đó, HTTP/2 sẽ sửa đổi cách dữ liệu được định dạng (đóng khung) và truyền tải giữa ứng dụng và máy chủ, cả hai đều quản lý toàn bộ quy trình và ẩn tất cả sự phức tạp khỏi các ứng dụng của chúng tôi trong lớp khung mới. Do đó, bạn có thể phân phối tất cả ứng dụng hiện có mà không cần sửa đổi.

Tại sao không phải là HTTP/1.2?

Để đạt được mục tiêu hiệu suất do Nhóm làm việc HTTP đặt ra, HTTP/2 có một lớp khung nhị phân mới không tương thích ngược với các máy khách và máy chủ HTTP/1.x trước đó. Do đó, phiên bản giao thức chính sẽ tăng lên HTTP/2.

Điều đó có nghĩa là trừ khi bạn triển khai máy chủ web (hoặc ứng dụng tuỳ chỉnh) bằng cách làm việc với các cổng TCP thô, thì bạn sẽ không thấy có gì khác biệt: tất cả việc lấy khung hình mới ở cấp thấp sẽ do ứng dụng và máy chủ thay mặt bạn thực hiện. Điểm khác biệt duy nhất có thể quan sát được sẽ là hiệu suất được cải thiện và khả năng sử dụng các tính năng mới như ưu tiên yêu cầu, kiểm soát luồng và đẩy máy chủ.

Lịch sử ngắn gọn của SPDY và HTTP/2

SPDY là một giao thức thử nghiệm, được Google phát triển và được công bố vào giữa năm 2009, với mục tiêu chính là cố gắng giảm độ trễ tải của các trang web bằng cách giải quyết một số hạn chế phổ biến về hiệu suất của HTTP/1.1. Cụ thể, chúng tôi đặt ra các mục tiêu đã đề ra của dự án như sau:

  • Nhắm mục tiêu giảm 50% thời gian tải trang (PLT).
  • Tránh cần phải thay đổi nội dung của tác giả trang web.
  • Giảm thiểu tính phức tạp trong quá trình triển khai và tránh những thay đổi về cơ sở hạ tầng mạng.
  • Hãy hợp tác với cộng đồng nguồn mở để phát triển giao thức mới này.
  • Thu thập dữ liệu hiệu suất thực tế để xác thực (vô hiệu) giao thức thử nghiệm.

Không lâu sau thông báo ban đầu, Mike Belshe và Roberto Peon (đều là kỹ sư phần mềm của Google) đã chia sẻ kết quả đầu tiên, tài liệu và mã nguồn để triển khai thử nghiệm giao thức SPDY mới:

Cho đến nay, chúng tôi chỉ thử nghiệm SPDY trong điều kiện phòng thí nghiệm. Kết quả ban đầu rất đáng khích lệ: khi tải 25 trang web hàng đầu xuống qua kết nối mạng gia đình mô phỏng, chúng tôi nhận thấy hiệu suất cải thiện đáng kể — các trang được tải nhanh hơn tới 55%. (Blog Chromium)

Đến năm 2012, giao thức thử nghiệm mới được hỗ trợ trong Chrome, Firefox và Opera, đồng thời số lượng trang web đang tăng nhanh, cả lớn (ví dụ: Google, Twitter, Facebook) và nhỏ, đã triển khai SPDY trong cơ sở hạ tầng của họ. Trên thực tế, SPDY đã đi đúng hướng để trở thành một tiêu chuẩn trên thực tế nhờ sự chấp nhận ngày càng tăng của ngành.

Khi quan sát xu hướng này, Nhóm làm việc HTTP (HTTP-WG) đã bắt đầu một nỗ lực mới nhằm rút ra các bài học rút ra từ SPDY, xây dựng và cải tiến các bài học đó, đồng thời cung cấp tiêu chuẩn "HTTP/2" chính thức. Một điều lệ mới đã được soạn thảo, một lệnh gọi mở cho các đề xuất HTTP/2 đã được thực hiện và sau nhiều cuộc thảo luận trong nhóm làm việc, thông số kỹ thuật SPDY được sử dụng làm điểm khởi đầu cho giao thức HTTP/2 mới.

Trong vài năm tiếp theo, SPDY và HTTP/2 tiếp tục phát triển song song, trong đó SPDY đóng vai trò là một nhánh thử nghiệm dùng để kiểm thử các tính năng và đề xuất mới cho tiêu chuẩn HTTP/2. Những gì trông đẹp trên giấy có thể sẽ không phát huy hiệu quả trong thực tế và ngược lại, SPDY đã cung cấp một tuyến để kiểm thử và đánh giá từng đề xuất trước khi đưa vào tiêu chuẩn HTTP/2. Rốt cuộc, quá trình này kéo dài 3 năm và tạo ra hơn 12 bản nháp trung gian:

  • Tháng 3 năm 2012: Gọi đề xuất cho HTTP/2
  • Tháng 11 năm 2012: Bản nháp đầu tiên của HTTP/2 (dựa trên SPDY)
  • Tháng 8 năm 2014: HTTP/2 draft-17 và HPACK draft-12 được xuất bản
  • Tháng 8 năm 2014: Cuộc gọi gần đây nhất của Nhóm công tác cho HTTP/2
  • Tháng 2 năm 2015: IESG đã phê duyệt các bản nháp HTTP/2 và HPACK
  • Tháng 5 năm 2015: RFC 7540 (HTTP/2) và RFC 7541 (HPACK) được xuất bản

Đầu năm 2015, IESG đã xem xét và phê duyệt tiêu chuẩn HTTP/2 mới cho việc xuất bản. Ngay sau đó, nhóm Google Chrome đã thông báo lịch trình ngừng sử dụng tiện ích SPDY và NPN cho TLS:

Những thay đổi chính của HTTP/2 so với HTTP/1.1 tập trung vào việc cải thiện hiệu suất. Một số tính năng chính như ghép kênh, nén tiêu đề, ưu tiên và thương lượng giao thức được phát triển từ công việc được thực hiện trong một giao thức mở trước đó nhưng không chuẩn có tên là SPDY. Chrome đã hỗ trợ SPDY kể từ Chrome 6, nhưng vì hầu hết các lợi ích đều có trong HTTP/2, nên đã đến lúc kết thúc chương trình này. Chúng tôi dự định ngừng hỗ trợ SPDY vào đầu năm 2016, đồng thời ngừng hỗ trợ tiện ích TLS có tên NPN và thay vào đó là ALPN trong Chrome. Các nhà phát triển máy chủ nên chuyển sang HTTP/2 và ALPN.

Chúng tôi rất vui vì đã đóng góp vào quy trình tiêu chuẩn mở dẫn đến HTTP/2, và hy vọng sẽ được áp dụng rộng rãi do có sự tham gia rộng rãi của toàn ngành trong việc tiêu chuẩn hoá và triển khai. (Blog Chromium)

Sự phát triển của các nhà phát triển máy chủ, trình duyệt và trang web có hỗ trợ SPDY và HTTP/2 để có được trải nghiệm thực tế với giao thức mới trong quá trình phát triển. Do đó, tiêu chuẩn HTTP/2 là một trong những tiêu chuẩn tốt nhất và được kiểm thử kỹ lưỡng nhất ngay từ đầu. Vào thời điểm HTTP/2 được IESG phê duyệt, đã có hàng chục phương thức triển khai máy khách và máy chủ đã được kiểm thử kỹ lưỡng và sẵn sàng triển khai thực tế. Trên thực tế, chỉ vài tuần sau khi giao thức cuối cùng được phê duyệt, nhiều người dùng đã được tận hưởng các lợi ích của giao thức này khi một số trình duyệt phổ biến (và nhiều trang web) đã triển khai hỗ trợ HTTP/2 đầy đủ.

Mục tiêu thiết kế và kỹ thuật

Các phiên bản giao thức HTTP trước đây được thiết kế có chủ đích nhằm đơn giản hoá quá trình triển khai: HTTP/0.9 là giao thức một dòng để khởi động Web Wide; HTTP/1.0 ghi nhận các tiện ích phổ biến thành HTTP/0.9 theo tiêu chuẩn thông tin; HTTP/1.1 đã giới thiệu tiêu chuẩn IETF chính thức; xem Tóm tắt Lịch sử HTTP. Do đó, HTTP/0.9-1.x đã phân phối chính xác những gì nó đặt ra: HTTP là một trong những giao thức ứng dụng được chấp nhận rộng rãi nhất trên Internet.

Thật không may là việc đơn giản hoá việc triển khai cũng phải đánh đổi hiệu suất của ứng dụng: máy khách HTTP/1.x cần dùng nhiều kết nối để đạt được tính đồng thời và giảm độ trễ; HTTP/1.x không nén tiêu đề yêu cầu và phản hồi, gây ra lưu lượng truy cập mạng không cần thiết; HTTP/1.x không cho phép ưu tiên tài nguyên hiệu quả, dẫn đến việc sử dụng kém kết nối TCP cơ bản; v.v.

Những giới hạn này không nghiêm trọng, nhưng khi các ứng dụng web tiếp tục phát triển về phạm vi, độ phức tạp và tầm quan trọng trong cuộc sống hằng ngày của chúng ta, chúng đã tạo ra gánh nặng ngày càng lớn lên cả nhà phát triển và người dùng web, đó chính là lỗ hổng chính xác mà HTTP/2 được thiết kế để giải quyết:

HTTP/2 cho phép sử dụng tài nguyên mạng hiệu quả hơn và giảm nhận biết độ trễ bằng cách áp dụng tính năng nén trường tiêu đề và cho phép nhiều lần trao đổi đồng thời trên cùng một kết nối... Cụ thể, giao thức này cho phép xen kẽ các thông điệp yêu cầu và phản hồi trên cùng một kết nối, đồng thời sử dụng mã hoá hiệu quả cho các trường tiêu đề HTTP. API này cũng cho phép ưu tiên các yêu cầu, cho phép nhiều yêu cầu quan trọng hơn được hoàn tất nhanh hơn, cải thiện hiệu suất hơn nữa.

Giao thức thu được thân thiện hơn với mạng vì có thể sử dụng ít kết nối TCP hơn so với HTTP/1.x. Điều này đồng nghĩa với việc ít phải cạnh tranh hơn với các quy trình khác và kết nối lâu dài hơn, từ đó giúp khai thác hiệu quả hơn dung lượng mạng hiện có. Cuối cùng, HTTP/2 cũng cho phép xử lý thông báo hiệu quả hơn thông qua việc sử dụng khung thông báo nhị phân. (Giao thức truyền siêu văn bản phiên bản 2, Bản nháp 17)

Điều quan trọng cần lưu ý là HTTP/2 đang mở rộng chứ không thay thế các tiêu chuẩn HTTP trước đó. Ngữ nghĩa ứng dụng của HTTP là giống nhau và không có thay đổi nào đối với chức năng hoặc khái niệm chính được cung cấp, chẳng hạn như phương thức HTTP, mã trạng thái, URI và trường tiêu đề. Những thay đổi này rõ ràng nằm ngoài phạm vi của nỗ lực HTTP/2. Tuy nhiên, mặc dù API cấp cao không thay đổi, nhưng quan trọng là bạn phải hiểu cách các thay đổi cấp thấp giải quyết được các hạn chế về hiệu suất của các giao thức trước đó. Hãy tìm hiểu nhanh về lớp lấy khung hình nhị phân và các tính năng của lớp này.

Lớp lấy khung hình nhị phân

Cốt lõi của tất cả các tính năng nâng cao hiệu suất của HTTP/2 là lớp đóng khung nhị phân mới, cho biết cách đóng gói và truyền thông báo HTTP giữa ứng dụng và máy chủ.

Lớp lấy khung hình nhị phân HTTP/2

"Lớp" là một lựa chọn thiết kế để giới thiệu một cơ chế mã hoá mới được tối ưu hoá giữa giao diện ổ cắm và API HTTP cao hơn mà ứng dụng của chúng tôi nhìn thấy: ngữ nghĩa HTTP như động từ, phương thức và tiêu đề không bị ảnh hưởng, nhưng cách mã hoá trong quá trình truyền lại thì khác. Không giống như giao thức HTTP/1.x văn bản thuần tuý được phân tách trên dòng mới, tất cả hoạt động giao tiếp HTTP/2 được chia thành các thông điệp và khung nhỏ hơn, mỗi khung và thông báo được mã hoá ở định dạng nhị phân.

Do đó, cả máy khách và máy chủ đều phải sử dụng cơ chế mã hoá nhị phân mới để hiểu lẫn nhau: máy khách HTTP/1.x sẽ không hiểu máy chủ chỉ HTTP/2 và ngược lại. Rất may là các ứng dụng của chúng tôi vẫn tuyệt đối không nhận biết được tất cả những thay đổi này, vì máy khách và máy chủ thay mặt chúng tôi thực hiện tất cả công việc cần thiết trong khung hình này.

Luồng, thông báo và khung

Sự ra mắt của cơ chế lấy khung hình nhị phân mới làm thay đổi cách trao đổi dữ liệu giữa ứng dụng và máy chủ. Để mô tả quá trình này, hãy làm quen với thuật ngữ HTTP/2:

  • Luồng: Một luồng hai chiều gồm các byte trong một kết nối đã thiết lập, có thể mang một hoặc nhiều thông báo.
  • Thông báo: Một chuỗi hoàn chỉnh gồm các khung liên kết với một yêu cầu logic hoặc thông báo phản hồi.
  • Khung: Đơn vị giao tiếp nhỏ nhất trong HTTP/2, mỗi đơn vị chứa một tiêu đề khung, tối thiểu là tiêu đề nhận dạng luồng chứa khung hình.

Mối quan hệ của các thuật ngữ này có thể được tóm tắt như sau:

  • Mọi hoạt động giao tiếp đều được thực hiện qua một kết nối TCP duy nhất có thể mang số lượng luồng hai chiều bất kỳ.
  • Mỗi luồng có một giá trị nhận dạng duy nhất và thông tin mức độ ưu tiên (không bắt buộc) dùng để truyền thông báo hai chiều.
  • Mỗi thông báo là một thông báo HTTP logic, chẳng hạn như một yêu cầu hoặc phản hồi, bao gồm một hoặc nhiều khung.
  • Khung là đơn vị giao tiếp nhỏ nhất, chứa một loại dữ liệu cụ thể, chẳng hạn như Tiêu đề HTTP, tải trọng thư, v.v. Các khung hình từ nhiều luồng có thể được xen kẽ rồi tập hợp lại thông qua giá trị nhận dạng luồng được nhúng trong tiêu đề của mỗi khung.

Luồng, thông báo và khung HTTP/2

Tóm lại, HTTP/2 chia nhỏ hoạt động giao tiếp của giao thức HTTP thành một quá trình trao đổi các khung được mã hoá nhị phân. Sau đó, các khung này được ánh xạ đến các thông báo thuộc một luồng cụ thể, tất cả đều được ghép kênh trong một kết nối TCP. Đây là nền tảng hỗ trợ tất cả các tính năng khác và hoạt động tối ưu hoá hiệu suất do giao thức HTTP/2 cung cấp.

Ghép yêu cầu và phản hồi

Với HTTP/1.x, nếu ứng dụng muốn thực hiện nhiều yêu cầu song song để cải thiện hiệu suất, thì bạn phải sử dụng nhiều kết nối TCP (xem phần Sử dụng nhiều kết nối TCP). Hành vi này là hệ quả trực tiếp của mô hình phân phối HTTP/1.x, đảm bảo rằng chỉ có thể phân phối một phản hồi tại một thời điểm (xếp hàng phản hồi) cho mỗi kết nối. Tồi tệ hơn, điều này cũng dẫn đến việc chặn đầu dòng và sử dụng không hiệu quả kết nối TCP cơ bản.

Lớp lấy khung tệp nhị phân mới trong HTTP/2 loại bỏ các giới hạn này và cho phép ghép kênh phản hồi và yêu cầu hoàn toàn, bằng cách cho phép ứng dụng và máy chủ chia thông báo HTTP thành các khung độc lập, xen kẽ rồi tập hợp lại chúng ở đầu bên kia.

Yêu cầu HTTP/2 và ghép kênh phản hồi trong một kết nối dùng chung

Ảnh chụp nhanh chụp nhiều luồng trong cùng một kết nối. Máy khách đang truyền một khung DATA (luồng 5) đến máy chủ, trong khi máy chủ đang truyền một chuỗi khung xen kẽ đến máy khách đối với luồng 1 và 3. Do đó, có 3 luồng song song đang trong quá trình.

Khả năng chia nhỏ thông báo HTTP thành các khung độc lập, xen kẽ chúng rồi tập hợp lại chúng trên đầu bên kia là điểm cải tiến quan trọng nhất của HTTP/2. Trên thực tế, công cụ này mang đến hiệu ứng gợn sóng của nhiều lợi ích về hiệu suất trên toàn bộ ngăn xếp của mọi công nghệ web, cho phép chúng tôi:

  • Xen kẽ nhiều yêu cầu song song mà không chặn trên bất kỳ yêu cầu nào.
  • Xen kẽ nhiều câu trả lời song song mà không chặn trên bất kỳ câu trả lời nào.
  • Sử dụng một kết nối duy nhất để gửi song song nhiều yêu cầu và phản hồi.
  • Xoá các giải pháp HTTP/1.x không cần thiết (xem phần Tối ưu hoá cho HTTP/1.x, chẳng hạn như các tệp nối, nhóm hình ảnh nổi bật và tính năng phân đoạn miền).
  • Giảm thời gian tải trang nhờ loại bỏ độ trễ không cần thiết và cải thiện mức sử dụng dung lượng mạng hiện có.
  • Và nhiều thông tin khác...

Lớp lấy khung tệp nhị phân mới trong HTTP/2 giải quyết vấn đề chặn đầu dòng có trong HTTP/1.x và loại bỏ nhu cầu có nhiều kết nối để bật tính năng xử lý song song cũng như phân phối các yêu cầu và phản hồi. Nhờ đó, chúng tôi có thể triển khai các ứng dụng nhanh hơn, đơn giản hơn và rẻ hơn.

Ưu tiên phát trực tiếp

Sau khi một thông báo HTTP có thể được chia thành nhiều khung hình riêng lẻ và chúng tôi cho phép ghép các khung hình từ nhiều luồng, thứ tự các khung được xen kẽ và phân phối bởi cả máy khách và máy chủ sẽ trở thành một yếu tố quan trọng cần xem xét về hiệu suất. Để tạo điều kiện cho việc này, tiêu chuẩn HTTP/2 cho phép mỗi luồng có trọng số và phần phụ thuộc được liên kết:

  • Bạn có thể chỉ định trọng số cho mỗi luồng là số nguyên trong khoảng từ 1 đến 256.
  • Mỗi luồng có thể được cung cấp một phần phụ thuộc rõ ràng trên một luồng khác.

Sự kết hợp giữa các phần phụ thuộc và trọng số của luồng cho phép ứng dụng tạo và truyền đạt "cây ưu tiên" thể hiện cách máy khách muốn nhận phản hồi. Đổi lại, máy chủ có thể sử dụng thông tin này để ưu tiên xử lý luồng bằng cách kiểm soát mức phân bổ CPU, bộ nhớ và các tài nguyên khác. Sau khi có dữ liệu phản hồi, máy chủ sẽ phân bổ băng thông nhằm đảm bảo phân phối tối ưu các phản hồi có mức độ ưu tiên cao cho ứng dụng.

Các phần phụ thuộc và trọng số của luồng HTTP/2

Phần phụ thuộc luồng trong HTTP/2 được khai báo bằng cách tham chiếu giá trị nhận dạng duy nhất của một luồng khác làm luồng gốc; nếu giá trị nhận dạng bị bỏ qua, thì luồng được cho là phụ thuộc vào "luồng gốc". Việc khai báo phần phụ thuộc của luồng cho biết rằng nếu có thể, luồng gốc phải được phân bổ các tài nguyên trước các phần phụ thuộc của luồng đó. Nói cách khác là "Vui lòng xử lý và đưa ra câu trả lời D trước câu trả lời C".

Các luồng có cùng nguồn gốc (nói cách khác là các luồng đồng cấp) phải được phân bổ tài nguyên tương ứng với trọng số của chúng. Ví dụ: nếu luồng A có trọng số là 12 và luồng đồng cấp B có trọng số là 4, thì để xác định tỷ lệ tài nguyên mà mỗi luồng này sẽ nhận được:

  1. Cộng tất cả trọng số: 4 + 12 = 16
  2. Chia trọng lượng của mỗi luồng cho tổng trọng lượng: A = 12/16, B = 4/16

Do đó, luồng A sẽ nhận được 3/4 và luồng B sẽ nhận được 1/4 tài nguyên có sẵn; luồng B sẽ nhận được 1/3 số tài nguyên được phân bổ cho luồng A. Hãy cùng xem qua một vài ví dụ thực tế khác trong hình trên. Từ trái sang phải:

  1. Cả luồng A và B đều không chỉ định phần phụ thuộc mẹ và được cho là phụ thuộc vào "luồng gốc" ngầm ẩn; A có trọng số là 12 và B có trọng số là 4. Do đó, dựa trên trọng số tỷ lệ: luồng B sẽ nhận được 1/3 tài nguyên được phân bổ cho luồng A.
  2. Luồng D phụ thuộc vào luồng gốc, C phụ thuộc vào D. Do đó, D sẽ nhận được toàn bộ tài nguyên được phân bổ trước C. Các trọng số không quan trọng vì phần phụ thuộc của C truyền đạt một lựa chọn ưu tiên mạnh mẽ hơn.
  3. Luồng D sẽ nhận được toàn bộ tài nguyên được phân bổ trước C; C sẽ nhận được toàn bộ tài nguyên được phân bổ trước A và B; luồng B sẽ nhận được 1/3 số tài nguyên được phân bổ cho luồng A.
  4. Luồng D sẽ nhận được mức phân bổ đầy đủ tài nguyên trước E và C; E và C sẽ nhận được mức phân bổ bằng nhau trước A và B; A và B sẽ nhận được mức phân bổ theo tỷ lệ dựa trên trọng số của chúng.

Như các ví dụ trên minh hoạ, sự kết hợp giữa phần phụ thuộc và trọng số của luồng cung cấp ngôn ngữ biểu đạt để ưu tiên tài nguyên. Đây là một tính năng quan trọng để cải thiện hiệu suất duyệt web trong đó chúng ta có nhiều loại tài nguyên với các phần phụ thuộc và trọng số khác nhau. Hơn nữa, giao thức HTTP/2 còn cho phép ứng dụng cập nhật các lựa chọn ưu tiên này bất cứ lúc nào, giúp tối ưu hoá hơn nữa trình duyệt. Nói cách khác, chúng tôi có thể thay đổi các phần phụ thuộc và phân bổ lại trọng số để phản hồi tương tác của người dùng và các tín hiệu khác.

Một kết nối cho mỗi điểm gốc

Khi áp dụng cơ chế lấy khung tệp nhị phân mới, HTTP/2 không còn cần nhiều kết nối TCP với nhiều luồng song song; mỗi luồng được chia thành nhiều khung hình có thể xen kẽ và được ưu tiên. Do đó, tất cả các kết nối HTTP/2 đều ổn định và chỉ cần một kết nối cho mỗi nguồn gốc, mang lại nhiều lợi ích về hiệu suất.

Đối với cả SPDY và HTTP/2, tính năng tắt là ghép kênh tuỳ ý trên một kênh được kiểm soát chống tắc nghẽn tốt. Tôi thật sự ngạc nhiên về tầm quan trọng và cách hoạt động của tính năng này. Một chỉ số tuyệt vời mà tôi thích là nhóm kết nối được tạo chỉ mang một giao dịch HTTP duy nhất (và do đó, khiến giao dịch đó chịu mọi chi phí). Đối với HTTP/1, 74% kết nối đang hoạt động của chúng tôi chỉ thực hiện một giao dịch duy nhất — các kết nối lâu dài không hữu ích như tất cả chúng ta mong muốn. Nhưng trong HTTP/2, con số đó giảm mạnh xuống còn 25%. Đó là một thắng lợi lớn giúp giảm chi phí vận hành. (HTTP/2 đã hoạt động trên Firefox, Patrick McManus)

Hầu hết các hoạt động truyền HTTP đều diễn ra nhanh và nhiều lần, trong khi TCP được tối ưu hoá để truyền dữ liệu hàng loạt trong thời gian dài. Bằng cách sử dụng lại cùng một kết nối, HTTP/2 có thể sử dụng hiệu quả hơn từng kết nối TCP và cũng giảm đáng kể tổng chi phí tổng thể của giao thức. Hơn nữa, việc sử dụng ít kết nối hơn sẽ làm giảm mức sử dụng bộ nhớ và hoạt động xử lý trên đường dẫn kết nối đầy đủ (nói cách khác là ứng dụng, máy chủ trung gian và máy chủ gốc). Điều này giúp giảm tổng chi phí vận hành, đồng thời cải thiện dung lượng và mức sử dụng mạng. Do đó, việc chuyển sang HTTP/2 không chỉ làm giảm độ trễ mạng, mà còn giúp cải thiện công suất và giảm chi phí vận hành.

Kiểm soát luồng

Kiểm soát luồng là một cơ chế ngăn không cho trình gửi làm trình thu nhận quá tải bằng dữ liệu mà nó có thể không muốn hoặc không xử lý được: trình thu nhận có thể bận, tải quá nhiều hoặc chỉ sẵn sàng phân bổ một lượng tài nguyên cố định cho một luồng cụ thể. Ví dụ: ứng dụng có thể đã yêu cầu một luồng video lớn với mức độ ưu tiên cao, nhưng người dùng đã tạm dừng video và ứng dụng hiện muốn tạm dừng hoặc điều tiết quá trình phân phối của nó từ máy chủ để tránh tìm nạp và lưu vào bộ đệm dữ liệu không cần thiết. Ngoài ra, máy chủ proxy có thể có các kết nối ngược dòng nhanh và chậm, đồng thời muốn điều chỉnh tốc độ phân phối dữ liệu xuôi dòng cho phù hợp với tốc độ của chiều ngược dòng để kiểm soát mức sử dụng tài nguyên của máy chủ đó; v.v.

Các yêu cầu trên có nhắc bạn về chế độ kiểm soát luồng TCP không? Chúng nên giống nhau vì vấn đề hoàn toàn giống hệt nhau (xem phần Kiểm soát luồng). Tuy nhiên, vì các luồng HTTP/2 được ghép kênh trong một kết nối TCP, nên tính năng kiểm soát luồng TCP vừa không đủ chi tiết và không cung cấp các API cấp ứng dụng cần thiết để điều chỉnh việc phân phối các luồng riêng lẻ. Để giải quyết vấn đề này, HTTP/2 cung cấp một bộ thành phần đơn giản cho phép ứng dụng và máy chủ triển khai cơ chế kiểm soát luồng ở cấp độ kết nối và luồng của riêng chúng:

  • Tính năng điều khiển luồng mang tính định hướng. Mỗi receiver có thể chọn đặt kích thước cửa sổ bất kỳ mà nó mong muốn cho mỗi luồng và toàn bộ kết nối.
  • Kiểm soát quy trình dựa trên giá trị đóng góp. Mỗi receiver quảng cáo cửa sổ kiểm soát luồng dữ liệu luồng và kết nối ban đầu (tính bằng byte). Cửa sổ này sẽ được giảm xuống bất cứ khi nào trình gửi phát ra một khung DATA và tăng lên qua một khung WINDOW_UPDATE do trình thu nhận gửi.
  • Không thể tắt tính năng kiểm soát luồng. Khi thiết lập kết nối HTTP/2, khung SETTINGS trao đổi máy khách và máy chủ sẽ đặt kích thước cửa sổ kiểm soát luồng theo cả hai hướng. Giá trị mặc định của cửa sổ kiểm soát luồng được đặt thành 65.535 byte, nhưng receiver có thể đặt kích thước cửa sổ tối đa lớn (2^31-1 byte) và duy trì kích thước này bằng cách gửi khung WINDOW_UPDATE bất cứ khi nào nhận được bất kỳ dữ liệu nào.
  • Việc kiểm soát luồng là từng bước, không phải từ đầu đến cuối. Tức là một bên trung gian có thể sử dụng nền tảng này để kiểm soát việc sử dụng tài nguyên và triển khai cơ chế phân bổ tài nguyên dựa trên các tiêu chí và suy đoán riêng.

HTTP/2 không chỉ định bất kỳ thuật toán cụ thể nào để triển khai chức năng kiểm soát luồng. Thay vào đó, công cụ này cung cấp các thành phần đơn giản và trì hoãn việc triển khai cho máy khách và máy chủ. Máy khách và máy chủ có thể sử dụng chúng để triển khai các chiến lược tuỳ chỉnh nhằm điều chỉnh việc sử dụng và phân bổ tài nguyên, cũng như triển khai các tính năng phân phối mới có thể giúp cải thiện cả hiệu suất thực tế lẫn hiệu suất cảm nhận được (xem Tốc độ, Hiệu suất và Cảm nhận của con người) của các ứng dụng web của chúng tôi.

Ví dụ: tính năng kiểm soát luồng ở lớp ứng dụng cho phép trình duyệt chỉ tìm nạp một phần của một tài nguyên cụ thể, đặt quá trình tìm nạp ở trạng thái chờ bằng cách giảm cửa sổ kiểm soát luồng luồng về 0 rồi tiếp tục sau đó. Nói cách khác, phương thức này cho phép trình duyệt tìm nạp bản xem trước hoặc bản quét đầu tiên của hình ảnh, hiển thị hình ảnh đó và cho phép các lần tìm nạp khác có mức độ ưu tiên cao tiếp tục, đồng thời tiếp tục tìm nạp khi một số tài nguyên quan trọng khác đã tải xong.

Công nghệ đẩy của máy chủ

Một tính năng mới mạnh mẽ khác của HTTP/2 là khả năng máy chủ gửi nhiều phản hồi cho một yêu cầu của ứng dụng. Tức là ngoài phản hồi cho yêu cầu ban đầu, máy chủ có thể đẩy thêm tài nguyên đến máy khách (Hình 12-5), mà không cần máy khách phải yêu cầu từng tài nguyên một cách rõ ràng.

Máy chủ bắt đầu các luồng mới (hứa hẹn) cho các tài nguyên đẩy

Tại sao chúng tôi cần một cơ chế như vậy trong trình duyệt? Một ứng dụng web điển hình bao gồm hàng chục tài nguyên mà tất cả đều được ứng dụng phát hiện bằng cách kiểm tra tài liệu do máy chủ cung cấp. Do đó, tại sao bạn không loại bỏ độ trễ bổ sung và để máy chủ đẩy các tài nguyên liên quan trước thời hạn? Máy chủ đã biết những tài nguyên mà ứng dụng sẽ yêu cầu; đó là tính năng đẩy máy chủ.

Trên thực tế, nếu bạn đã từng đưa CSS, JavaScript hoặc bất kỳ thành phần nào khác vào qua URI dữ liệu (xem phần Tài nguyên cùng dòng), thì tức là bạn đã có trải nghiệm thực tế về tính năng đẩy máy chủ. Bằng cách chèn tài nguyên vào tài liệu theo cách thủ công, trên thực tế, chúng tôi sẽ đẩy tài nguyên đó đến máy khách mà không cần chờ khách hàng yêu cầu. Với HTTP/2, chúng tôi có thể đạt được kết quả tương tự, nhưng có thêm các lợi ích khác về hiệu suất. Tài nguyên đẩy có thể là:

  • Đã được máy khách lưu vào bộ nhớ đệm
  • Được sử dụng lại trên các trang khác nhau
  • Kết hợp với các tài nguyên khác
  • Máy chủ ưu tiên
  • Khách hàng đã từ chối

PUSH_PROMISE 101

Tất cả các luồng đẩy của máy chủ đều được bắt đầu thông qua khung PUSH_PROMISE báo hiệu ý định của máy chủ là đẩy các tài nguyên được mô tả đến máy khách và cần được phân phối trước dữ liệu phản hồi yêu cầu tài nguyên được đẩy. Thứ tự phân phối này rất quan trọng: máy khách cần biết những tài nguyên mà máy chủ dự định đẩy lên để tránh tạo các yêu cầu trùng lặp cho các tài nguyên này. Chiến lược đơn giản nhất để đáp ứng yêu cầu này là gửi tất cả các khung PUSH_PROMISE, chỉ chứa các tiêu đề HTTP của tài nguyên đã hứa, trước phản hồi của thành phần mẹ (nói cách khác là khung DATA).

Sau khi nhận được khung PUSH_PROMISE, ứng dụng có thể từ chối luồng (thông qua khung RST_STREAM) nếu muốn. (Chẳng hạn, điều này có thể xảy ra vì tài nguyên đã có trong bộ nhớ đệm.) Đây là một điểm cải tiến quan trọng so với HTTP/1.x. Ngược lại, việc sử dụng tài nguyên cùng dòng, vốn là một "cách tối ưu hoá" phổ biến cho HTTP/1.x, tương đương với thao tác "đẩy bắt buộc": ứng dụng không thể chọn không sử dụng, huỷ hoặc xử lý riêng tài nguyên cùng dòng.

Với HTTP/2, máy khách vẫn có toàn quyền kiểm soát cách sử dụng tính năng đẩy máy chủ. Ứng dụng có thể giới hạn số lượng luồng được đẩy đồng thời; điều chỉnh cửa sổ kiểm soát luồng ban đầu để kiểm soát lượng dữ liệu được đẩy khi luồng mở lần đầu; hoặc tắt hoàn toàn tính năng đẩy máy chủ. Các lựa chọn ưu tiên này được thông báo qua khung SETTINGS khi bắt đầu kết nối HTTP/2 và có thể được cập nhật bất cứ lúc nào.

Mỗi tài nguyên được đẩy là một luồng mà, không giống như tài nguyên cùng dòng, cho phép ứng dụng ghép kênh, ưu tiên và xử lý từng tài nguyên đó. Hạn chế bảo mật duy nhất (được trình duyệt thực thi) là các tài nguyên được đẩy phải tuân thủ chính sách cùng nguồn gốc: máy chủ phải có thẩm quyền đối với nội dung được cung cấp.

Nén tiêu đề

Mỗi lượt chuyển HTTP chứa một tập hợp các tiêu đề mô tả tài nguyên được chuyển và các thuộc tính của tài nguyên đó. Trong HTTP/1.x, siêu dữ liệu này luôn được gửi dưới dạng văn bản thuần tuý và tăng thêm mức hao tổn từ 500–800 byte cho mỗi lượt chuyển và đôi khi nhiều kilobyte nếu sử dụng cookie HTTP. (Xem phần Đo lường và kiểm soát mức hao tổn giao thức.) Để giảm mức hao tổn này và cải thiện hiệu suất, HTTP/2 nén siêu dữ liệu tiêu đề yêu cầu và phản hồi bằng định dạng nén HPACK, sử dụng 2 kỹ thuật đơn giản nhưng mạnh mẽ:

  1. API này cho phép mã hoá các trường tiêu đề được truyền qua mã Huffman tĩnh, giúp giảm kích thước truyền riêng lẻ của các trường đó.
  2. Phương thức này yêu cầu cả ứng dụng và máy chủ duy trì và cập nhật danh sách đã lập chỉ mục cho các trường tiêu đề đã thấy trước đó (nói cách khác, thao tác này thiết lập một ngữ cảnh nén dùng chung), sau đó được dùng làm tham chiếu để mã hoá hiệu quả các giá trị đã truyền trước đó.

Mã Huffman cho phép nén các giá trị riêng lẻ khi chuyển và danh sách các giá trị được lập chỉ mục đã được chuyển trước đó cho phép chúng tôi mã hoá các giá trị trùng lặp bằng cách chuyển các giá trị chỉ mục có thể dùng để tra cứu và tạo lại các khoá và giá trị tiêu đề đầy đủ một cách hiệu quả.

HPACK: Nén tiêu đề cho HTTP/2

Ngoài ra, ngữ cảnh nén HPACK bao gồm một bảng tĩnh và động: bảng tĩnh được xác định trong thông số kỹ thuật và cung cấp danh sách các trường tiêu đề HTTP phổ biến mà tất cả kết nối đều có thể sử dụng (ví dụ: tên tiêu đề hợp lệ); ban đầu bảng trống và được cập nhật dựa trên các giá trị được trao đổi trong một kết nối cụ thể. Do đó, kích thước của mỗi yêu cầu sẽ giảm bằng cách sử dụng phương pháp mã hoá Huffman tĩnh cho các giá trị chưa từng thấy trước đây, đồng thời thay thế chỉ mục cho các giá trị đã có trong bảng tĩnh hoặc động ở mỗi bên.

Tính bảo mật và hiệu suất của HPACK

Các phiên bản ban đầu của HTTP/2 và SPDY đã sử dụng zlib cùng với từ điển tuỳ chỉnh để nén tất cả tiêu đề HTTP. Điều này đã giúp giảm 85% đến 88% kích thước của dữ liệu tiêu đề được chuyển và cải thiện đáng kể độ trễ thời gian tải trang:

Đối với đường liên kết DSL băng thông thấp hơn, trong đó đường liên kết tải lên chỉ là 375 Kb/giây, cụ thể là tính năng nén tiêu đề yêu cầu, đã giúp cải thiện đáng kể thời gian tải trang cho một số trang web (nói cách khác, những trang web đưa ra số lượng lớn yêu cầu về tài nguyên). Chúng tôi nhận thấy thời gian tải trang giảm 45–1142 mili giây chỉ nhờ nén tiêu đề. (sách trắng SPDY, chromium.org)

Tuy nhiên, vào mùa hè năm 2012, một cuộc tấn công bảo mật "CRIME" đã được phát hành chống lại các thuật toán nén TLS và SPDY. Điều này có thể dẫn đến hành vi chiếm đoạt phiên. Do đó, thuật toán nén zlib đã được thay thế bằng HPACK, được thiết kế riêng để: giải quyết các vấn đề bảo mật đã phát hiện, hiệu quả và đơn giản để triển khai đúng cách, và tất nhiên là cho phép nén tốt siêu dữ liệu tiêu đề HTTP.

Để biết đầy đủ thông tin chi tiết về thuật toán nén HPACK, hãy xem bài viết IETF HPACK – Nén tiêu đề cho HTTP/2.

Tài liệu đọc thêm