Giới thiệu về HTTP/2

HTTP/2 sẽ làm cho các ứng dụng của chúng tôi nhanh hơn, đơn giản hơn và mạnh mẽ hơn – một tổ hợp hiếm gặp – bằng cách cho phép chúng tôi huỷ nhiều giải pháp HTTP/1.1 từng được thực hiện trước đó trong các ứng dụng và giải quyết những mối lo ngại này trong chính tầng truyền tải. Hơn nữa, việc này còn mở ra nhiều 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 chế độ ghép kênh yêu cầu và phản hồi đầy đủ, giảm thiểu mức hao tổn giao thức thông qua việc 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, có rất nhiều cải tiến khác về giao thức, chẳng hạn như cơ chế kiểm soát luồng mới, 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 nên hiểu và tận dụng trong các ứ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. Tất cả 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 đề, vẫn giữ nguyên. Thay vào đó, HTTP/2 sửa đổi cách dữ liệu được định dạng (lấy khung) và di chuyển giữa ứng dụng và máy chủ, cả hai đều quản lý toàn bộ quy trình và ẩn mọi sự phức tạp khỏi các ứng dụng của chúng ta trong lớp khung mới. Do đó, tất cả ứng dụng hiện có có thể được phân phối mà không cần sửa đổi.

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

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

Tuy nhiên, trừ phi 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ổng TCP thô, bạn sẽ không thấy có gì khác biệt: toàn bộ quá trình lấy khung hình mới ở cấp thấp đều do máy khách và máy chủ thay mặt bạn thực hiện. Những điểm khác biệt duy nhất có thể ghi nhận sẽ là cải thiện hiệu suất 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 về SPdy và HTTP/2

SPdy là một giao thức thử nghiệm, được Google phát triển và 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ế về hiệu suất phổ biến của HTTP/1.1. Cụ thể, chúng tôi đã đặt ra các mục tiêu đã đề ra cho dự án như sau:

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

Không lâu sau thông báo đầu tiên, Mike Belshe và Roberto Peon, cả hai kỹ sư phần mềm tại Google, đã chia sẻ kết quả, tài liệu và mã nguồn đầu tiên của họ để 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 đến 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 tăng nhanh chóng, cả lớn (ví dụ: Google, Twitter, Facebook) và nhỏ, đều đang triển khai SPdy trong cơ sở hạ tầng của mình. Trên thực tế, SPdy đang trên đà trở thành một tiêu chuẩn thực tế thông qua việc ngày càng nhiều người sử dụng trong ngành.

Tuân thủ xu hướng này, Nhóm làm việc HTTP (HTTP-WG) đã khởi động một nỗ lực mới để lấy các bài học kinh nghiệm từ SPdy, xây dựng và cải thiện chúng, đồ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, lời gọi mở cho các đề xuất HTTP/2 đã được đưa ra 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 xuất phát cho giao thức HTTP/2 mới.

Trong vài năm tiếp theo, SPdy và HTTP/2 tiếp tục kết hợp với nhau, trong đó SPdy hoạt động như 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ì có vẻ phù hợp trên giấy có thể không hoạt động 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 nó vào tiêu chuẩn HTTP/2. Cuối cùng, quá trình này kéo dài trong ba năm và tạo ra hơn một chục 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: Xuất bản HTTP/2 draft-17 và HPACK draft-12
  • Tháng 8 năm 2014: Cuộc gọi gần đây nhất của Nhóm công tác về 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: Xuất bản RFC 7540 (HTTP/2) và RFC 7541 (HPACK)

Đầ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 ngừng sử dụng tiện ích SPdy và NPN cho TLS:

