開始之前,我們先看下各個瀏覽器公佈的資源併發數限制個數,如下圖

這裡寫圖片描述

瀏覽器的併發請求數目限制是針對同一域名的。
意即,同一時間針對同一域名下的請求有一定數量限制。超過限制數目的請求會被阻塞,這就是很多網站專門解決這個問題的原因。

有的請求會持續很長時間,如果把 img, css, js… 都放到http://一個域名下面,其他請求就遲遲無法完成,瀏覽者看來就是『卡住了』。而把圖片放到另一個域名之後,css和圖片就可以併發請求了。

使用併發的原因

首先,是基於埠數量和執行緒切換開銷的考慮,瀏覽器不可能無限量的併發請求,因此衍生出來了併發限制和HTTP/1.1的Keep alive。 所以,IE6/7在HTTP/1.1下的併發才2,但HTTP/1.0卻是4。 而隨著技術的發展,負載均衡和各類NoSQL的大量應用,基本已經足以應對C10K的問題。 但卻並不是每個網站都懂得利用domain hash也就是多域名來加速訪問。因此,新的瀏覽器加大了併發數的限制,但卻仍控制在8以內。

補充一小點就是瀏覽器即使放棄保護自己,將所有請求一起發給伺服器,也很可能會引發伺服器的併發閾值控制而被BAN,而另外一個控制在8以內的原因也是keep alive技術的存在使得瀏覽器複用現有連線和伺服器通訊比建立新連線的效能要更好一些。

所以,瀏覽器的併發數其實並不僅僅只是良知的要求,而是雙方都需要保護自己的默契,並在可靠的情況下提供更好的效能。

=========================================================================

前端技術的逐漸成熟,還衍生了domain hash, cookie free, css sprites, js/css combine, max expires time, loading images on demand等等技術。這些技術的出現和大量使用都和併發資源數有關。

按照普通設計,當網站cookie資訊有1 KB、網站首頁共150個資源時,使用者在請求過程中需要傳送150 KB的cookie資訊,在512 Kbps的常見上行頻寬下,需要長達3秒左右才能全部發送完畢。 儘管這個過程可以和頁面下載不同資源的時間併發,但畢竟對速度造成了影響。 而且這些資訊在js/css/images/flash等靜態資源上,幾乎是沒有任何必要的。 解決方案是啟用和主站不同的域名來放置靜態資源,也就是cookie free。

將css放置在頁面最上方應該是很自然的習慣,但第一個css內引入的圖片下載是有可能堵塞後續的其他js的下載的。而在目前普遍過百的整頁請求數的前提下,瀏覽器提供的僅僅數個併發,對於進行了良好優化甚至是前面有CDN的系統而言,是極大的效能瓶頸。 這也就衍生了domain hash技術來使用多個域名加大併發量(因為瀏覽器是基於domain的併發控制,而不是page),不過過多的散佈會導致DNS解析上付出額外的代價,所以一般也是控制在2-4之間。 這裡常見的一個性能小坑是沒有機制去確保URL的雜湊一致性(即同一個靜態資源應該被雜湊到同一個域名下),而導致資源被多次下載。

再怎麼提速,頁面上過百的總資源數也仍然是很可觀的,如果能將其中一些很多頁面都用到的元素如常用元素如按鈕、導航、Tab等的背景圖,指示圖示等等合併為一張大圖,並利用css background的定位來使多個樣式引用同一張圖片,那也就可以大大的減少總請求數了,這就是css sprites的由來。

全站的js/css原本並不多,其合併技術的產生卻是有著和圖片不同的考慮。 由於cs/js通常可能對dom佈局甚至是內容造成影響,在瀏覽器解析上,不連貫的載入是會造成多次重新渲染的。因此,在網站變大需要保持模組化來提高可維護性的前提下,js/css combine也就自然衍生了,同時也是minify、compress等對內容進行多餘空格、空行、註釋的整理和壓縮的技術出現的原因。

隨著cookie free和domain hash的引入,網站整體的開啟速度將會大大的上一個臺階。 這時我們通常看到的問題是大量的請求由於全站公有header/footer/nav等關係,其對應檔案早已在本地快取裡存在了,但為了確保這個內容沒有發生修改,瀏覽器還是需要請求一次伺服器,拿到一個304 Not Modified才能放心。 一些比較大型的網站在建立了比較規範的釋出制度後,會將大部分靜態資源的有效期設定為最長,也就是Cache-Control max-age為10年。 這樣設定後,瀏覽器就再也不會在有快取的前提下去確認檔案是否有修改了。  超長的有效期可以讓使用者在訪問曾訪問過的網站或網頁時,獲得最佳的體驗。 帶來的複雜性則體現在每次對靜態資源進行更新時,必須釋出為不同的URL來確保使用者重新載入變動的資源。

即使是這樣做完,仍然還存在著一個很大的優化空間,那就是很多頁面瀏覽量很大,但其實使用者直接很大比例直接就跳走了,第一屏以下的內容使用者根本就不感興趣。 對於超大流量的網站如淘寶、新浪等,這個問題尤其重要。   這個時候一般是通過將圖片的src標籤設定為一個loading或空白的樣式,在使用者翻頁將圖片放入可見區或即將放入可見區時再去載入。 不過這個優化其實和併發資源數的關係就比較小了,只是對一些散佈不合理,或第一頁底部的資源會有一定的幫助。 主要意圖還是降低頻寬費用。

總的來說,各類技術都是為了能讓使用者更快的看到頁面進行下一步操作,但卻不必將寶貴的資源浪費在沒有必要的重複請求、不看的內容上。

瀏覽器限制併發的原因

由於 TCP 協議的限制,PC 端只有65536個埠可用以向外部發出連線,而作業系統對半開連線數也有限制以保護作業系統的 TCP\IP 協議棧資源不被迅速耗盡,因此瀏覽器不好發出太多的 TCP 連線,而是採取用完了之後再重複利用 TCP 連線或者乾脆重新建立 TCP 連線的方法。

如果採用阻塞的套接字模型來建立連線,同時發出多個連線會導致瀏覽器不得不多開幾個執行緒,而執行緒有時候算不得是輕量級資源,畢竟做一次上下文切換開銷不小。

這是瀏覽器作為一個有良知的客戶端在保護伺服器。就像乙太網的衝突檢測機制,客戶端在使用公共資源的時候必須要自行決定一個等待期。當超過2個客戶端要使用公共資源時,強勢的那個邪惡的客戶端可能會導致弱勢的客戶端完全無法訪問公共資源。從前迅雷被噴就是因為它不是一個有良知的客戶端,它作為 HTTP 協議客戶端沒有考慮到伺服器的壓力,作為 BT 客戶端沒有考慮到自己回饋上傳量的義務。

小貼士

半開連線指的是 TCP 連線的一種狀態,當客戶端向伺服器端發出一個 TCP 連線請求,在客戶端還沒收到伺服器端的迴應併發回一個確認的資料包時,這個 TCP 連線就是一個半開連線。

若伺服器到超時以後仍無響應,那麼這個 TCP 連線就等於白費了,所以作業系統會本能的保護自己,限制 TCP 半開連線的總個數,以免有限的核心態記憶體空間被維護 TCP 連線所需的記憶體所浪費。