Xem xét lại Server-Timing như một công cụ giám sát quan trọng

theanh

Administrator
Nhân viên
Trong thế giới Tiêu đề HTTP, có một tiêu đề mà tôi tin rằng xứng đáng được quan tâm nhiều hơn, đó là tiêu đề Server-Timing. Đối với tôi, đây là tiêu đề bắt buộc phải sử dụng trong bất kỳ dự án nào có giám sát người dùng thực (RUM). Thật ngạc nhiên, các cuộc thảo luận về giám sát hiệu suất web hiếm khi đề cập đến Server-Timing hoặc đề cập đến hiểu biết rất hời hợt về ứng dụng của nó — mặc dù nó đã ra mắt trong nhiều năm.

Một phần là do hạn chế được nhận thấy là nó chỉ dành riêng cho việc theo dõi thời gian trên máy chủ — nó có thể mang lại nhiều giá trị hơn nữa! Hãy cùng xem xét lại cách chúng ta có thể tận dụng tiêu đề này. Trong bài viết này, chúng ta sẽ đi sâu hơn để chỉ ra cách tiêu đề Server-Timing mạnh mẽ một cách độc đáo, đưa ra một số ví dụ thực tế bằng cách giải quyết các vấn đề giám sát đầy thách thức với tiêu đề này và khơi dậy một số cảm hứng sáng tạo bằng cách kết hợp kỹ thuật này với các nhân viên dịch vụ.

Server-Timing mạnh mẽ một cách độc đáo, vì đây là tiêu đề HTTP Response duy nhất hỗ trợ thiết lập các giá trị dạng tự do cho một tài nguyên cụ thể và cho phép chúng có thể truy cập được từ API trình duyệt JavaScript riêng biệt với các tham chiếu Yêu cầu/Phản hồi. Điều này cho phép các yêu cầu tài nguyên, bao gồm cả tài liệu HTML, được làm giàu dữ liệu trong suốt vòng đời của nó và thông tin đó có thể được kiểm tra để đo các thuộc tính của tài nguyên đó!

Tiêu đề duy nhất khác gần với khả năng này là tiêu đề HTTP Set-Cookie / Cookie. Không giống như tiêu đề Cookie, Server-Timing chỉ nằm trong phản hồi cho một tài nguyên cụ thể, trong đó Cookie được gửi theo yêu cầu và phản hồi cho tất cả các tài nguyên sau khi chúng được đặt và chưa hết hạn. Việc liên kết dữ liệu này với một phản hồi tài nguyên duy nhất là tốt hơn, vì nó ngăn dữ liệu tạm thời về tất cả các phản hồi trở nên mơ hồ và góp phần vào việc thu thập ngày càng nhiều cookie được gửi cho các tài nguyên còn lại trong quá trình tải trang.

Thiết lập Server-Timing

Có thể thiết lập tiêu đề này trên phản hồi của bất kỳ tài nguyên mạng nào, chẳng hạn như XHR, fetch, hình ảnh, HTML, bảng định kiểu, v.v. Bất kỳ máy chủ hoặc proxy nào cũng có thể thêm tiêu đề này vào yêu cầu để cung cấp dữ liệu có thể kiểm tra được. Tiêu đề được xây dựng thông qua tên có mô tả tùy chọn và/hoặc giá trị số liệu. Trường bắt buộc duy nhất là tên. Ngoài ra, có thể có nhiều tiêu đề Server-Timing được đặt trên cùng một phản hồi sẽ được kết hợp và phân tách bằng dấu phẩy.

Một số ví dụ đơn giản:
Mã:
Server-Timing: cdn_process;desc=”cach_hit";dur=123Server-Timing: cdn_process;desc=”cach_hit", server_process; dur=42;Server-Timing: cdn_cache_hitServer-Timing: cdn_cache_hit; dur=123
Lưu ý quan trọng: Đối với các tài nguyên có nguồn gốc chéo, Server-Timing và các giá trị thời gian nhạy cảm khác không được hiển thị cho người dùng. Để cho phép các tính năng này, chúng ta cũng sẽ cần tiêu đề Timing-Allow-Origin bao gồm giá trị origin hoặc * của chúng ta.

Đối với bài viết này, đó là tất cả những gì chúng ta cần để bắt đầu công khai giá trị và để lại các bài viết cụ thể hơn khác để đi sâu hơn. Tài liệu MDN.

Tiêu thụ Server-Timing

Trình duyệt web công khai API Dòng thời gian hiệu suất toàn cầu để kiểm tra thông tin chi tiết về các số liệu/sự kiện cụ thể đã xảy ra trong suốt vòng đời của trang. Từ API này, chúng ta có thể truy cập các tiện ích mở rộng API hiệu suất tích hợp, cho phép hiển thị thời gian dưới dạng PerformanceEntries.

Có một số ít các loại mục nhập phụ khác nhau nhưng trong phạm vi của bài viết này, chúng ta sẽ quan tâm đến các loại mục nhập phụ PerformanceResourceTimingPerformanceNavigationTiming. Các loại mục nhập phụ này hiện là những loại mục nhập phụ duy nhất liên quan đến các yêu cầu mạng và do đó hiển thị thông tin Server-Timing.

Đối với tài liệu HTML cấp cao nhất, tài liệu này được truy xuất khi người dùng điều hướng nhưng vẫn là một yêu cầu tài nguyên. Vì vậy, thay vì có các PerformanceEntries khác nhau cho điều hướng và các khía cạnh tài nguyên, PerformanceNavigationTiming cung cấp dữ liệu tải tài nguyên cũng như dữ liệu cụ thể bổ sung cho điều hướng. Vì chúng ta chỉ xem xét dữ liệu tải tài nguyên nên chúng ta sẽ chỉ gọi các yêu cầu (tài liệu điều hướng hoặc các tài liệu khác) đơn giản là tài nguyên.

