Google Chrome中的高性能網絡

jopen 11年前發布 | 36K 次閱讀 Google Chrome

        Google Chrome 的歷史和指導原則 【譯注】這部分不再詳細翻譯,只列出核心意思。

        驅動 Chrome 繼續前進的核心原則包括:

        Speed: 做最快的(fastest)的瀏覽器. Security:為用戶提供最為安全的(most secure)的上網環境。

        Stability: 提供一個健壯且穩定的(resilient and stable)的 Web 應用平臺。

        Simplicity: 以簡練的用戶體驗(simple user experience)封裝精益求精的技術(sophisticated technology)。

        本文關將注于第一點,速度。

        關于性能的方方面面 一個現代瀏覽器就是一個和操作系統一樣的平臺。在 Chrome 之前的瀏覽器都是單進程的應用,所有頁面共享相同的地址空間和資源。引入多進程架構這是 Chrome 最為著名的改進【譯注:省略一些反復談論的細節】。

Google Chrome中的高性能網絡

        一個進程內,Web 應用主要需要執行三個任務:獲取資源,頁面排版及渲染,和運行 JavaScript。渲染和腳本都是在運行中交替以單線程的方式運行的,其原因是為了保持 DOM 的一致性,而 JavaScript 本身也是一個單線程的語言。所以優化渲染和腳本運行無論對于頁面開發者還是瀏覽器開發者都是極為重要的。

        Chrome 的渲染引擎是 WebKit, JavaScript Engine 則使用深入優論的 V8 (“V8″ JavaScript runtime)。但是,如果網絡不暢,無論優化 V8 的 JavaScript 執行,還是優化 WebKit 的解析和渲染,作用其實很有限。巧婦難為無米之炊,數據沒來就得等著!

        相對于用戶體驗,作用最為明顯的就是如何優化網絡資源的加載順序、優先級及每一個資源的延遲時間(latency)。也許你察覺不 到,Chrome 網絡模塊每天都在進步,逐步降低每個資源的加載成本:向 DNS lookups 學習,記住頁面拓撲結構(topology of the web), 預先連接可能的目標網址,等等,還有很多。從外面來看就是一個簡單的資源加載的機制,但在內部卻是一個精彩的世界。

        關于 Web 應用 開始正題前,還是先來了解一下現在網頁或者 Web 應用在網絡上的需求。

        HTTP Archive 項目一直在追蹤網頁構建。除了頁面內容外,它還會分析流行頁面使用的資源數量,類型,頭信息以及不同目標地址的元數據(metadata)。下面是 2013 年 1 月的統計資料,由 300,000 目標頁面得出的平均數據:

Google Chrome中的高性能網絡

        1280 KB 包含 88 個資源(Images,JavaScript,CSS …)

        連接 15 個以上的不同主機(distinct hosts)。

        這些數字在過去幾年中一直持續增長( steadily increasing ),沒有停下的跡象。這說明我們正不斷地建構一個更加龐大的、野心勃勃的網絡應用。還要注意,平均來看每個資源不過 12KB, 表明絕大多數的網絡傳輸都是短促(short and bursty)的。這和 TCP 針對大數據、流式(streaming)下載的方向不一致,正因為如此,而引入了一些并發癥。下面就用一個例子來抽絲剝繭,一窺究竟……

        一個 Resource Request 的一生 W3C 的 Navigation Timing specification 定義了一組 API,可以觀察到瀏覽器的每一個請求(request)的時序和性能數據。下面了解一些細節:

Google Chrome中的高性能網絡

        給定一個網頁資源地址后,瀏覽器就會檢查本地緩存和應用緩存。如果之前獲取過并且有相應的緩存信息(appropriate cache headers)(如 Expires, Cache-Control, etc.), 就會用緩存數據填充這個請求,畢竟最快的請求就是沒有請求(the fastest request is a request not made)。否則,我們重新驗證資源,如果已經失效(expired),或者根本就沒見過,一個耗費網絡的請求就無法避免地發送了。

        給定了一個主機名和資源路徑后,Chrome 先是檢查現有已建立的連接(existing open connections)是否可以復用, 即 sockets 指定了以(scheme、host 和 port)定義的連接池(pool)。但如果配置了一個代理,或者指定了 proxy auto-config (PAC)腳本,Chrome 就會檢查與 proxy 的連接。PAC 腳本基于 URL 提供不同的代理,或者為此指定了特定的規則。與每一個代理間都可以有自己的 socket pool。最后,上述情況都不存在,這個請求就會從 DNS 查詢(DNS lookup)開始了,以便獲得它的 IP 地址。

        幸運的話,這個主機名已經被緩存過。否則,必須先發起一個 DNS Query。這個過程所需的時間和 ISP,頁面的知名度,主機名在中間緩存(intermediate caches)的可能性,以及 authoritative servers 的響應時間這些因素有關。也就是說這里變量很多,不過一般還不致于到幾百毫秒那么夸張。

