HTTP 1.x 學習筆記 —— Web 性能權威指南


HTTP 1.0的優化策略非常簡單,就一句話:升級到HTTP 1.1。完了!

改進HTTP的性能是HTTP 1.1工作組的一個重要目標,后來這個版本也引入了大量增強性能的重要特性,其中一些大家比較熟知的有:

  • 持久化連接以支持連接重用;

  • 分塊傳輸編碼以支持流式響應;

  • 請求管道以支持並行請求處理;

  • 字節服務以支持基於范圍的資源請求; 

  • 改進的更好的緩存機制。

當然,這些只是其中一部分,要全面討論HTTP 1.1的所有增強特性,非得用一本書不可。同樣,推薦大家買一本《HTTP權威指南》(David Gourley和Brian Totty合著)放在手邊。另外,提到好的參考書,Steve Souder的《高性能網站建設指南》中概括了14條規則,有一半針對網絡優化:

減少DNS查詢

每次域名解析都需要一次網絡往返,增加請求的延遲,在查詢期間會阻塞請求。

減少HTTP請求

任何請求都不如沒有請求更快,因此要去掉頁面上沒有必要的資源。

使用CDN

從地理上把數據放到接近客戶端的地方,可以顯著減少每次TCP連接的網絡延遲,增加吞吐量。

添加Expires首部並配置ETag標簽

相關資源應該緩存,以避免重復請求每個頁面中相同的資源。Expires首部可用於指定緩存時間,在這個時間內可以直接從緩存取得資源,完全避免HTTP請求。ETag及Last-Modified首部提供了一個與緩存相關的機制,相當於最后一次更新的指紋或時間戳。

Gzip資源

所有文本資源都應該使用Gzip壓縮,然后再在客戶端與服務器間傳輸。一般來說,Gzip可以減少 60%~80% 的文件大小,也是一個相對簡單(只要在服務器上配置一個選項),但優化效果較好的舉措。

避免HTTP重定向

HTTP重定向極其耗時,特別是把客戶端定向到一個完全不同的域名的情況下,還會導致額外的DNS查詢、TCP連接延遲,等等。

上面每一條建議都經受了時間檢驗,無論是該書出版的2007年還是今天,都是適用的。這並不是巧合,而是因為所有這些建議都反映了兩個根本方面:消除和減少不必要的網絡延遲,把傳輸的字節數降到最少。這兩個根本問題永遠是優化的核心,對任何應用都有效。

可是,對所有HTTP 1.1的特性和最佳實踐,我們就不能這么說了。因為有些HTTP 1.1特性,比如請求管道,由於缺乏支持而流產,而其他協議限制,比如隊首響應阻塞,則導致了更多問題。為此,Web開發社區(一直都最有創造性),創造和推行了很多自造的優化手段:域名分區、連接文件、拼合圖標、嵌入代碼,等等,不下數十種。

對多數Web開發者而言,所有這些都是切實可行的優化手段:熟悉、必要,而且通用。可是,現實當中,我們應該對這些技術有正確的認識:它們都是些針對當前HTTP 1.1協議的局限性而采用的權宜之計。我們本來不應該操心去連接文件、拼合圖標、分割域名或嵌入資源。但遺憾的是,“不應該”並不是務實的態度:這些優化手段之所以存在,都是有原因的,在背后的問題被HTTP的下一個版本解決之前,必須得依靠它們。

持久連接的優點 

HTTP 1.1的一個主要改進就是引入了持久HTTP連接 。現在我們再演示一下為什么這個特性對我們的優化策略如此重要。

為簡單起見,我們限定最多只有一個TCP連接,並且只取得兩個小文件(每個<4 KB):一個HTML文檔,一個CSS文件,服務器響應需要不同的時間(分別為40 ms和20 ms)。

 假設從紐約到倫敦的單向光纖延遲都是28 ms 

每個TCP連接開始都有三次握手,要經歷一次客戶端與服務器間完整的往返。此后,會因為HTTP請求和響應的兩次通信而至少引發另一次往返。最后,還要加上服務器處理時間,才能得到每次請求的總時間。

服務器處理時間無法預測,因為這個時間因資源和后端硬件而異。不過,這里的重點其實是由一個新TCP連接發送的HTTP請求所花的總時間,最少等於兩次網絡往返的時間:一次用於握手,一次用於請求和響應。這是所有非持久HTTP會話都要付出的固定時間成本。