Để truy vấn các mục nhập hiệu suất, chúng ta có 3 API mà chúng ta có thể gọi: performance.getEntries(), performance.getEntriesByType(), performance.getEntriesByName(). Mỗi API sẽ trả về một mảng các mục nhập hiệu suất với độ cụ thể tăng dần.
Mã:
const navResources = performance.getEntriesByType('navigation');const allOtherResources = performance.getEntriesByType('resource');
Cuối cùng, mỗi tài nguyên này sẽ có một trường serverTiming là một mảng các đối tượng được ánh xạ từ thông tin được cung cấp trong tiêu đề Server-Timing — trong đó PerformanceEntryServerTiming được hỗ trợ (xem các cân nhắc bên dưới). Hình dạng của các đối tượng trong mảng này được xác định bởi giao diện PerformanceEntryServerTiming về cơ bản ánh xạ các tùy chọn số liệu tiêu đề Server-Timing tương ứng: name, descriptionduration.

Chúng ta hãy xem xét điều này trong một ví dụ hoàn chỉnh.

Một yêu cầu đã được gửi đến điểm cuối dữ liệu của chúng tôi và trong số các tiêu đề, chúng tôi đã gửi lại những thông tin sau:
Mã:
Server-Timing: lookup_time; dur=42, db_cache; desc="hit";
Ở phía máy khách, hãy giả sử đây là tài nguyên duy nhất của chúng ta được tải trên trang này:
Mã:
const dataEndpointEntry = performance.getEntriesByName('resource')[0];console.log( dataEndpointEntry.serverTiming );// đầu ra:// [// { name: “lookup_time”, description: undefined, duration: 42 },// { name: “db_cache”, description:”hit”, duration: 0.0 },// ]
Điều đó bao gồm các API cơ bản được sử dụng để truy cập các mục tài nguyên và thông tin được cung cấp từ tiêu đề Server-Timing. Để biết thêm thông tin chi tiết về các API này, hãy xem phần tài nguyên ở cuối.

Bây giờ chúng ta đã có những kiến thức cơ bản về cách thiết lập và sử dụng tổ hợp tiêu đề/API này, hãy cùng tìm hiểu sâu hơn về những điều thú vị.

Không chỉ là vấn đề thời gian​

Qua các cuộc trò chuyện và công việc của tôi với các nhà phát triển khác, cái tên “Server-Timing” gợi lên mối liên hệ chặt chẽ rằng đây là một công cụ được sử dụng để theo dõi khoảng thời gian hoặc chi tiết về khoảng thời gian cụ thể. Điều này hoàn toàn hợp lý với tên gọi và mục đích của tính năng. Tuy nhiên, thông số kỹ thuật cho tiêu đề này rất linh hoạt; cho phép các giá trị và thể hiện thông tin có thể không liên quan gì đến thời gian hoặc hiệu suất theo bất kỳ cách nào. Ngay cả trường duration cũng không có đơn vị đo lường được xác định trước — bạn có thể đặt bất kỳ số nào (kép) vào trường đó. Bằng cách lùi lại và nhận ra rằng các trường có sẵn không có ràng buộc đặc biệt nào với các loại dữ liệu cụ thể, chúng ta có thể thấy rằng kỹ thuật này cũng là một cơ chế phân phối hiệu quả cho bất kỳ dữ liệu tùy ý nào cho phép nhiều khả năng thú vị.

Ví dụ về thông tin không theo thời gian mà bạn có thể gửi: Mã trạng thái phản hồi HTTP, vùng, ID yêu cầu, v.v. — bất kỳ dữ liệu dạng tự do nào phù hợp với nhu cầu của bạn. Trong một số trường hợp, chúng tôi có thể gửi thông tin trùng lặp có thể đã có trong các tiêu đề khác, nhưng không sao cả. Như chúng tôi sẽ đề cập, việc truy cập các tiêu đề khác cho các tài nguyên thường không khả thi và nếu nó có giá trị giám sát, thì việc trùng lặp là bình thường.

Không cần tham chiếu​

Do thiết kế của API trình duyệt web, hiện tại không có cơ chế nào để truy vấn các yêu cầu và phản hồi tương đối của chúng sau đó. Điều này rất quan trọng vì cần phải quản lý bộ nhớ. Để đọc thông tin về một yêu cầu hoặc phản hồi tương ứng của yêu cầu đó, chúng ta phải có tham chiếu trực tiếp đến các đối tượng này. Tất cả các phần mềm giám sát hiệu suất web mà chúng tôi làm việc cùng đều cung cấp các máy khách RUM đặt thêm các lớp vá lỗi trên trang để duy trì quyền truy cập trực tiếp vào yêu cầu được thực hiện hoặc phản hồi trả về. Đây là cách họ cung cấp khả năng giám sát trực tiếp tất cả các yêu cầu được thực hiện mà không cần chúng tôi phải thay đổi mã để giám sát yêu cầu. Đây cũng là lý do tại sao các máy khách này yêu cầu chúng tôi đặt máy khách trước bất kỳ yêu cầu nào mà chúng tôi muốn giám sát. Sự phức tạp của việc vá tất cả các API mạng khác nhau và chức năng được liên kết của chúng có thể trở nên rất phức tạp rất nhanh. Nếu có một cơ chế truy cập dễ dàng để kéo thông tin tài nguyên/yêu cầu có liên quan về một yêu cầu, chúng tôi chắc chắn sẽ muốn thực hiện điều đó ở phía giám sát.