Google Chrome中的高性能網絡

        拿到解析出的 IP 后,Chrome 就會在目標地址間打開一個新 TCP 連接,我們就要執行一個 3 度握手(“three-way handshake”): SYN > SYN-ACK > ACK。這個操作每個新的 TCP 連接都必須完成,沒有捷徑。根據遠近,路由路徑的選擇,這個過程可能要耗時幾百毫秒,甚至幾秒。而到現在,我們連一個有效的字節都還沒收到。

        當 TCP 握手完成了,如果我們連接的是一個 HTTPS 地址,還有一個 SSL 握手過程,同時又要增加最多兩輪的延遲等待。如果 SSL 會話被緩存了,就只需一次。

        最后,Chrome 終于要發送 HTTP 請求了 (如上面圖示中的 requestStart)。 服務器收到請求后,就會傳送響應數據(response data)回到客戶端。這里包含最少的往返延遲和服務的處理時間。然后一個請求就完成了。但是,如果是一個 HTTP 重定向(redirect)的話?我們又要從頭開始這個過程。如果你的頁面里有些冗余的重定向,最好三思一下!

        你得出所有的延遲時間了嗎? 我們假設一個典型的寬帶環境:沒有本地緩存,相對較快的 DNS lookup (50ms), TCP 握手,SSL 協商,以及一個較快服務器響應時間(100ms)和一次延遲(80ms,在美國國內的平均值): 50ms for DNS 80ms for TCP handshake (one RTT)

        160ms for SSL handshake (two RTT’s)

        40ms (發送請求到服務器)

        100ms (服務器處理)

        40ms (服務器回傳響應數據)

        一個請求花了 470 毫秒, 其中 80% 的時間被網絡延遲占去了。看到了吧,我們真得有很多事情要做!事實上,470 毫秒已經很樂觀了:

        如果服務器沒有達到到初始 TCP 的擁塞窗口(congestion window),即4-15KB,就會引入更多的往返延遲。 SSL 延遲也可能變得更糟。如果需要獲取一個沒有的認證(certificate)或者執行 online certificate status check (OCSP), 都會讓我們需要一個新的 TCP 連接,又增加了數百至上千毫秒的延遲。

        怎樣才算”夠快”? 前面可以看到服務器響應時間僅是總延遲時間的 20%,其它都被 DNS,握手等操作占用了。過去用戶體驗研究( user experience research )表明用戶對延遲時間的不同反應:

        延遲及用戶反應 0 – 100ms 迅速 100 – 300ms 有點慢 300 – 1000ms 機器還在運行 1s+ 想想別的事…… 10s+ 我一會再來看看吧…

        上表同樣適用于頁面的性能表現: 渲染頁面,至少要在 250ms 內給個回應來吸引住用戶。這就是簡單地針對速度。從 Google, Amazon, Microsoft,以及其它數千個站點來看,額外的延遲直接影響頁面表現:流暢的頁面會吸引更多的瀏覽、以及更強的用戶吸引力(engagement) 和頁面轉換率(conversion rates).

        現在我們知道了理想的延遲時間是 250ms,而前面的示例告訴我們,DNS Lookup, TCP 和 SSL 握手,以及 request 的準備時間花去了 370ms, 即便不考慮服務器處理時間,我們也超出了 50%。

        對于絕大多數的用戶和網頁開發者來說,DNS, TCP,以及 SSL 延遲都是透明,很少有人會想到它。這也就是為什么 Chrome 的網絡模塊那么的復雜。

        我們已經識別出了問題,下面讓我們深入一下實現的細節…

        深入 Chrome 的網絡模塊 多進程架構

        Chrome 的多進程架構為瀏覽器的網絡請求處理帶來了重要意義,它目前支持四種不同的執行模式( four different execution models ).

        默認情況下,桌面的 Chrome 瀏覽器使用 process- per-site 模式, 將不同的網站頁面隔離起來, 相同網站的頁面組織在一起。舉個簡單的例子: 每個 tab 獨立一個進程。從網絡性能的角度上說,并沒什么本質上的不同,只是 process-per- tabl 模式更易于理解。

Google Chrome中的高性能網絡

        每一個 tab 有一個渲染進程(render process),其中包括了用于解析頁面(interpreting)和排版(layout out)的 WebKit 的排版引擎(layout engine), 即上圖中的 HTML Render。還有 V8 引擎和兩者之間的 DOM Bindings,如果你對這部分很好奇,可以看這里( great introduction to the plumbing )。

        每一個這樣的渲染進程被運行在一個沙箱環境中,只會對用戶的電腦環境做極有限的訪問–包括網絡。而使用這些資源,每一個渲染進程必須和瀏覽內核 進程(browser[kernel] process)溝通,以管理每個渲染進程的安全性和訪問策略(access policies)。

        進程間通訊(IPC)和多進程資源加載 渲染進程和內核進程之間的通訊是通過 IPC 完成的。在 Linux 和 Mac OS 上,使用了一個提供異步命名管道通訊方式的 socketpair ()。每一個渲染進程的消息會被序列化地到一個專用的I/O線程中,然后再由它發到內核進程。在接收端,內核進程提供一個過濾接口(filter interface)用于解析資源相關的 IPC 請求( ResourceMessageFilter ), 這部分就是網絡模塊負責的。

Google Chrome中的高性能網絡

        這樣做其中一個好處是所有的資源請求都由I/O進程處理,無論是 UI 產生的活動,或者網絡事件觸發的交互。在內核進程(browser/kernel process)的I/O線程解析資源請求消息,將轉發到一個 ResourceDispatcherHost 的單例(singleton)對象中處理。

        這個單例接口允許瀏覽器控制每個渲染進程對網絡的訪問,也能達到有效和一致的資源共享:

        Socket pool 和 connection limits: 瀏覽器可以限定每一個 profile 打開 256 個 sockets, 每個 proxy 打開 32 個 sockets, 而每一組{scheme, host, port}可以打開 6 個。注意同時針對一組{host,port}最多允計打開 6 個 HTTP 和 6 個 HTTPS 連接。 Socket reuse: 在 Socket Pool 中提供持久可用的 TCP connections,以供復用。這樣可以為新的連接避免額外建立 DNS、TCP 和 SSL (如果需要的話)所花費的時間。

        Socket late-binding (延遲綁定): 網絡請求總是當 Scoket 準備好發送數據時才與一個 TCP 連接關連起來,所以首先有機會做到對請求有效分級(prioritization),比如,在 socket 連接過程中可能會到達到一個更高優先級的請求。同時也可以有更好的吞吐率(throughput),比如,在連接打開過程中,去復用一個剛好可用的 socket, 就可以使用到一個完全可用的 TCP 連接。其實傳統的 TCP pre-connect (預連接)及其它大量的優化方法也是這個效果。

        Consistent session state (一致的會話狀態): 授權、cookies 及緩存數據會在所有渲染進程間共享。

        Global resource and network optimizations (全局資源和網絡優化): 瀏覽器能夠在所有渲染進程和未處理的請求間做更優的決策。比如給當前 tab 對應的請求以更好的優先級。

        Predictive optimizations (預測優化): 通過監控網絡活動,Chrome 會建立并持續改善預測模型來提升性能。

        … 項目還在增加中.

        單就一個渲染進程而言, 透過 IPC 發送資源請求很容易,只要告訴瀏覽器內核進程一個唯一 ID, 后面就交給內核進程處理了。

        跨平臺的資源加載 跨平臺也是 Chrome 網絡模塊的一個主要考量,包括 Linux, Windows, OS X, Chrome OS, Android, 和 iOS。 為此,網絡模塊盡量實現成了單進程模式(只分出了獨立的 cache 和 proxy 進程)的跨平臺函數庫, 這樣就可以在平臺間共用基礎組件(infrastructure)并分享相同的性能優化,更有機會做到同時為所有平臺進行優化。

        相關的代碼可以在這里找到 the “src/net” subdirectory)。本文不會詳細展開每個組件,不過了解一下代碼結構可以幫助我們理解它的能力結構。 比如:

        net/android 綁定到 Android 運行時(runtime) [譯注(Horky):運行時真是一個很爛的術語,翻和沒翻一樣。] net/base 公共的網絡工具函數。比如,主機解析, cookies, 網絡轉換偵測(network change detection),以及 SSL 認證管理 net/cookies 實現了 Cookie 的存儲、管理及獲取 net/disk_cache 磁盤和內存緩存的實現 net/dns 實現了一個異步的 DNS 解析器(DNS resolver) net/http 實現了 HTTP 協議 net/proxy 代理(SOCKS 和 HTTP)配置、解析(resolution) 、腳本抓取(script fetching), … net/socket TCP sockets,SSL streams 和 socket pools 的跨平臺實現 net/spdy 實現了 SPDY 協議 net/url_request URLRequest, URLRequestContext 和 URLRequestJob 的實現 net/websockets 實現了 WebSockets 協議上面每一項都值得好好讀讀,代碼組織的很好,你還會發現大量的單元測試。

        Mobile 平臺上的架構和性能 移動瀏覽器正在大發展,Chrome 團隊也視優化移動端的體驗為最高優先級。先要說明的是移動版的 Chrome 的并不是其桌面版本的直接移植,因為那樣根本不會帶來好的用戶體驗。移動端的先天特性就決定了它是一個資源嚴重受限的環境,在運行參數有一些基本的不同:

        桌面用戶使用鼠標操作,可以有重疊的窗口,大的屏幕,也不用擔心電池。網絡也非常穩定,有大量的存儲空間和內存。 移動端的用戶則是觸摸和手勢操作,屏幕小,電池電量有限,通過只能用龜速且昂貴的網絡,存儲空間和內存也是相當受限。

        再者,不但沒有典型的樣板移動設備,反而是有一大批各色硬件的設備。Chrome 要做的,只能是設法兼容這些設備。好在 Chrome 有不同的運行模式(execution models),面對這些問題,游刃有余!

        在 Android 版本上,Chrome 同樣運用了桌面版本的多進程架構- 一個瀏覽器內核進程,以及一個或多個渲染進程。但因為內存的限制,移動版的 Chrome 無法為每一個 tabl 運行一個特定的渲染進程,而是根據內存情況等條件決定一個最佳的渲染進程個數,然后就會在多個 tab 間共享這些渲染進程。

        如果內存實在不足,或其它原因導致 Chrome 無法運行多進程,它就會切到單進程、多線程的模式。比如在 iOS 設備上,因為其沙箱機制的限制,Chrome 只能運行在這種模式下。

        關于網絡性能,首先 Chrome 在 Android 和 iOS 使用的是各其它平臺相同的網絡模塊。這可以做到跨平臺的網絡優化,這也是 Chrome 明顯領先的優勢之一。所不同的是需要經常根據網絡情況和設備能力進行些調整, 包括推測優化(speculative optimization)的優先級、socket 的超時設置和管理邏輯、緩存大小等。

        比如,為了延長電池壽命,移動端的 Chrome 會傾向于延遲關閉空閑的 sockets (lazy closing of idle sockets), 通常是為了減少信號(radio)的使用而在打開新的 socket 時關閉舊的。另外因為預渲染(pre-rendering,稍后會介紹)會使用一定的網絡和處理資源,它通常只在 WiFi 才會使用。

        關于移動瀏覽體驗會獨立一章,也許就在 POSA 系列的下一期。

        Chrome Predictor 的預測功能優化 Chrome 會隨著使用變得更快。 它這個特性是通過一個單例對象 Predictor 來實現的。這個對象在瀏覽器內核進程(Browser Kernel Process)中實例化,它唯一的職責就是觀察和學習當前網絡活動方式,提前預估用戶下一步的操作。下面是一個示例:

        用戶將鼠標停留在一個鏈接上,就預示著一個用戶的偏好以及下一步的瀏覽行為。這時 Chrome 就可以提前進行 DNS Lookup 及 TCP 握手。用戶的點擊操作平均需要將近 200ms,在這個時間就可能處理完 DNS 和 TCP 相關的操作, 也就是省去幾百毫秒的延遲時間。 當在地址欄(Omnibox/URL bar) 觸發高可能性選項時,就同樣會觸發一個 DNS lookup 和 TCP 預連接(pre-connect),甚至在一個不可見的頁簽中進行預渲染(pre-render)!

        我們每個人都一串天天會訪問的網站, Chrome 會研究在這些頁面上的子資源, 并且嘗試進行預解析(pre-resolve), 甚至可能會進行預加載(pre-fetch)以優化瀏覽體驗。

        除了上面三項,還有很多..

        Chrome 會在你使用過程中學習 Web 的拓撲結構,而不單單是你的瀏覽模式。理想的話,它將為你省去數百毫秒的延遲, 更接近于即時頁面加載的狀態. 正是為了這個目標,Chrome 投入了以下的核心優化技術:

        DNS 預解析(pre-resolve) 提前解析主機地址,以減少 DNS 延遲 TCP 預連接(pre-connect) 提前連接到目標服務器,以減少 TCP 握手延遲資源預加載(prefetching) 提前加載頁面的核心資源,以加載頁面顯示頁面預渲染(prerendering)

        提前獲取整個頁面和相關子資源,這樣可以做到及時顯示

        每一個決策都包含著一個或多個的優化, 用來克服大量的限制因素. 不過畢竟都只是預測性的優化策略,如果效果不理想,就會引入多余的處理和網絡傳輸。甚至可能會帶來一些加載時間上的負體驗。

        Chrome 如何處理這些問題呢? Predictor 會盡量收集各種信息,諸如用戶操作,歷史瀏覽數據,以及來自渲染引擎(render)和網絡模塊自身的信息。

        和 Chrome 中負責網絡事務調度的 ResourceDispatcherHost 不同,Predictor 對象會針對用戶和網絡事務創建一組過濾器(filter):

        IPC channel filter 用來監控來自 render 進程的事務。 每個請求上都會加一個 ConnectInterceptor 對象,這樣就可以跟蹤網絡傳輸的模式以及每一個請求的度量數據。

        渲染進程(render process)會在一系列的事件下發送消息到瀏覽器進程(browser process), 這些事件被定義在一個枚舉(ResolutionMotivation)中以便于使用 (url_info.h):

        enum ResolutionMotivation { MOUSE_OVER_MOTIVATED, // 鼠標懸停. OMNIBOX_MOTIVATED, // Omni-box 建議進行解析. STARTUP_LIST_MOTIVATED, // 這是在前 10 個啟動項中的資源. EARLY_LOAD_MOTIVATED, // 有時需要使用 prefetched 來提前建立連接.

        // 下面定義了預加載評估的方式,會由一個 navigation 變量指定. // referring_url_也需要同時指定. STATIC_REFERAL_MOTIVATED, // 外部數據庫(External Database)建議進行解析。 LEARNED_REFERAL_MOTIVATED, // 前一次瀏覽(prior navigation 建議進行解析. SELF_REFERAL_MOTIVATED, // 猜測下一個連接是不是需要進行解析.

        // <略> … }; 通過這些給定的事件,Predictor 的目標就可以評估它成功的可能性, 然后再適時觸發操作。每一項事件都有其成功的機率、優先級以及時間戳,這些可以在內部維護一個用優先級管理的隊列,也是優化的一個手段。最終,對于這個隊 列中發出的每一個請求的成功率,都可以被 Predictor 追蹤到。基于這些數據,Predictor 就可以進一步優化它的決策。

        Chrome 網絡架構小結 Chrome 使用多進程架構,將渲染進程同瀏覽器進程隔離開來。 Chrome 維護著一個資源分發器的實例(a single instance of the resource dispatcher), 它運行在瀏覽器內核進程,并在各個渲染進程間共享。

        網絡層是跨平臺的,大部分情況下以單進程庫存在。

        網絡層使用非阻塞式(no-blocking)操作來管理所有網絡任務。

        共享的網絡層支持有效的資源排序、復用、并為瀏覽器提供在多進程間進行全局優化的能力。

        每一個渲染進程通過 IPC 和資源分發器(resource dispatcher)通訊。

        資源分發器(Resource dispatcher)通過自定義的 IPC Filter 解析資源請求。

        Predictor 在解析資源請求和響應網絡事務中學習,并對后續的網絡請求進行優化。

        Predictor 會根據學習到的網絡事務模式預測性的進行 DNS 解析, TCP 握手,甚至是資源請求,為用戶實際操作時節省數百毫秒的時間。

        了解晦澀的內部細節后,讓我們來看一下用戶可以感受到的優化。一切從全新的 Chrome 開始。

        優化冷啟動(Cold-Boot)體驗 第一次啟動瀏覽器,它當然不可能了解你的使用習慣和喜歡的頁面。但事實上,我們大多數會在瀏覽器的冷啟動后做些類似的事情,比如到電子郵箱查看郵件,加一 些新聞頁面、社交頁面及內部頁面到我的最愛,諸如此類。這些頁面各有不同,但它們仍然具有一些相似性,所以 Predictor 仍然可以對這個過程提速。

        Chrome 記下了用戶在全新啟動瀏覽器時最常用的 10 個域名。當瀏覽器啟動時,Chrome 會提前對這些域名進行 DNS 預解析。你可以在 Chrome 中使用 chrome://dns 查看到這個列表。在打開頁面的最上面的表格中會列出啟動時的備選域名列表。

Google Chrome中的高性能網絡

        通過 Omnibox 優化與用戶的交互

        引入 Omnibox 是 Chrome 的一項創新, 并不是簡單地處理目標的 URL。除了記錄之前訪問過的頁面 URL,它還與搜索引擎的整合,并且支持在歷史記錄中進行全文搜索(比如,直接輸入頁面名稱)。

        當用戶輸入時,Omnibox 自動發起一個行為,要么查找瀏覽記錄中的 URL, 要么進行一次搜索。每一次發起的操作都會被加以評分,以統計它的性能。你可以在 Chrome 輸入 chrome://predictors 來觀察這些數據。

Google Chrome中的高性能網絡

        Chrome 維護著一個歷史記錄,內容包括用戶輸入的前置文字,采用的行為,以命中的資數。 在上面的列表,你可以看到,當輸入g時,有 76% 的機會嘗試打開 Gmail. 如果再補充一個 m (就是 gm), 打開 Gmail 的可能性增加到 99.8%。

        那么網絡模塊會做什么呢?上表中的黃色和綠色對于 ResourceDispatcher 非常重要。如果有一個一般可能性的頁面(黃色), Chrome 就是發起 DNS 預解析。如果有一個高可能性的頁面(綠色),Chrome 還會在 DNS 解析后發起 TCP 預連接。如果這兩項都完成了,用戶仍然繼續錄入,Chrome 就會在一個隱藏的頁簽進行預渲染(pre-render)。

        相對的,如果輸入的前置文字找不到合適的匹配項目,Chrome 會向搜索引擎服務者發起 DNS 預解析和 TCP 預連,以獲取相似的搜索結果。

        平均而言用戶從填寫查詢內容到評估給出的建議需要花費數百毫秒。 此時 Chrome 可以在后臺進行預解析,預連接,甚至進行預渲染。再當用戶準備按下回車鍵時,大量的網絡延遲已經被提前處理掉了。

        優化緩存性能 最快的請求就是沒有請求。 無論何時討論性能,都不能不談緩存。相信你已經為頁面上所有資源的都提供了 Expires, ETag, Last-Modified 和 Cache-Control 這些響應頭信息(response headers)。什么? 還沒有?那你還是先處理好再來看吧!

        Chrome 有兩種不同的內部緩存的實現:一種備份于本地磁盤(local disk),另一種則存儲于內存(in-memory)中。內存模式(in-memory)主要應用于無痕瀏覽模式(Incognito browsing mode),并在關閉窗口清除掉。 兩種方式使用了相同的內部接口(disk_cache::Backend, 和 disk_cache::Entry),大大簡化了系統架構。如果你想實現一個自己的緩存算法,可以很容易地實現進去。

        在內部,磁盤緩存(disk cache)實現了它自己的一組數據結構, 它們被存儲在一個單獨的緩存目錄里。其中有索引文件(在瀏覽器啟動時加載到內存中),數據文件(存儲著實際數據,以及 HTTP 頭以及其它信息)。比較有趣的是,16KB 以下的文件存儲于共同的數據塊文件中(data block-files,即小文件集中存儲于一個大文件中),其它較大的文件才會存儲到自己專屬的文件中。最后,磁盤緩存的淘汰策略是維護一個 LRU,通過比如訪問頻率和資源的使用時間(age)的度量進行管理。

Google Chrome中的高性能網絡

        在 Chrome 開個頁簽,輸入 chrome://net-internals/#httpCache。 如果你要看到實際的 HTTP 數據和緩存的響應處理,可以打開 chrome://cache, 里面會列出所有緩存中可用的資源。打開每一項,還可以看到詳細的數據頭等信息。

        優化 DNS 預連接 前面已經多次提到了 DNS 預解析,在深入實現之前,先匯總一下 DNS 預解析的場景和理由:

        運行在渲染進程中的 WebKit 文檔解析器(document parser), 會為當前頁面上所有的鏈接提供一個主機名(hostname)列表,Chrome 可以選擇是否提前解析。 當用戶要打開頁面時,渲染進程先會觸發一個鼠標懸停(hover)或按鍵(button down)事件。

        Omnibox 可能會針對一個高可能性的建議頁面發起解析請求。

        Chrome Predictor 會基于過往瀏覽記錄和資源請求數據發起主機解析請求。(下面會詳細解釋。)

        頁面本身會顯式地要求 Chrome 對某些主機名稱進行預解析。

        上述各項對于 Chrome 都只是一個線索。 Chrome 并不保證預解析一定會被執行,所有的線索會由 Predictor 進行評估,以決定后續的操作。最壞的情況下,可能無法及時解析主機名,用戶就必須等待一個 DNS 解析時間,然后是 TCP 連接時間,最后是資源加載時間。Predictor 會記下這個場景,在未來決策時相應地加以參考。總之,一定是越用越快。

        之前提過到 Chrome 可以記住每個頁面的拓撲(topology),并可以基于這個信息進行加速。還記得吧,平均每個頁面帶有 88 個資源,分別來自于 30 多個獨立的主機。每打開這個頁面,Chrome 會記下資源中比較常用的主機名,在后續的瀏覽過程中,Chrome 就會發起針對某些主機或者全部主機的 DNS 解析,甚至是 TCP 預連接!

Google Chrome中的高性能網絡

        使用 chrome://dns 就可以觀察到上面的數據(Google+ 頁面), 其中有 6 個子資源對應的主機名,并記錄了 DNS 預解析發生的次數,TCP 預連接發生的次數,以及到每個主機的請求次數。這些數據就可以讓 Chrome Predictor 執行相應的優化決策。

        除了內部事件通知外,頁面設計者可以在頁面中嵌入如下的語句請求瀏覽器進行預解析:

        之所以有這個需求,一個典型的例子是重定向(redirects). Chrome 本身沒辦法判斷出這種模式,通過這種方式則可以讓瀏覽器提前進行解析。

        具體的實現也是因版本而有所差異,總體而言,Chrome 中的 DNS 處理有兩個主要的實現:1.基于歷史數據(historically), 通過調用平臺無關的 getaddrinfo ()系統函數實現。2.代理操作系統的 DNS 處理方法,這種方法正在被 Chrome 自行實現的一套異步 DNS 解析機制(asynchronous DNS resolver)所取代。

        依賴于系統的實現,代碼少而且簡單,但是 getaddrInfo ()是一個阻塞式的系統調用,無法有效地并行多個查詢操作。經驗數據還顯示,并行請求過多甚至會超出路由器的負額。 Chrome 為此設計了一個復雜的機制。對于其中帶有 worker-pool 的預解析, Chrome 只是簡單的發送 getaddrinfo () 調用, 同時阻塞 worker thread 直到收到響應數據。因為系統有 DNS 緩存的原因,針對解析過的主機,解析操作會立即返回。 這個過程簡單,有效。

        但還不夠! getaddrinfo ()隱藏了太多有用的信息,比如 Time-to-live (TTL)時間戳, DNS 緩存的狀態等。于是 Chrome 決定自己實現一套跨平臺的異步 DNS 解析器。

Google Chrome中的高性能網絡

        這個新技術可以支持以下優化:

        更好地控制重轉的時機,有能力并行執行多個查詢操作。 清晰地記錄 TTLs。

        更好地處理 IPv4 和 IPv6 的兼容。

        基于 RTT 和其它事件,針對不同服務器進行錯誤處理(failover)

        Chrome 仍然進行著持續的優化. 通過 chrome://histograms/DNS 可以觀察到 DNS 度量數據:

Google Chrome中的高性能網絡

        上面的柱狀圖展示了 DNS 預解析延遲時間的分布:比如將近 50%(最右側)的查詢在 20ms 內完成。這些數據基于最近的瀏覽操作(采樣 9869 次),用戶可以選擇是否報告這些使用數據,然后這些數據會以匿名的形式交由工程團隊加以分析,這樣就可以了解到功能的性能,以及未來如何進一步調整。周而 復始,不斷優化。

        使用預連接優化了 TCP 連接管理

        已經預解析到了主機名,也有了由 OmniBox 和 Chrome Predictor 提供信號,預示著用戶未來的操作。為什么再進一步連接到目標主機,在用戶真正發起請求前完成 TCP 握手呢?這樣就可省掉了另一個往返的延遲,輕易地就能為用戶節省到上百毫秒。其實,這就是 TCP 預連接的工作。 通過訪問 chrome://dns 就可以看到 TCP 預連接的使用情況。

Google Chrome中的高性能網絡

        首先, Chrome 檢查它的 socket pool 里有沒有目標主機可以復用的 socket, 這些 sockets 會在 socket pool 里保留一段時間,以節省 TCP 握手時間及啟動延時(slow-start penalty)。如果沒有可用的 socket, 就需要發起 TCP 握手,然后放到 socket pool 中。這樣當用戶發起請求時,就可以用這個 socket 立即發起 HTTP 請求。

        打開 chrome://net-internals#sockets 就可以看到當前的 sockets 的狀態:

Google Chrome中的高性能網絡

        你可以看到每一個 socket 的時間軸:連接和代理的時間,每個封包到達的時間,以及其它一些信息。你也可以把這些數據導出,以方便進一步分析或者報告問題。有好的測試數據是優化的基 礎, chrome://net-internals 就是 Chrome 網絡的信息中心。

        使用預加載優化資源加載

        Chrome 支持在頁面的 HTML 標簽中加入的兩個線索來優化資源加載:

        在 rel 中指定的 subresource (子資源)和 prefetch (預加載)非常相似。不同的是,如果一個 link 指定 rel (relation)為 prefetch 后,就是告訴瀏覽器這個資源是稍后的頁面中要用到的。而指定為 subresource 則表示在本頁中就會用到,期望能在使用前加載。兩者不同的語義讓 resource loader 有不同的行為。prefetch 的優先級較低,一般只會在頁面加載完成后才會開始。而 subresource 則會在解析出來時就被嘗試優先執行。

        還要注意,prefetch 是 HTML5 的一部分,Firefox 和 Chrome 都可以支持。但 subresource 還只能用在 Chrome 中。

        應用 Browser Prefreshing 優化資源加載

        不過,并不是所有的 Web 開發者會愿意加入上面所述的 subresource relation, 就算加了,也要等收到主文檔并解析出這些內容才行,這段時間開銷依賴于服務器的響應時間和客戶端與服務器間的延遲時間,甚至要耗去上千毫秒。

        和前面的預解析,預連接一樣,這里還有一個 prefreshing::

        用戶初始化一個目標頁面的請求。 Chrome 查詢 Predictor 之前針對目標頁面的子資源加載,初始化一組 DNS 預解析,TCP 預連接及資源 prefreshing。

        如是在緩存中發現之前記錄的子資源,由從磁盤中加載到內存中。

        如果沒有或者已經過期了,就是發送網絡請求。

Google Chrome中的高性能網絡

        直到在 2013 年初, prefreshing 還是處于早期的討論階段。如果通過數據結果分析,這個功能最終上線了,我們就可以稍晚些時候使用到它了。

        使用預渲染優化頁面瀏覽

        前面討論的每個優化都用來幫助減少用戶發起請求到看到頁面內容的延遲時間。多快才能帶來即時呈現的體驗呢?基于用戶體驗數據,這個時間是 100 毫秒,根本沒給網絡延遲留什么空間。而在 100 毫秒內,又怎樣渲染頁面呢?

        大家可能都有這樣的體驗: 同時開多個頁簽時會明顯快于在一個頁簽中等待。瀏覽器為此提供了一個實現方式:

        這就是 Chrome 的預渲染(prerendering in Chrome)! 相對于只下載一個資源的“prefetch”, “prerender”會讓 Chrome 在一個不可見的頁簽中渲染一個頁面,包含了它所有的子資源。當用戶要瀏覽它時,這個頁簽被切到前臺,做到了即時的體驗。

        可以瀏覽 prerender-test.appspot.com 來體驗一下效果,再通過 chrome://net-internals/#prerender 查看下歷史記錄和預連接頁面的狀態。

Google Chrome中的高性能網絡

        因為預渲染會同時消耗 CPU 和網絡資源,因些一定要在確信預渲染頁面會被使用到情況下才用。Google Search 就在它的搜索結果里加入 prerender, 因為第一個搜索結果很可能就是下一個頁面(也叫作 Google Instant Pages)

        你可以使用預渲染特性,但以下限制項一定要牢記:

  • 所有的進程中最多只能有一個預渲染頁。 HTTPS 和帶有 HTTP 認證的頁面不可以預渲染。
  • 如果請求資源需要發起非冪等(non-idempotent,idempotent request 的意義為發起多次,不會帶來服務的負面響應的請求)的請求(只有 GET 請求)時,預渲染也不可用。
  • 所有的資源的優先級都很低。
  • 頁面渲染進程的使用最低的 CPU 優先級。
  • </ul>

            .如果需要超過 100MB 的內存,將無法使用預渲染。

            .不支持 HTML5 多媒體元素。

            預渲染只能應用于確信安全的頁面。另外 JavaScript 也最好在運行時使用 Page Visibility API 來判斷一下當前頁是否可見(參考 you should be doing anyway) !

    Google Chrome中的高性能網絡

            最后,總之,Chrome 正逐步優化網絡延遲和用戶體驗,讓它隨著用戶的使用越來越快!

    Google Chrome中的高性能網絡

    Ilya Grigorik is a web performance engineer and developer advocate on the Make The Web Fast team at Google, where he spends his days and nights on making the web fast and driving adoption of performance best practices. Follow @igrigorik

    </blockquote>

    來自: tech.uc.cn
     本文由用戶 jopen 自行上傳分享,僅供網友學習交流。所有權歸原作者,若您的權利被侵害,請聯系管理員。
     轉載本站原創文章,請注明出處,并保留原始鏈接、圖片水印。
     本站是一個以用戶分享為主的開源技術平臺,歡迎各類分享!