服務器處理速度越快,固定延遲對每個網絡請求總時間的影響就越大!要驗證這一點,可以改一改前面例子中的往返時間和服務器處理時間。”

實際上,這時候最簡單的優化就是重用底層的連接!添加對HTTP持久連接的支持,就可以避免第二次TCP連接時的三次握手、消除另一次TCP慢啟動的往返,節約整整一次網絡延遲。

通過持久TCP連接取得HTML和CSS文件

在我們兩個請求的例子中,總共只節約了一次往返時間。但是,更常見的情況是一次TCP連接要發送N 次HTTP請求,這時:

  • 沒有持久連接,每次請求都會導致兩次往返延遲;

  • 有持久連接,只有第一次請求會導致兩次往返延遲,后續請求只會導致一次往返延遲。

在啟用持久連接的情況下,N 次請求節省的總延遲時間就是(N -1)×RTT。還記得嗎,前面說過,在當代Web應用中,N 的平均值是90,而且還在繼續增加。因此,依靠持久連接節約的時間,很快就可以用秒來衡量了!這充分說明持久化HTTP是每個Web應用的關鍵優化手段。

HTTP管道

持久HTTP可以讓我們重用已有的連接來完成多次應用請求,但多次請求必須嚴格滿足先進先出(FIFO)的隊列順序:發送請求,等待響應完成,再發送客戶端隊列中的下一個請求。HTTP管道是一個很小但對上述工作流卻非常重要的一次優化。管道可以讓我們把

FIFO隊列從客戶端(請求隊列)遷移到服務器(響應隊列)。

要理解這樣做的好處,我們再看一看通過持久TCP連接取得HTML和CSS文件示意圖。首先,服務器處理完第一次請求后,會發生了一次完整的往返:先是響應回傳,接着是第二次請求。在此期間服務器空閑。如果服務器能在處理完第一次請求后,立即開始處理第二次請求呢?甚至,如果服務器可以並行或在多線程上或者使用多個工作進程,同時處理兩個請求呢?

通過盡早分派請求,不被每次響應阻塞,可以再次消除額外的網絡往返。這樣,就從非持久連接狀態下的每個請求兩次往返,變成了整個請求隊列只需要兩次網絡往返!

 

現在我們暫停一會,回顧一下在性能優化方面的收獲。一開始,每個請求要用兩個TCP連接,總延遲為284 ms。在使用持久連接后,避免了一次握手往返,總延遲減少為228 ms。最后,通過使用HTTP管道,又減少了兩次請求之間的一次往返,總延遲減少為172 ms。這樣,從284 ms到172 ms,這40%的性能提升完全拜簡單的協議優化所賜。

而且,這40%的性能提升還不是固定不變的。這個數字與我們選擇的網絡延遲和兩個請求的例子有關。希望讀者自己能夠嘗試一些不同的情況,比如延遲更高、請求更多的情況。嘗試之后,你會驚訝於性能提升效果比這里還要高得多。事實上,網絡延遲越高,請求越多,節省的時間就越多。我覺得大家很有必要自己動手驗證一下這個結果。因此,越是大型應用,網絡優化的影響越大。

不過,這還不算完。眼光敏銳的讀者可能已經發現了,我們可以在服務器上並行處理請求。理論上講,沒有障礙可以阻止服務器同時處理管道中的請求,從而再減少20 ms的延遲。

可惜的是,當我們想要采取這個優化措施時,發現了HTTP 1.x協議的一些局限性。HTTP 1.x只能嚴格串行地返回響應。特別是,HTTP 1.x不允許一個連接上的多個響應數據交錯到達(多路復用),因而一個響應必須完全返回后,下一個響應才會開始傳輸。為說明這一點,我們可以看看服務器並行處理請求的情況(如下圖)。

 

上圖演示了如下幾個方面:

  • HTML和CSS請求同時到達,但先處理的是HTML請求;

  • 服務器並行處理兩個請求,其中處理HTML用時40 ms,處理CSS用時20 ms;

  • CSS請求先處理完成,但被緩沖起來以等候發送HTML響應;

  • 發送完HTML響應后,再發送服務器緩沖中的CSS響應。”