Các thay đổi chính của HTTP/2 từ 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 đã 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 do hầu hết các lợi ích đều có trong HTTP/2, nên đã đến lúc phải nói lời tạm biệt. Chúng tôi dự định sẽ ngừng hỗ trợ SPdy vào đầu năm 2016 và cũng sẽ 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 có thể thấy được nhiều sự áp dụng rộng rãi trong ngành về 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 trang web, trình duyệt và máy chủ 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 tra kỹ lưỡng nhất ngay từ đầu. Vào thời điểm IESG phê duyệt HTTP/2, đã có hàng chục cách triển khai máy khách và máy chủ được thử nghiệm kỹ lưỡng và sẵn sàng phát hành. 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 hưởng lợi ích của giao thức này vì 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 trước của giao thức HTTP được thiết kế nhằm đơn giản hoá quá trình triển khai: HTTP/0.9 là giao thức một dòng để tự khởi động World Wide Web; HTTP/1.0 đã ghi lại các tiện ích phổ biến thành HTTP/0.9 theo tiêu chuẩn thông tin; HTTP/1.1 đã ra mắt tiêu chuẩn IETF chính thức; xem Lịch sử tóm tắt về HTTP. Do đó, HTTP/0.9-1.x đã phân phối chính xác những gì được đặt ra: HTTP là một trong những giao thức ứng dụng được sử dụng rộng rãi nhất trên Internet.

Thật không may, việc triển khai đơn giản cũng đi kèm với chi phí hiệu suất ứng dụng: máy khách HTTP/1.x cần sử 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 một cách 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 hạn chế 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, tính 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 cho cả nhà phát triển và người dùng web, chính là khoảng cách 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 thức về độ trễ bằng cách áp dụng tính năng nén trường tiêu đề và cho phép nhiều 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 báo yêu cầu và phản hồi trên cùng một kết nối và sử dụng mã lập trình 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, giúp các yêu cầu quan trọng hơn hoàn thành nhanh hơn, nhờ đó cải thiện hiệu suất hơn nữa.

Giao thức thu được thân thiện với mạng hơn, 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 với các luồng khác và kết nối lâu hơn, dẫn đến việc tận dụng tốt 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 tính năng lấy khung hình 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 các khái niệm cốt lõi đượ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. Mặc dù API cấp cao vẫn không đổi, nhưng điều quan trọng là bạn phải hiểu được cách các thay đổi cấp thấp giải quyết hạn chế về hiệu suất của các giao thức trước đó. Hãy cùng tham quan lớp khung nhị phân và các tính năng của lớp này.

Lớp khung nhị phân

Cốt lõi của tất cả các cải tiến về hiệu suất của HTTP/2 là lớp 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 nhị phân HTTP/2

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

Do đó, cả ứng dụng và máy chủ đều phải sử dụng cơ chế mã hoá nhị phân mới để hiểu lẫn nhau: ứng dụng 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 vô cùng không biết về tất cả những thay đổi này, vì máy khách và máy chủ sẽ thay mặt chúng tôi thực hiện mọi công việc lấy khung hình cần thiết.

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

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

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

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 được thực hiện qua một kết nối TCP có thể truyền nhiều luồng hai chiều với số lượng bất kỳ.
  • Mỗi luồng có một giá trị nhận dạng duy nhất và thông tin về 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 mang một loại dữ liệu cụ thể, ví dụ: tiêu đề HTTP, tải trọng thư, v.v. Các khung của nhiều luồng có thể được xen kẽ, sau đó được 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 giao thức HTTP thành các khung trao đổi các khung mã hoá nhị phân, sau đó được ánh xạ đến các thông điệp thuộc về một luồng cụ thể, tất cả đều được ghép kênh trong một kết nối TCP duy nhất. Đây là nền tảng cho phép 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 kênh yêu cầu và phản hồi

Với HTTP/1.x, nếu muốn thực hiện nhiều yêu cầu song song để cải thiện hiệu suất, thì ứng dụng phải 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, giúp đảm bảo rằng mỗi lần chỉ gửi một phản hồi (hàng đợi phản hồi) cho mỗi kết nối. Tệ hơn nữa, điều này còn dẫn đến việc chặn nội dung đầu vào và việc sử dụng kết nối TCP cơ bản không hiệu quả.

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