Để làm cho vấn đề trở nên khó khăn hơn, mô hình vá lỗi này chỉ hoạt động đối với các tài nguyên mà JavaScript được sử dụng trực tiếp để khởi tạo mạng. Đối với Hình ảnh, Biểu định kiểu, tệp JS, Tài liệu HTML, v.v., các phương pháp giám sát chi tiết yêu cầu/phản hồi rất hạn chế vì thường không có tham chiếu trực tiếp nào khả dụng.

Đây là nơi API Dòng thời gian hiệu suất cung cấp giá trị tuyệt vời. Như chúng ta đã thấy trước đó, về cơ bản, đây là danh sách các yêu cầu được thực hiện và một số dữ liệu về từng yêu cầu tương ứng. Dữ liệu cho mỗi mục nhập hiệu suất rất tối thiểu và hầu như chỉ giới hạn ở thông tin thời gian và một số trường, tùy thuộc vào giá trị của chúng, sẽ ảnh hưởng đến cách hiệu suất của tài nguyên được đo lường so với các tài nguyên khác. Trong số các trường thời gian, chúng ta có quyền truy cập trực tiếp vào dữ liệu serverTiming.

Gộp tất cả các phần lại với nhau, các tài nguyên có thể có tiêu đề Server-Timing trong phản hồi mạng của chúng chứa dữ liệu tùy ý. Sau đó, có thể dễ dàng truy vấn các tài nguyên đó và có thể truy cập dữ liệu Server-Timing mà không cần tham chiếu trực tiếp đến chính yêu cầu/phản hồi. Với điều này, không quan trọng bạn có thể truy cập/quản lý các tham chiếu cho một tài nguyên hay không, tất cả các tài nguyên đều có thể được làm giàu bằng dữ liệu tùy ý có thể truy cập từ API trình duyệt web dễ sử dụng. Đó là một khả năng rất độc đáo và mạnh mẽ!

Tiếp theo, chúng ta hãy áp dụng mô hình này cho một số thách thức khó khăn theo truyền thống để đo lường.

Giải pháp 1: Kiểm tra hình ảnh và các phản hồi tài sản khác​

Hình ảnh, bảng định kiểu, tệp JavaScript, v.v. thường không được tạo bằng cách sử dụng các tham chiếu trực tiếp đến API mạng có thông tin về các yêu cầu đó. Ví dụ: chúng tôi hầu như luôn kích hoạt tải xuống hình ảnh bằng cách đặt phần tử img trong HTML của mình. Có những kỹ thuật để tải các tài sản này yêu cầu sử dụng API JavaScript fetch/xhr để kéo dữ liệu và đẩy trực tiếp vào tham chiếu tài sản. Mặc dù kỹ thuật thay thế đó giúp chúng dễ theo dõi hơn, nhưng trong hầu hết các trường hợp, nó lại gây ra thảm họa cho hiệu suất. Thách thức là làm thế nào chúng ta có thể kiểm tra các tài nguyên này mà không cần tham chiếu API mạng trực tiếp?

Để liên kết điều này với các trường hợp sử dụng trong thế giới thực, điều quan trọng là phải hỏi tại sao chúng ta lại muốn kiểm tra và nắm bắt thông tin phản hồi về các tài nguyên này? Sau đây là một số lý do:
  • Chúng ta có thể muốn chủ động nắm bắt các chi tiết như mã trạng thái cho các tài nguyên của mình để có thể phân loại mọi thay đổi.
    Ví dụ: hình ảnh bị thiếu (404) có thể là các vấn đề và loại công việc hoàn toàn khác so với việc xử lý hình ảnh trả về lỗi máy chủ (500).
  • Thêm giám sát vào các phần trong ngăn xếp của chúng ta mà chúng ta không kiểm soát.
    Thông thường, các nhóm sẽ chuyển các loại tài sản này sang CDN để lưu trữ và phân phối cho người dùng. Nếu họ gặp sự cố, nhóm sẽ có thể phát hiện sự cố nhanh như thế nào?
  • Các biến thể theo thời gian chạy hoặc theo yêu cầu của tài nguyên đã trở thành các kỹ thuật phổ biến hơn.
    Ví dụ: thay đổi kích thước hình ảnh, tự động điền polyfill các tập lệnh trên CDN, v.v. — các hệ thống này có thể có nhiều giới hạn và lý do khiến chúng không thể tạo hoặc cung cấp biến thể. Nếu bạn mong đợi 100% người dùng truy xuất một loại biến thể tài sản cụ thể, thì việc có thể xác nhận điều đó là rất có giá trị.
    Điều này đã xảy ra tại một công ty trước đây tôi từng làm, nơi mà việc thay đổi kích thước hình ảnh theo yêu cầu được sử dụng cho hình ảnh thu nhỏ. Do những hạn chế của nhà cung cấp, một số lượng lớn người dùng sẽ có trải nghiệm tệ hơn do hình ảnh kích thước đầy đủ tải ở nơi mà hình thu nhỏ được cho là sẽ xuất hiện. Vì vậy, khi chúng tôi nghĩ rằng >99% người dùng sẽ nhận được hình ảnh tối ưu, >30% sẽ gặp sự cố về hiệu suất vì hình ảnh không thay đổi kích thước.
Bây giờ chúng ta đã hiểu được một số lý do có thể thúc đẩy chúng ta kiểm tra các tài nguyên này, hãy cùng xem Server-Timing có thể được tận dụng như thế nào để kiểm tra.

HTML hình ảnh:
Mã:
[IMG]/user-rsrc/12345?resize=true&height=80&width=80&format=webp[/IMG]
Tiêu đề phản hồi hình ảnh:
Mã:
Trạng thái: 200…Server-Timing: status_code; dur=200;, resizing; desc="failed"; dur=1200; req_id; desc=”zyx4321”
Kiểm tra thông tin phản hồi hình ảnh:
Mã:
const imgPerfEntry = performance.getEntriesByName('/user-rsrc/12345?resize=true&height=80&width=80&format=webp')[0];// lọc/chụp dữ liệu mục nhập khi cầnconsole.log(imgPerfEntry.serverTiming);// đầu ra:// [// { name: “status_code”, description: undefined, duration: 200 },// { name: “resizing”, description:”failed”, duration: 1200 },// { name: “req_id”, description:”zyx4321”, duration: 0.0 },// ]
Số liệu này rất có giá trị vì mặc dù trả về phản hồi "hài lòng" (200), hình ảnh của chúng tôi không được thay đổi kích thước và có khả năng không được chuyển đổi sang đúng định dạng, v.v. Cùng với các thông tin hiệu suất khác trên mục nhập như thời gian tải xuống, chúng tôi thấy trạng thái được phục vụ là 200 (không kích hoạt trình xử lý onerror của chúng tôi trên phần tử), việc thay đổi kích thước không thành công sau khi dành 1,2 giây để cố gắng thay đổi kích thước và chúng tôi có một request-id mà chúng tôi có thể sử dụng để gỡ lỗi điều này trong các công cụ khác của mình. Bằng cách gửi dữ liệu này đến nhà cung cấp RUM, chúng tôi có thể tổng hợp và chủ động theo dõi tần suất xảy ra của những điều kiện này.

Giải pháp 2: Kiểm tra các tài nguyên trả về trước khi JS chạy​

Mã được sử dụng để theo dõi các tài nguyên (lấy, XHR, hình ảnh, bảng định kiểu, tập lệnh, HTML, v.v.) yêu cầu mã JavaScript phải tổng hợp và sau đó gửi thông tin đến một nơi nào đó. Điều này hầu như luôn có nghĩa là có kỳ vọng rằng mã giám sát sẽ chạy trước các tài nguyên đang được theo dõi. Ví dụ được trình bày trước đó về bản vá lỗi cơ bản được sử dụng để tự động theo dõi các yêu cầu lấy dữ liệu là một ví dụ điển hình về điều này. Mã đó phải chạy trước bất kỳ yêu cầu lấy dữ liệu nào cần được theo dõi. Tuy nhiên, có rất nhiều trường hợp, từ hiệu suất đến các hạn chế về mặt kỹ thuật, mà chúng ta có thể không thể hoặc đơn giản là không nên thay đổi thứ tự yêu cầu tài nguyên để dễ theo dõi hơn.

Một kỹ thuật theo dõi rất phổ biến khác là đặt trình lắng nghe sự kiện trên trang để nắm bắt các sự kiện có thể có giá trị theo dõi. Điều này thường ở dạng trình xử lý onload hoặc onerror trên các phần tử hoặc sử dụng addEventListener trừu tượng hơn. Kỹ thuật này yêu cầu JS phải được đặt trước khi sự kiện kích hoạt hoặc trước khi trình lắng nghe được đính kèm. Vì vậy, cách tiếp cận này vẫn mang đặc điểm chỉ theo dõi các sự kiện trong tương lai, sau khi JS theo dõi được chạy, do đó yêu cầu JS phải thực thi trước các tài nguyên cần đo lường.

Ánh xạ điều này vào các trường hợp sử dụng trong thế giới thực, các trang web thương mại điện tử nhấn mạnh rất nhiều vào việc hiển thị nội dung "trên màn hình đầu tiên" rất nhanh — thường trì hoãn JS càng nhiều càng tốt. Tuy nhiên, có thể có những tài nguyên có tác động cần đo lường, chẳng hạn như việc phân phối thành công hình ảnh sản phẩm. Trong những tình huống khác, chúng ta cũng có thể quyết định rằng thư viện giám sát không nên nằm trong đường dẫn quan trọng do trọng lượng trang. Có những tùy chọn nào để kiểm tra các yêu cầu này một cách hồi tố?

Kỹ thuật này giống như Giải pháp số 1! Điều này khả thi vì trình duyệt tự động duy trì bộ đệm của tất cả các Mục nhập hiệu suất (tùy thuộc vào giới hạn kích thước bộ đệm có thể thay đổi). Điều này cho phép chúng ta trì hoãn JS cho đến sau trong chu kỳ tải trang mà không cần phải thêm trình lắng nghe trước tài nguyên.

Thay vì lặp lại ví dụ Giải pháp số 1, hãy xem cả kiểm tra hồi tố và kiểm tra trong tương lai đối với các mục nhập hiệu suất trông như thế nào để chỉ ra sự khác biệt về nơi chúng có thể được tận dụng. Xin lưu ý rằng, trong khi chúng ta đang kiểm tra hình ảnh trong các ví dụ này, chúng ta có thể thực hiện việc này đối với bất kỳ loại tài nguyên nào.

Khi thiết lập ngữ cảnh cho mã này, chúng ta cần đảm bảo rằng hình ảnh sản phẩm của mình được phân phối thành công. Giả sử tất cả hình ảnh trang web đều trả về cấu trúc tiêu đề Server-Timing này. Một số hình ảnh quan trọng của chúng ta có thể xuất hiện trước tập lệnh giám sát của chúng ta và khi người dùng điều hướng, nhiều hình ảnh khác sẽ tiếp tục tải. Chúng ta xử lý cả hai như thế nào?

Tiêu đề phản hồi hình ảnh:
Mã:
Trạng thái: 200…Server-Timing: status_code; dur=200;, resizing; desc="success"; dur=30; req_id; desc="randomId"
Logic giám sát của chúng ta. Chúng tôi mong đợi điều này chạy sau nội dung đường dẫn quan trọng của trang.

Kiểm tra thông tin phản hồi hình ảnh:
Mã:
function monitorImages(perfEntries){ perfEntries.forEach((perfEntry)=>{ // theo dõi các mục nhập hiệu suấtconsole.log(perfEntry.serverTiming);})}const alreadyLoadedImageEntries = performance.getEntriesByType('resource').filter(({ initiatorType })=> initiatorType === 'img');monitorImages( alreadyLoadedImageEntries );const imgObserver = new PerformanceObserver(function(entriesList) {const newlyLoadedImageEntries = entriesList.getEntriesByType('resource').filter(({ initiatorType })=> initiatorType === 'img'); monitorImages( newlyLoadedImageEntries );});imgObserver.observe({entryTypes: ["resource"]});
Mặc dù hoãn tập lệnh giám sát của chúng tôi cho đến khi nó ra khỏi đường dẫn quan trọng, chúng tôi vẫn đang thu thập dữ liệu cho tất cả các hình ảnh đã tải trước tập lệnh của chúng tôi và sẽ tiếp tục giám sát chúng khi người dùng tiếp tục sử dụng trang web.

Giải pháp 3: Kiểm tra Tài liệu HTML​

Giải pháp ví dụ cuối cùng mà chúng tôi sẽ xem xét liên quan đến tài nguyên cuối cùng "trước khi JS có thể chạy" — chính tài liệu HTML. Nếu các giải pháp giám sát của chúng tôi được tải dưới dạng JS thông qua HTML, làm thế nào chúng tôi có thể giám sát việc phân phối tài liệu HTML?

Có một số ưu tiên trong việc giám sát việc phân phối tài liệu HTML. Để giám sát dữ liệu phản hồi, thiết lập phổ biến nhất là sử dụng nhật ký máy chủ/số liệu/dấu vết để thu thập thông tin này. Đó là một giải pháp tốt nhưng tùy thuộc vào công cụ, dữ liệu có thể bị tách khỏi dữ liệu RUM khiến chúng tôi cần nhiều công cụ để kiểm tra trải nghiệm người dùng. Ngoài ra, phương pháp này cũng có thể bỏ lỡ siêu dữ liệu (ví dụ: mã định danh phiên bản trang) cho phép chúng ta tổng hợp và liên kết thông tin cho một lần tải trang nhất định — ví dụ: liên kết các yêu cầu bất đồng bộ không thành công khi tài liệu trả về một số mã phản hồi tài liệu nhất định.

Một mô hình phổ biến để thực hiện công việc này là đưa nội dung vào bên trong chính nội dung HTML. Điều này phải được đưa vào nội dung HTML, vì logic giám sát dựa trên JS không có quyền truy cập vào các tiêu đề yêu cầu HTML đã có trước nó. Điều này biến tài liệu HTML của chúng ta thành nội dung tài liệu động. Điều này có thể phù hợp với nhu cầu của chúng ta và cho phép chúng ta lấy thông tin đó và cung cấp cho công cụ RUM của mình. Tuy nhiên, điều này có thể trở thành một thách thức nếu hệ thống phân phối HTML của chúng ta nằm ngoài tầm kiểm soát của chúng ta hoặc nếu hệ thống có một số giả định về cách phân phối HTML phải hoạt động. Ví dụ về điều này có thể là, mong đợi rằng HTML hoàn toàn tĩnh, sao cho chúng ta có thể lưu trữ nó theo cách xác định nào đó — các nội dung HTML "một phần động" có nhiều khả năng bị logic lưu trữ xử lý không chính xác.

Trong quy trình phân phối HTML, cũng có thể có dữ liệu bổ sung mà chúng ta muốn hiểu, chẳng hạn như trung tâm dữ liệu nào đã xử lý yêu cầu trong toàn bộ chuỗi. Chúng ta có thể có trình xử lý biên CDN ủy quyền yêu cầu từ một nguồn gốc. Trong trường hợp này, chúng ta không thể mong đợi mỗi lớp có thể/nên xử lý và đưa nội dung HTML vào. Tiêu đề Server-Timing có thể giúp chúng ta như thế nào ở đây?

Dựa trên các khái niệm của Giải pháp số 1 và Giải pháp số 2, sau đây là cách chúng ta có thể thu thập dữ liệu có giá trị về chính tài liệu HTML. Hãy nhớ rằng bất kỳ phần nào của ngăn xếp cũng có thể thêm tiêu đề Server-Timing vào phản hồi và tiêu đề này sẽ được nối với nhau trong giá trị tiêu đề cuối cùng.

Giả sử chúng ta có trình xử lý cạnh CDN và nguồn gốc có thể xử lý tài liệu:

Tiêu đề phản hồi được CDN thêm vào:
Mã:
Trạng thái: 200…Server-Timing: cdn_status_code; dur=200;, cdn_cache; desc=”expired”; dur=15; cdn_datacenter; desc=”ATL”; cdn_req_id; desc=”zyx321abc789”; cdn_time; dur=120;
Tiêu đề phản hồi gốc đã thêm:
Mã:
Trạng thái: 200…Server-Timing: origin_status_code; dur=200;, origin_time; dur=30; origin_region; desc=”us-west”; origin_req_id; desc="qwerty321ytrewq789";
Kiểm tra thông tin phản hồi HTML:
Mã:
// như đã đề cập trước đó, tài liệu HTML là loại 'điều hướng' của Performance Entry// có siêu tập thông tin liên quan đến tài nguyên và thông tin cụ thể về điều hướngconst htmlPerfEntry = performance.getEntriesByType('navigation')[0];// lọc/chụp dữ liệu nhập khi cầnconsole.log(htmlPerfEntry.serverTiming);// đầu ra:// [// { name: “cdn_status_code”, mô tả: không xác định, thời lượng: 200 },// { name: “cdn_cache”, mô tả:”hết hạn”, thời lượng: 0.0},// { name: “cdn_datacenter”, mô tả:”ATL”, thời lượng: 0.0 },// { name: “cdn_req_id”, mô tả:”zyx321abc789”, thời lượng: 0.0 },// { name: “cdn_time”, mô tả: không xác định, thời lượng: 120 },// { name: “origin_status_code”, mô tả: không xác định, thời lượng: 200 },// { name: “origin_time”, mô tả: không xác định, thời lượng: 30 },// { name: “origin_region”, mô tả:”us-west”, thời lượng: 0.0 },// { name: “origin_req_id”, description:”qwerty321ytrewq789”, duration: 0.0 },// ]
Từ thông tin này, JavaScript giám sát của chúng tôi (có thể được tải sau đó) có thể tổng hợp nơi xử lý HTML diễn ra, mã trạng thái từ các máy chủ khác nhau (có thể khác nhau vì lý do chính đáng — hoặc lỗi) và yêu cầu mã định danh nếu chúng cần liên kết điều này với nhật ký máy chủ. Nó cũng biết được thời gian đã mất trên "máy chủ" thông qua khoảng thời gian cdn_time — thời gian "máy chủ" là tổng thời gian bắt đầu từ proxy/máy chủ không phải người dùng đầu tiên mà chúng tôi cung cấp. Sử dụng khoảng thời gian cdn_time đó, giá trị HTML Time-To-First-Byte đã có thể truy cập và khoảng thời gian origin_time, chúng ta có thể xác định các phần độ trễ chính xác hơn, chẳng hạn như độ trễ của người dùng, độ trễ từ cdn đến gốc, v.v. Điều này cực kỳ hiệu quả trong việc tối ưu hóa điểm phân phối quan trọng như vậy và bảo vệ điểm đó khỏi sự hồi quy.

Kết hợp Server-Timing với Service Workers​

Service Workers là các tập lệnh được trang web khởi tạo để nằm giữa trang web, trình duyệt và mạng (khi khả dụng). Khi hoạt động như một proxy, chúng có thể được sử dụng để đọc và sửa đổi các yêu cầu đến từ và các phản hồi trả về trang web. Vì service worker có rất nhiều tính năng, chúng tôi sẽ không cố gắng trình bày chi tiết về chúng trong bài viết này — một tìm kiếm đơn giản trên web sẽ cho ra rất nhiều thông tin về khả năng của chúng. Trong bài viết này, chúng tôi sẽ tập trung vào khả năng proxy của service worker — khả năng xử lý yêu cầu/phản hồi.

Chìa khóa để kết hợp các công cụ này là biết rằng tiêu đề Server-TimingPerformanceEntry tương ứng của nó được tính toán sau khi proxy service worker diễn ra. Điều này cho phép chúng tôi sử dụng service worker để thêm tiêu đề Server-Timing vào các phản hồi có thể cung cấp thông tin có giá trị về chính yêu cầu.

Chúng ta có thể muốn nắm bắt loại thông tin nào trong service worker? Như đã đề cập trước đó, service worker có rất nhiều khả năng và bất kỳ hành động nào trong số đó cũng có thể tạo ra thứ gì đó có giá trị để nắm bắt. Sau đây là một số điều tôi nghĩ đến:
  • Yêu cầu này có được phục vụ từ bộ đệm của service worker không?
  • Yêu cầu này có được phục vụ từ service worker khi ngoại tuyến không?
  • Chiến lược service worker nào cho loại yêu cầu này đang được sử dụng?
  • Phiên bản service worker nào đang được sử dụng?
    Điều này hữu ích trong việc kiểm tra các giả định của chúng tôi về việc vô hiệu hóa service worker.
  • Lấy các giá trị từ các tiêu đề khác và đưa chúng vào tiêu đề Server-Timing để tổng hợp hạ lưu.
    Có giá trị khi chúng tôi không có tùy chọn thay đổi tiêu đề trên yêu cầu nhưng muốn kiểm tra chúng trong RUM — trường hợp này thường xảy ra với các nhà cung cấp CDN.
  • Một tài nguyên đã nằm trong bộ đệm của service worker trong bao lâu?
Service worker phải được khởi tạo trên trang web, đây là một quy trình không đồng bộ. Hơn nữa, service worker chỉ xử lý các yêu cầu trong phạm vi được xác định. Do đó, ngay cả câu hỏi cơ bản là "yêu cầu này có được service worker xử lý không?" có thể thúc đẩy các cuộc trò chuyện thú vị về mức độ chúng ta dựa vào khả năng của nó để mang lại những trải nghiệm tuyệt vời.

Hãy cùng tìm hiểu xem điều này có thể trông như thế nào trong mã.

Logic JS cơ bản được sử dụng trên trang web để khởi tạo trình làm việc dịch vụ:
Mã:
if ('serviceWorker' in navigator) {navigator.serviceWorker.register('/service-worker.js').then(function (registration) {registration.update(); // ngay lập tức bắt đầu sử dụng phần mềm này });}
Bên trong /service-worker.js, proxy yêu cầu/phản hồi cơ bản:
Mã:
const TÊN_CACHE = 'sw-cached-files-v1';self.addEventListener('fetch', function (event) { event.respondWith( // kiểm tra xem yêu cầu này có được lưu trong bộ nhớ đệm caches.match(event.request) .then(function (response) { // Cache hit - trả về phản hồi if (response) { const updatedHeaders = new Headers(response.headers); updatedHeaders.append('Server-Timing', 'sw_cache; desc="hit";'); const updatedResponse = new Response(response.body, { ...response, headers: updatedHeaders }); return updatedResponse; } return fetch(event.request).then(function (response) { // tùy thuộc vào phạm vi mà chúng ta tải service worker, // chúng ta có thể cần lọc các phản hồi của mình để chỉ xử lý // các yêu cầu/phản hồi của bên thứ nhất // Khớp biểu thức chính quy trên tên máy chủ event.request.url phải là const updatedHeaders = new Headers(response.headers); updatedHeaders.append('Server-Timing', `status_code;desc=${response.status};, sw_cache; desc="miss";`) const modifiableResponse = new Response(response.body, { ...response, headers: updatedHeaders }); // chỉ lưu trữ bộ nhớ đệm các phản hồi trạng thái tốt đã biết if (!response || response.status !== 200 || response.type !== 'basic' || response.headers.get('Content-Type').includes('text/html')) { return modifiableResponse; } const responseToCache = modifiableResponse.clone(); caches.open(CACHE_NAME).then(function (cache) { cache.put(event.request, responseToCache); }); return modifiableResponse; } ); }) );});
Các yêu cầu được xử lý từ service worker, giờ đây sẽ có tiêu đề Server-Timing được thêm vào phản hồi của chúng. Điều này cho phép chúng tôi kiểm tra dữ liệu đó thông qua API Performance Timeline, như chúng tôi đã chứng minh trong tất cả các ví dụ trước đây. Trong thực tế, chúng tôi có thể đã không thêm service worker cho nhu cầu duy nhất này — nghĩa là chúng tôi đã có công cụ để xử lý các yêu cầu. Việc thêm một tiêu đề ở 2 nơi cho phép chúng tôi đo mã trạng thái cho tất cả các yêu cầu, tỷ lệ truy cập bộ đệm dựa trên service worker và tần suất service worker xử lý các yêu cầu.

Tại sao nên sử dụng Server-Timing nếu chúng ta có Service Workers?​

Đây là một câu hỏi quan trọng nảy sinh khi thảo luận về việc kết hợp các kỹ thuật này. Nếu một service worker có thể lấy tất cả thông tin tiêu đề và nội dung, tại sao chúng ta lại cần một công cụ khác để tổng hợp chúng?

Công việc đo thời gian và siêu dữ liệu tùy ý khác về các yêu cầu hầu như luôn luôn là để chúng ta có thể gửi thông tin này đến nhà cung cấp RUM để phân tích, cảnh báo, v.v. Tất cả các máy khách RUM chính đều có 1 hoặc 2 cửa sổ mà chúng ta có thể làm giàu dữ liệu về một yêu cầu — khi phản hồi xảy ra và khi PerformanceEntry được phát hiện. Ví dụ: nếu chúng ta thực hiện một yêu cầu tìm nạp, máy khách RUM sẽ nắm bắt các chi tiết yêu cầu/phản hồi và gửi nó. Nếu phát hiện thấy PerformanceEntry, máy khách cũng sẽ gửi thông tin đó — cố gắng liên kết thông tin đó với yêu cầu trước đó nếu có thể. Nếu máy khách RUM cung cấp khả năng thêm thông tin về các yêu cầu/mục nhập đó, thì đó là cửa sổ duy nhất có thể thực hiện việc đó.

Trong thực tế, một dịch vụ có thể đã được kích hoạt hoặc chưa, một yêu cầu/phản hồi có thể đã xử lý hoặc chưa, và tất cả việc chia sẻ dữ liệu của dịch vụ đều yêu cầu nhắn tin không đồng bộ đến trang web thông qua API postMessage(). Tất cả các khía cạnh này đều đưa ra các điều kiện chạy đua để một service worker có thể hoạt động, có thể thu thập dữ liệu và sau đó gửi dữ liệu đó kịp thời để được làm giàu bởi máy khách RUM.

Trái ngược với Server-Timing, một máy khách RUM xử lý API Dòng thời gian hiệu suất sẽ ngay lập tức có quyền truy cập vào bất kỳ tập dữ liệu Server-Timing nào trên PerformanceEntry.

Với đánh giá này về những thách thức của service worker trong việc làm giàu dữ liệu yêu cầu/phản hồi một cách đáng tin cậy, tôi khuyến nghị nên sử dụng service worker để cung cấp thêm dữ liệu và ngữ cảnh thay vì là cơ chế độc quyền để phân phối dữ liệu cho máy khách RUM trên luồng chính. Nghĩa là, hãy sử dụng Server-Timing và khi cần, hãy sử dụng service worker để thêm ngữ cảnh hoặc trong trường hợp Server-Timing không được hỗ trợ — nếu cần. Trong trường hợp này, chúng ta có thể tạo các sự kiện/số liệu tùy chỉnh thay vì làm giàu dữ liệu tổng hợp yêu cầu/phản hồi ban đầu, vì chúng ta sẽ cho rằng các điều kiện chạy đua được đề cập sẽ dẫn đến việc bỏ lỡ các cửa sổ để làm giàu máy khách RUM nói chung.

Những cân nhắc khi sử dụng Server-Timing

Mặc dù mạnh mẽ một cách độc đáo, nhưng nó không phải là không có những cân nhắc quan trọng. Sau đây là danh sách các cân nhắc dựa trên triển khai hiện tại tại thời điểm viết bài:
  • Hỗ trợ trình duyệt — Safari không hỗ trợ việc đưa dữ liệu Server-Timing vào API Dòng thời gian hiệu suất (họ hiển thị dữ liệu này trong DevTools).
    Tuy nhiên, đây là điều đáng tiếc vì đây không phải là vấn đề về chức năng dành cho người dùng mà là về khả năng cải thiện để giám sát hiệu suất — Tôi cho rằng đây không phải là vấn đề gây cản trở. Với giám sát dựa trên trình duyệt, chúng tôi không bao giờ mong đợi có thể đo lường 100% trình duyệt/người dùng. Hiện tại, điều này có nghĩa là chúng tôi sẽ tìm cách nhận được ~70-75% hỗ trợ dựa trên dữ liệu sử dụng trình duyệt toàn cầu. Con số này thường là quá đủ để chúng tôi tự tin rằng các số liệu của mình đang cho chúng tôi thấy những tín hiệu tốt về tình trạng và hiệu suất của hệ thống. Như đã đề cập, Server-Timing đôi khi là cách duy nhất để có được các số liệu này một cách đáng tin cậy, vì vậy chúng tôi nên tự tin tận dụng công cụ này.
    Như đã đề cập trước đó, nếu chúng tôi nhất thiết phải có dữ liệu này cho Safari, chúng tôi có thể khám phá việc sử dụng giải pháp dựa trên cookie cho người dùng Safari. Bất kỳ giải pháp nào ở đây cũng phải được thử nghiệm kỹ lưỡng để đảm bảo chúng không cản trở hiệu suất.
  • Nếu chúng ta muốn cải thiện hiệu suất, chúng ta muốn tránh thêm nhiều trọng số vào phản hồi của mình, bao gồm cả tiêu đề. Đây là sự đánh đổi giữa trọng số bổ sung cho siêu dữ liệu có giá trị gia tăng. Tôi khuyên bạn rằng nếu tiêu đề Server-Timing của bạn không nằm trong phạm vi 500 byte trở lên, tôi sẽ không lo lắng. Nếu bạn lo lắng, hãy thử thay đổi độ dài và đo lường tác động của nó!
  • Khi thêm nhiều tiêu đề Server-Timing vào một phản hồi duy nhất, có nguy cơ trùng lặp tên số liệu Server-Timing. Trình duyệt sẽ hiển thị tất cả các tên đó trong mảng serverTiming trên PerformanceEntry. Tốt nhất là đảm bảo tránh điều này bằng cách đặt tên cụ thể hoặc theo không gian tên. Nếu không thể tránh được, thì chúng ta sẽ phá vỡ thứ tự các sự kiện đã thêm từng tiêu đề và xác định một quy ước mà chúng ta có thể tin cậy. Nếu không, chúng ta có thể tạo một tiện ích không thêm các mục Server-Timing một cách mù quáng nhưng cũng sẽ cập nhật các mục hiện có nếu chúng đã có trong Phản hồi.
  • Cố gắng tránh lỗi nhớ nhầm rằng các phản hồi cũng lưu vào bộ đệm các giá trị Server-Timing. Trong một số trường hợp, bạn có thể muốn lọc ra dữ liệu liên quan đến thời gian của các phản hồi được lưu trong bộ đệm mà trước khi được lưu vào bộ đệm, đã dành thời gian trên máy chủ. Có nhiều cách khác nhau để phát hiện xem yêu cầu có được gửi đến mạng với dữ liệu trên PerformanceEntry hay không, chẳng hạn như entry.transferSize > 0 hoặc entry.decodedBodySize > 0 hoặc entry.duration > 40. Chúng ta cũng có thể dựa vào những gì chúng ta đã học được với Server-Timing để đặt dấu thời gian trên tiêu đề để so sánh.

Kết thúc​

Chúng ta đã đi khá sâu vào ứng dụng của Tiêu đề Server-Timing cho các trường hợp sử dụng không phù hợp với trường hợp sử dụng "thời gian" mà tiêu đề này thường liên kết với. Chúng ta đã thấy sức mạnh của nó trong việc thêm dữ liệu dạng tự do về một tài nguyên và truy cập dữ liệu mà không cần tham chiếu đến API mạng được sử dụng để tạo ra dữ liệu đó. Đây là một khả năng rất độc đáo mà chúng ta đã tận dụng để đo lường các loại tài nguyên, kiểm tra chúng một cách hồi tố và thậm chí thu thập dữ liệu về chính tài liệu HTML. Kết hợp kỹ thuật này với service worker, chúng ta có thể thêm nhiều thông tin hơn từ chính service worker hoặc để ánh xạ thông tin phản hồi từ các phản hồi máy chủ không được kiểm soát vào Server-Timing để dễ dàng truy cập.

Tôi tin rằng Server-Timing độc đáo đến mức đáng kinh ngạc đến mức nó nên được sử dụng nhiều hơn, nhưng tôi cũng tin rằng nó không nên được sử dụng cho mọi thứ. Trước đây, đây là một công cụ bắt buộc phải có cho các dự án đo lường hiệu suất mà tôi đã làm việc để cung cấp dữ liệu tài nguyên không thể truy cập và xác định nơi xảy ra độ trễ. Nếu bạn không nhận được giá trị từ việc có dữ liệu trong tiêu đề này hoặc nếu nó không phù hợp với nhu cầu của bạn — thì không có lý do gì để sử dụng nó. Mục tiêu của bài viết này là cung cấp cho bạn góc nhìn mới về Server-Timing như một công cụ để sử dụng, ngay cả khi bạn không đo thời gian.

Tài nguyên​

Đọc thêm​

  • Tạo biểu mẫu nhiều bước hiệu quả để cải thiện trải nghiệm người dùng
  • Mở rộng quy mô thành công: Những hiểu biết sâu sắc chính và bài học thực tế
  • Tạo phân tích tình cảm âm thanh thời gian thực bằng AI
  • GraphQL đầy đủ với Next.js, Neo4j AuraDB và Vercel
 
Back
Bên trên