即使客戶端同時發送了兩個請求,而且CSS資源先准備就緒,服務器也會先發送HTML響應,然后再交付CSS。這種情況通常被稱作隊首阻塞 ,並經常導致次優化交付:不能充分利用網絡連接,造成服務器緩沖開銷,最終導致無法預測的客戶端延遲。假如第一個請求無限期掛起,或者要花很長時間才能處理完,怎么辦呢?在HTTP 1.1中,所有后續的請求都將被阻塞,等待它完成。

實際中,由於不可能實現多路復用,HTTP管道會導致HTTP服務器、代理和客戶端出現很多微妙的,不見文檔記載的問題:

  • 一個慢響應就會阻塞所有后續請求;

  • 並行處理請求時,服務器必須緩沖管道中的響應,從而占用服務器資源,如果有個響應非常大,則很容易形成服務器的受攻擊面;

  • 響應失敗可能終止TCP連接,從頁強迫客戶端重新發送對所有后續資源的請求,導致重復處理;

  • 由於可能存在中間代理,因此檢測管道兼容性,確保可靠性很重要;

  • 如果中間代理不支持管道,那它可能會中斷連接,也可能會把所有請求串聯起來。

由於存在這些以及其他類似的問題,而HTTP 1.1標准中也未對此做出說明,HTTP管道技術的應用非常有限,雖然其優點毋庸置疑。今天,一些支持管道的瀏覽器,通常都將其作為一個高級配置選項,但大多數瀏覽器都會禁用它。換句話說,如果瀏覽器是Web應用的主要交付工具,那還是很難指望通過HTTP管道來提升性能。

使用多個TCP連接

由於HTTP 1.x不支持多路復用,瀏覽器可以不假思索地在客戶端排隊所有HTTP請求,然后通過一個持久連接,一個接一個地發送這些請求。然而,這種方式在實踐中太慢。實際上,瀏覽器開發商沒有別的辦法,只能允許我們並行打開多個TCP會話。多少個?現實中,大多數現代瀏覽器,包括桌面和移動瀏覽器,都支持每個主機打開6個連接。
進一步討論之前,有必要先想一想同時打開多個TCP連接意味着什么。當然,有正面的也有負面的。下面我們以每個主機打開最多6個獨立連接為例:

  • 客戶端可以並行分派最多6個請求;

  • 服務器可以並行處理最多6個請求;

  • 第一次往返可以發送的累計分組數量(TCP cwnd)增長為原來的6倍。

在沒有管道的情況下,最大的請求數與打開的連接數相同。相應地,TCP擁塞窗口也要乘以打開的連接數量,從而允許客戶端繞開由TCP慢啟動規定的分組限制。這好像是一個方便的解決方案。我們再看看這樣做的代價:

  • 更多的套接字會占用客戶端、服務器以及代理的資源,包括內存緩沖區和CPU時鍾周期;

  • 並行TCP流之間競爭共享的帶寬;

  • 由於處理多個套接字,實現復雜性更高;

  • 即使並行TCP流,應用的並行能力也受限制。

實踐中,CPU和內存占用並非微不足道,由此會導致客戶端和服務器端的資源占用量上升,運維成本提高。類似地,由於客戶端實現的復雜性提高,開發成本也會提高。最后,說到應用的並行性,這種方式提供的好處還是非常有限的。這不是一個長期的方案。了解這些之后,可以說今天之所以使用它,主要有三個原因:

  • 作為繞過應用協議(HTTP)限制的一個權宜之計;

  • 作為繞過TCP中低起始擁塞窗口的一個權宜之計;

  • 作為讓客戶端繞過不能使用TCP窗口縮放”的一個權宜之計。

后兩個針對TCP的問題(窗口縮放和cwnd)最好是通過升級到最新的OS內核來解決。cwnd值最近又提高到了10個分組,而所有最新的平台都能可靠地支持TCP窗口縮放。這當然是好消息。但壞消息是,沒有更好辦法繞開HTTP 1.x的多路復用問題。

只要必須支持HTTP 1.x客戶端,就不得不想辦法應對多TCP流的問題。而這又會帶來一個明顯的問題:為什么瀏覽器要規定每個主機6個連接呢?恐怕有讀者也猜到了,這個數字是多方平衡的結果:這個數字越大,客戶端和服務器的資源占用越多,但隨之也會帶來更高的請求並行能力。每個主機6個連接只不過是大家都覺得比較安全的一個數字。對某些站點而言,這個數字已經足夠了,但對其他站點來說,可能還滿足不了需求。  