Ghép kênh yêu cầu HTTP/2 và phản hồi trong một kết nối được chia sẻ

Ảnh chụp nhanh ghi lại nhiều luồng trong một chuyến bay trong cùng một kết nối. Ứng dụng đ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 hình xen kẽ đến ứng dụng đối với luồng 1 và 3. Do đó, có ba luồng song song đang diễn ra.

Khả năng chia nhỏ một thông báo HTTP thành các khung độc lập, xen kẽ các khung hình đó rồi tập hợp lại ở phía bên kia là tính năng nâng cao quan trọng nhất của HTTP/2. Trên thực tế, công cụ này tạo ra 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 phản hồi nhưng không chặn bất kỳ phản hồi nào.
  • Sử dụng một kết nối để 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 tiếp, sprite hình ảnh và phân đoạn miền).
  • Giảm thời gian tải trang bằng cách 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 tính năng khác...

Lớp tạo khung nhị phân mới trong HTTP/2 giải quyết vấn đề chặn đầu dòng gặp phải trong HTTP/1.x và không cần phải có nhiều kết nối để bật quá trình xử lý và phân phối song song các yêu cầu và phản hồi. Nhờ đó, việc này giúp việc triển khai ứng dụng của chúng tôi nhanh hơn, đơn giản hơn và rẻ hơn.

Mức độ ưu tiên của luồng

Khi có thể chia thông báo HTTP thành nhiều khung 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ự mà các khung được xen kẽ và phân phối bởi cả ứng dụng và máy chủ sẽ trở thành một yếu tố quan trọng cần cân nhắc về hiệu suất. Để hỗ trợ 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 đi kèm:

  • Mỗi luồng có thể được gán trọng số bằng số nguyên từ 1 đến 256.
  • Mỗi luồng có thể được phụ thuộc rõ ràng trên một luồng khác.

Sự kết hợp các phần phụ thuộc và trọng số của luồng cho phép ứng dụng xây dựng và truyền đạt "cây ưu tiên" biểu thị cách ứng dụng 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 việ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 để đả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, 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 mẹ cần được phân bổ tài nguyên trước các phần phụ thuộc. Nói cách khác là "Vui lòng xử lý và gửi câu trả lời D trước câu trả lời C".

Bạn nên phân bổ tài nguyên theo tỷ lệ thuận với trọng số của các luồng có cùng mẹ (nói cách khác là luồng đồng cấp). 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, hãy xác định tỷ lệ tài nguyên mà mỗi luồng này sẽ nhận được:

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

Do đó, luồng A sẽ nhận được 3/4 tài nguyên còn 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 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ế nữa ở hình trên. Từ trái sang phải:

  1. Không có luồng A và B 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 lượt phân bổ tài nguyên đầy đủ 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 lựa chọn ưu tiên mạnh hơn.
  3. Luồng D phải nhận được toàn bộ tài nguyên được phân bổ trước C; C sẽ nhận được lượt phân bổ đầy đủ tài nguyên trước A và B; luồng B sẽ nhận được 1/3 tài nguyên được phân bổ cho luồng A.
  4. Luồng D phải nhận được mức phân bổ đầy đủ tài nguyên trước E và C; E và C phải được phân bổ đồng đều trước A và B; A và B sẽ được phân bổ 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 các phần phụ thuộc và trọng số của luồng sẽ cung cấp một ngôn ngữ có ý nghĩa để ư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 khi 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ũng cho phép khách hàng cập nhật các lựa chọn ưu tiên này bất cứ lúc nào, qua đó hỗ trợ tối ưu hoá hơn nữa trong trình duyệt. Nói cách khác, chúng ta 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 nguồn gốc

Với cơ chế lấy khung hình nhị phân mới được triển khai, HTTP/2 không còn cần nhiều kết nối TCP đến song song các luồng đa kênh; mỗi luồng được chia thành nhiều khung hình có thể được xen kẽ và ưu tiên. Do đó, tất cả các kết nối HTTP/2 đều ổn định và chỉ yêu cầu 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ả SPD và HTTP/2, tính năng định tuyến là ghép kênh tuỳ ý trên một kênh được kiểm soát tắc nghẽn. Tôi rất ngạc nhiên về tầm quan trọng và hiệu quả của nó. Một chỉ số tuyệt vời về điều mà tôi thích là phần kết nối được tạo chỉ thực hiện một giao dịch HTTP duy nhất (và do đó khiến giao dịch đó phải chịu toàn bộ chi phí). Đối với HTTP/1, 74% kết nối đang hoạt động của chúng ta chỉ mang 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 muốn. Nhưng trong HTTP/2, con số đó giảm xuống còn 25%. Đó là một thắng lợi to lớn cho việc giảm chi phí chung. (HTTP/2 đang hoạt động trên Firefox, Patrick McManus)

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

Điều khiển luồng

Kiểm soát luồng là một cơ chế giúp người gửi không thể xử lý quá nhiều dữ liệu mà người nhận có thể không muốn hoặc không thể xử lý: người nhận có thể bận, tải quá nhiều hoặc có thể 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 có 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 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ột máy chủ proxy có thể có các kết nối hạ nguồn và tải lên chậm, đồng thời muốn điều chỉnh tốc độ phân phối dữ liệu cho phù hợp với tốc độ của luồng ngược dòng để kiểm soát mức sử dụng tài nguyên của nó; và cứ tiếp tục như vậy.

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

  • Điều khiển luồng có tính định hướng. Mỗi receiver có thể chọn đặt bất kỳ kích thước cửa sổ nào mà mình muốn cho mỗi luồng và toàn bộ kết nối.
  • Kiểm soát luồng dựa trên tín dụng. Mỗi receiver quảng cáo cửa sổ điều khiển luồng ban đầu và luồng (tính bằng byte). Cửa sổ này sẽ giảm bất cứ khi nào người gửi phát ra một khung DATA và tăng lên thông qua khung WINDOW_UPDATE do dịch vụ nhận gửi.
  • Không thể tắt tính năng điều khiển luồng. Khi kết nối HTTP/2 được thiết lập, máy khách và máy chủ sẽ trao đổi khung SETTINGS, thiết lập 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 là 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ì bằng cách gửi một khung WINDOW_UPDATE bất cứ khi nào có dữ liệu được nhận.
  • Điều khiển luồng là từng bước, không phải là toàn bộ. Điều này nghĩa là bên trung gian có thể sử dụng nó để kiểm soát việc sử dụng tài nguyên và triển khai các cơ chế phân bổ tài nguyên dựa trên tiêu chí và thông tin phỏng đoán riêng.

HTTP/2 không chỉ định bất kỳ thuật toán cụ thể nào để triển khai việc kiểm soát luồng. Thay vào đó, nó cung cấp các khối dựng đơ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 công cụ này để triển khai các chiến lược tuỳ chỉnh nhằm điều chỉnh mức sử dụng và phân bổ tài nguyên, cũng như triển khai các khả 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 nhận thấy được (xem phần Tốc độ, Hiệu suất và Nhận thức của con người) của các ứng dụng web.

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 tài nguyên cụ thể, tạm dừng tìm nạp bằng cách giảm cửa sổ kiểm soát luồng luồng xuống 0 rồi tiếp tục sau đó. Nói cách khác, API này cho phép trình duyệt tìm nạp bản xem trước hoặc lần quét hình ảnh đầu tiên, hiển thị hình ảnh đó và cho phép các phương thức tìm nạp có mức độ ưu tiên cao khác tiếp tục và tiếp tục tìm nạp sau khi các tài nguyên quan trọng hơn đã tải xong.

Đẩy 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 ứng dụng. Nghĩa 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 cho ứng dụng (Hình 12-5), mà không cần ứng dụng 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 luồng mới (hứa hẹn) cho tài nguyên đẩy

Tại sao chúng ta cần một cơ chế như vậy trong trình duyệt? Một ứng dụng web thông thường bao gồm hàng chục tài nguyên, 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 không loại bỏ độ trễ thêm và cho phép máy chủ đẩy trước các tài nguyên liên quan? Máy chủ đã biết tài nguyên nào mà ứng dụng sẽ yêu cầu; đó là lệnh đẩy của máy chủ.

Trên thực tế, nếu bạn đã từng sử dụng nội tuyến CSS, JavaScript hoặc bất kỳ thành phần nào khác thông qua URI dữ liệu (xem phần Chèn tài nguyên), thì bạn đã có kinh nghiệm thực tế về tính năng đẩy của máy chủ. Bằng cách chèn tài nguyên theo cách thủ công vào tài liệu, trên thực tế, chúng tôi đẩy tài nguyên đó cho ứng dụng mà không cần chờ ứng dụ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 với thêm nhiều lợi ích về hiệu suất. Tài nguyên đẩy có thể là:

  • 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
  • Được Multiplexed cùng với các tài nguyên khác
  • Đượ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 qua khung PUSH_PROMISE, báo hiệu ý định của máy chủ là đẩy các tài nguyên đã mô tả đến ứng dụng 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: ứng dụng cần biết những tài nguyên mà máy chủ định đẩy để 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. (Điều này có thể xảy ra chẳng hạn như 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 cùng dòng tài nguyên (một cách "tối ưu hoá" phổ biến cho HTTP/1.x tương đương với hoạt động "buộc đẩy": ứng dụng không thể chọn không sử dụng, huỷ hoặc xử lý tài nguyên cùng dòng một cách riêng lẻ.

Với HTTP/2, máy khách vẫn nắm toàn quyền kiểm soát cách sử dụng tính năng đẩy của 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ổ điều khiển luồng ban đầu để kiểm soát lượng dữ liệu được đẩy khi luồng mở lần đầu tiên; hoặc tắt hoàn toàn chế độ đẩy của máy chủ. Các lựa chọn ưu tiên này được thông báo qua khung SETTINGS ở đầ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, khác với tài nguyên cùng dòng, cho phép máy khách riêng lẻ ghép, ưu tiên và xử lý tài nguyên đó. Hạn chế duy nhất về bảo mật (do 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ó 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 quá trình chuyển HTTP chứa một nhóm 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à bổ sung thêm khoảng từ 500 đến 800 byte chi phí cho mỗi lượt chuyển và đôi khi là thêm kilobyte nếu đang 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 đề phản hồi và yêu cầu bằng định dạng nén HPACK sử dụng hai 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 đề đã 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 được lập chỉ mục gồm các trường tiêu đề đã thấy trước đó (nói cách khác, ứng dụng thiết lập một ngữ cảnh nén dùng chung), sau đó được dùng làm tệp tham chiếu để mã hoá một cách hiệu quả các giá trị được truyền trước đó.

Phương pháp lập trình Huffman cho phép nén các giá trị riêng lẻ khi được chuyển, còn danh sách lập chỉ mục gồm các giá trị đã 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 truyền các giá trị chỉ mục có thể dùng để tra cứu và tạo lại toàn bộ khoá và giá trị tiêu đề một cách hiệu quả.

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

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

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:

Trên đườ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ỉ có 375 Kb/giây), cụ thể là yêu cầu nén tiêu đề đã cải thiện đáng kể thời gian tải trang cho một số trang web (nói cách khác là 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ỉ đơn giản là nhờ tính năng 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 xuất bản nhằm chống lại các thuật toán nén TLS và SPdy. Điều này có thể dẫn đến việc chiếm đoạt phiên. Do đó, thuật toán nén zlib đã được thay thế bằng HPACK. Thuật toán này được thiết kế riêng để: xử lý 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 – Tiêu đề nén cho HTTP/2.

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