域名分區

HTTP 1.x協議的一項空白強迫瀏覽器開發商引入並維護着連接池,每個主機最多6個TCP流。好的一方面是對這些連接的管理工作都由瀏覽器來處理。作為應用開發者,你根本不必修改自己的應用。不好的一方面呢,就是6個並行的連接對你的應用來說可能仍然不夠用。

根據HTTP Archive的統計,目前平均每個頁面都包含90多個獨立的資源,如果這些資源都來自同一個主機,那么仍然會導致明顯的排隊等待(如下圖所示)。實際上,何必把自己只限制在一個主機上呢?我們不必只通過一個主機(例如www.example.com)提供所有資源,而是可以手工將所有資源分散到多個子域名:{shard1, shardn}.example.com。由於主機名稱不一樣了,就可以突破瀏覽器的連接限制,實現更高的並行能力。域名分區使用得越多,並行能力就越強!

由於每個主機只能同時發起6個連接而導致的資源錯列

當然,天下沒有免費的午餐,域名分區也不例外:每個新主機名都要求有一次額外的DNS查詢,每多一個套接字都會多消耗兩端的一些資源,而更糟糕的是,站點作者必須手工分離這些資源,並分別把它們托管到多個主機上。

實踐中,域名分區經常會被濫用,導致幾十個TCP流都得不到充分利用,其中很多永遠也避免不了TCP慢啟動,最壞的情況下還會降低性能。此外,如果使用的是HTTPS,那么由於TLS握手導致的額外網絡往返,會使得上述代價更高。此時,請大家注意如下幾條:

  • 首先,把TCP利用好;

  • 瀏覽器會自動為你打開6個連接;

  • 資源的數量、大小和響應時間都會影響最優的分區數目;”

  • 客戶端延遲和帶寬會影響最優的分區數目;

  • 域名分區會因為額外的DNS查詢和TCP慢啟動而影響性能。

域名分區是一種合理但又不完美的優化手段。請大家一定先從最小分區數目(不分區)開始,然后逐個增加分區並度量分區后對應用的影響。現實當中,真正因同時打開十幾個連接而提升性能的站點並不多,如果你最終使用了很多分區,那么你會發現減少資源數量或者將它們合並為更少的請求,反而能帶來更大的好處。

DNS查詢和TCP慢啟動導致的額外消耗對高延遲客戶端的影響最大。換句話說,移動(3G、4G)客戶端經常是受過度域名分區影響最大的!

度量和控制協議開銷

HTTP 0.9當初就是一個簡單的只有一行的ASCII請求,用於取得一個超文本文檔,這樣導致的開銷是最小的。HTTP 1.0增加了請求和響應首部,以便雙方能夠交換有關請求和響應的元信息。最終,HTTP 1.1把這種格式變成了標准:服務器和客戶端都可以輕松擴展首部,而且始終以純文本形式發送,以保證與之前HTTP版本的兼容。

今天,每個瀏覽器發起的HTTP請求,都會攜帶額外500~800字節的HTTP元數據:用戶代理字符串、很少改變的接收和傳輸首部、緩存指令,等等。有時候,500~800字節都少說了,因為沒有包含最大的一塊:HTTP cookie。現代應用經常通過cookie進行會話管理、記錄個性選項或者完成分析。綜合到一起,所有這些未經壓縮的HTTP元數據經常會給每個HTTP請求增加幾千字節的協議開銷。

HTTP首部的增多對它本身不是壞事,因為大多數首部都有其特定用途。然而,由於所有HTTP首部都以純文本形式發送(不會經過任何壓縮),這就會給每個請求附加較高的額外負荷,而這在某些應用中可能造成嚴重的性能問題。舉個例子,API驅動的Web應用越來越多,這些應用需要頻繁地以序列化消息(如JSON)的形式通信。在這些應用中,額外的HTTP開銷經常會超過實際傳輸的數據凈荷一個數量級:

“$> curl --trace-ascii - -
d'{&quot;msg&quot;:&quot;hello&quot;}'
http://www.igvita.com/api”

對應的結果:

== Info: Connected to www.igvita.com
=> Send header, 218 bytes ➊
POST /api HTTP/1.1
User-Agent: curl/7.24.0 (x86_64-apple-darwin12.0) libcurl/7.24.0 ...
Host: www.igvita.com
Accept: */*
Content-Length: 15 ➋
Content-Type: application/x-www-form-urlencoded
=> Send data, 15 bytes (0xf)
{&quot;msg&quot;:&quot;hello&quot;}

<= Recv header, 134 bytes ➌
HTTP/1.1 204 No Content
Server: nginx/1.0.11
Via: HTTP/1.1 GWA
Date: Thu, 20 Sep 2012 05:41:30 GMT
Cache-Control: max-age=0, no-cache
  1. HTTP請求首部:218字節

  2. 應用靜荷15字節({&quot;msg”:&quot;hello&quot;})

  3. 服務器的204響應:134字節

在前面的例子中,寥寥15個字符的JSON消息被352字節的HTTP首部包裹着,全部以純文本形式發送——協議字節開銷占96%,而且這還是沒有cookie的最好情況。減少要傳輸的首部數據(高度重復且未壓縮),可以節省相當於一次往返的延遲時間,顯著提升很多Web應用的性能。

“Cookie在很多應用中都是常見的性能瓶頸,很多開發者都會忽略它給每次請求增加的額外負擔。

連接與拼合

最快的請求是不用請求。不管使用什么協議,也不管是什么類型的應用,減少請求次數總是最好的性能優化手段。可是,如果你無論如何也無法減少請求,那么對HTTP 1.x而言,可以考慮把多個資源捆綁打包到一塊,通過一次網絡請求獲取:

  • 連接 :把多個JavaScript或CSS文件組合為一個文件。

  • 拼合:把多張圖片組合為一個更大的復合的圖片。

對JavaScript和CSS來說,只要保持一定的順序,就可以做到把多個文件連接起來而不影響代碼的行為和執行。類似地,多張圖片可以組合為一個“圖片精靈”,然后使用CSS選擇這張大圖中的適當部分,顯示在瀏覽器中。這兩種技術都具備兩方面的優點。

  • 減少協議開銷:通過把文件組合成一個資源,可以消除與文件相關的協議開銷。如前所述,每個文件很容易招致KB級未壓縮數據的開銷。

  • 應用層管道:說到傳輸的字節,這兩種技術的效果都好像是啟用了HTTP管道:來自多個響應的數據前后相繼地連接在一起,消除了額外的網絡延遲。實際上,就是把管道提高了一層,置入了應用中。

連接和拼合技術都屬於以內容為中心的應用層優化,它們通過減少網絡往返開銷,可以獲得明顯的性能提升。可是,實現這些技術也要求額外的處理、部署和編碼(比如選擇圖片精靈中子圖的CSS代碼),因而也會給應用帶來額外的復雜性。此外,把多個資源打包到一塊,也可能給緩存帶來負擔,影響頁面的執行速度。

要理解為什么這些技術會傷害性能,可以考慮一種並不少見的情況:一個包含十來個JavaScript和CSS文件的應用,在產品狀態下把所有文件合並為一個CSS文件和一個JavaScript文件。

  • 相同類型的資源都位於一個URL(緩存鍵)下面。

  • 資源包中可能包含當前頁面不需要的內容。

  • 對資源包中任何文件的更新,都要求重新下載整個資源包,導致較高的字節開銷。

  • JavaScript和CSS只有在傳輸完成后才能被解析和執行,因而會拖慢應用的執行速度。

實踐中,大多數Web應用都不是只有一個頁面,而是由多個視圖構成。每個視圖都有自己的資源,同時資源之間還有部分重疊:公用的CSS、JavaScript和圖片。實際上,把所有資源都組合到一個文件經常會導致處理和加載不必要的字節。雖然可以把它看成一種預獲取,但代價則是降低了初始啟動的速度。

對很多應用來說,更新資源帶來的問題更大。更新圖片精靈或組合JavaScript文件中的某一處,可能就會導致重新傳輸幾百KB數據。由於犧牲了模塊化和緩存粒度,假如打包資源變動頻率過高,特別是在資源包過大的情況下,很快就會得不償失。如果你的應用真到了這種境地,那么可以考慮把“穩定的核心”,比如框架和庫,轉移到獨立的包中。

內存占用也會成為問題。對圖片精靈來說,瀏覽器必須分析整個圖片,即便實際上只顯示了其中的一小塊,也要始終把整個圖片都保存在內存中。瀏覽器是不會把不顯示的部分從內存中剔除掉的!

最后,為什么執行速度還會受影響呢?我們知道,瀏覽器是以遞增方式處理HTML的,而對於JavaScript和CSS的解析及執行,則要等到整個文件下載完畢。JavaScript和CSS處理器都不允許遞增式執行。

CSS和JavaScript文件大小與執行性能

CSS文件越大,瀏覽器在構建CSSOM前經歷的阻塞時間就越長,從而推遲首次繪制頁面的時間。類似地,JavaScript文件越大,對執行速度的影響同樣越大;小文件倒是能實現“遞增式”執行。打包文件到底多大合適呢?可惜的是,沒有理想的大小。然而,谷歌PageSpeed團隊的測試表明,30~50 KB(壓縮后)是每個JavaScript文件大小的合適范圍:既大到了能夠減少小文件帶來的網絡延遲,還能確保遞增及分層式的執行。具體的結果可能會由於應用類型和腳本數量而有所不同。

總之,連接和拼合是在HTTP 1.x協議限制(管道沒有得到普遍支持,多請求開銷大)的現實之下可行的應用層優化。使用得當的話,這兩種技術可以帶來明顯的性能提升,代價則是增加應用的復雜度,以及導致緩存、更新、執行速度,甚至渲染頁面的問題。應用這兩種優化時,要注意度量結果,根據實際情況考慮如下問題。

  • 你的應用在下載很多小型的資源時是否會被阻塞?

  • 有選擇地組合一些請求對你的應用有沒有好處?

  • 放棄緩存粒度對用戶有沒有負面影響?

  • 組合圖片是否會占用過多內存?

  • 首次渲染時是否會遭遇延遲執行?

在上述問題的答案間求得平衡是一種藝術。

嵌入資源

嵌入資源是另一種非常流行的優化方法,把資源嵌入文檔可以減少請求的次數。比如,JavaScript和CSS代碼,通過適當的script 和style 塊可以直接放在頁面中,而圖片甚至音頻或PDF文件,都可以通過數據URI(data:[mediatype][;base64],data )的方式嵌入到頁面中:

<img src=&quot;data:image/gif;base64,R0lGODlhAQABAIAAAAA
          AAAAAACH5BAAAAAAALAAAAAABAAEAAAICTAEAOw==&quot;
     alt=&quot;1x1 transparent (GIF) pixel&quot; />

數據URI適合特別小的,理想情況下,最好是只用一次的資源。以嵌入方式放到頁面中的資源,應該算是頁面的一部分,不能被瀏覽器、CDN或其他緩存代理作為單獨的資源緩存。換句話說,如果在多個頁面中都嵌入同樣的資源,那么這個資源將會隨着每個頁面的加載而被加載,從而增大每個頁面的總體大小。另外,如果嵌入資源被更新,那么所有以前出現過它的頁面都將被宣告無效,而由客戶端重新從服務器獲取。

最后,雖然CSS和JavaScript等基於文本的資源很容易直接嵌入頁面,也不會帶來多余的開銷,但非文本性資源則必須通過base64編碼,而這會導致開銷明顯增大:編碼后的資源大小比原大小增大33%!

base64編碼使用64個ASCII符號和空白符將任意字節流編碼為ASCII字符串。編碼過程中,base64會導致被編碼的流變成原來的4/3,即增大33%的字節開銷。

實踐中,常見的一個經驗規則是只考慮嵌入1~2 KB以下的資源,因為小於這個標准的資源經常會導致比它自身更高的HTTP開銷。然而,如果嵌入的資源頻繁變更,又會導致宿主文檔的無效緩存率升高。嵌入資源也不是完美的方法。如果你的應用要使用很小的、個別的文件,在考慮是否嵌入時,可以參照如下建議:

  • 如果文件很小,而且只有個別頁面使用,可以考慮嵌入;

  • 如果文件很小,但需要在多個頁面中重用,應該考慮集中打包;

  • 如果小文件經常需要更新,就不要嵌入了;

  • 通過減少HTTP cookie的大小將協議開銷最小化。

 

關於 HTTP 系列文章:

 

參考書籍:

 Ilya Grigorik. Web性能權威指南 (圖靈程序設計叢書)


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM