前言
由於HTTP 1自身的局限性,它不能很好的為用戶提供性能良好的WEB服務。於1999年6月正式發布了HTTP1.1標准REC2616,它厘清了之前版本中很多有歧義的地方,而且還新增了很多重要的優化,如持久連接、分塊編碼傳輸、狀態碼擴充、增強的緩存機制、傳輸編碼及請求管道等。本文是個人在學習《WEB性能權威指南》后,又查閱了一些文檔資料寫的一篇隨筆,僅供參考和個人以后查閱。下面將對比http1.0講述一些在新版本中的重要改進。(本文最初發布於公司內網,外網原文地址:騰雲閣--HTTP 1.1學習筆記)
1.持久連接
每個TCP連接在建立初期都需要進行三次握手,需要經歷一次客戶端與服務器間的完整往返,如果進行數據傳輸的話,至少還需要引發另外一次往返。再加上服務器端的處理請求的時間,就是可以得到每次請求的總時間。如果每次發送請求都是由新建的TCP連接發送的話,它至少需要兩次完整的網絡往返時間,因為每個TCP連接的建立都需要重新進行三次握手。
那么,能不能對TCP連接進行重用呢?
答案無疑是肯定的,HTTP1.1里添加了持久連接的支持,再次發送請求時,可以直接使用上次已經建立完成的TCP連接,這樣就避免了第二次TCP連接時的三次握手、消除另一次TCP慢啟動的往返,極大了減小了網絡延時。如果重用CTP連接發送HTTP請求的次數越多,帶來的性能提升越可觀,因為在第一次經過三次握手建立連接之后,無需再花費多余的時間再建立連接。
注:目前,所有現代瀏覽器都嘗試持久化HTTP連接,如果服務器支持的話。使用HTTP1.1的話,默認的就是持久連接;如果使用HTTP 1.0,可以明確使用connection:keep-alive首部聲明使用持久連接;還應注意一些HTTP庫和框架的默認行為,它們有時候並不是默認使用持久連接的。
2.HTTP管道
持久HTTP可以讓我們重用已有的連接來完成多次請求,但這些請求需要滿足先進先出的隊列順序:發送請求--等待響應,再發送下一個請求。HTTP/1.1允許多個http請求通過一個套接字同時被輸出 ,而不用等待相應的響應。然后請求者就會等待各自的響應,這些響應是按照之前請求的順序依次到達。(所有請求保持一個FIFO的隊列,一個請求發送完之后,不必等待這個請求的響應被接受到,下一個請求就可以被再次發出;同時,服務器端返回這些請求的響應時也是按照FIFO的順序)。
在高延遲和多請求的場景下,通過HTTP管道進行數據傳輸會有更大的性能提升,會節省更多的時間。經過仔細的觀察,可以發現在HTTP1.1中存在一些局限,它嚴格串行的返回響應,它不允許多個數據交錯到達(多路復用),只能等待一個響應完全返回后,下一個響應才能發送,無論下一個響應是否早於前一個響應完成處理,這也叫做隊首阻塞。這就帶來一個非常糟糕的體驗,如果第一個請求需要處理的時間非常長,那么后續的請求即使被服務器已經處理完成,響應也不能立即返回,而是存儲在服務端的緩存區中,等待第一個響應的完成,才能按照FIFO順序返回。
由於TCP嚴格按照按順序交付,丟失一個TCP分組就會阻塞所有高序號的分組,除非重傳丟失的分組,這也會帶來額外的延遲。由於HTTP1.1中不允許多路復用,HTTP管道也會帶來一些不容忽視的問題:
-
一個慢響應會阻塞所有后續請求;
-
並行處理請求時,服務器需要緩存處理結果,會占用服務器資源,如果某個響應很大,很容易形成服務器的受攻擊面;
-
響應失敗可能終止TCP連接,會強迫客戶端重新發送對后續資源的請求,導致重復處理;
-
網絡環境中存在中間代理,檢測管道兼容性十分必要;
-
如果中間代理不支持管道,那它可能中斷連接,也可能把所有請求串聯起來。
正是由於存在這樣或那樣的問題,HTTP管道技術的應用比較有限,並沒有大面積推廣開來,即使一些支持它的瀏覽器也僅僅把它作為一個高級選項。如果你對客戶端和服務端都有很強的控制力,依然可以使用它,會帶來不錯的性能提升。如果要在采用這種技術,你需要注意以下事項:
-
確保HTTP客戶端支持管道;
-
確保HTTP服務器支持管道;
-
應用必須處理中斷的連接並恢復;
-
應用必須處理中斷的請求的冪等問題;
-
應用必須保持自身不受出問題的代理的影響。
實踐中部署HTTP管道的最佳途徑,就是在客戶端和服務端使用HTTPS,這樣就可以免除不支持管道的中間代理的干擾。
3.增強的緩存機制
在HTTP/1.0中,使用Expire頭域來判斷資源的fresh或stale,並使用條件請求(conditional request)來判斷資源是否仍有效。
相關字段:
-
Date: 服務器響應的時間;
-
Expires: 資源過期時間;
-
Last-Modified: 資源最后修改時間;
-
If-Modified-Since: 用來驗證資源是否過期;
大致策略為:
如果Expires設置的時間在Date之后,則瀏覽器在Expires標記的時間之前都不會訪問服務器了,而是使用瀏覽器緩存;如果Expires設置的時間在Date之前,或者瀏覽器時間已經在Expires之后,那么再次訪問資源時, 瀏覽器就要向服務器發送請求,但不是重新拉取數據,而是詢問服務器該資源是否過期,方法時,把上次response中Last-Modified的時間作為If-Modified-Since的時間,發送請求,服務器對比該時間和資源目前的更改時間,如果未更改,則返回304,否則傳輸新文件。此外,HTTP/1.0中還定義了Pragma:no-cache頭域,客戶端使用該頭域說明請求資源不能從cache中獲取,而必須回源獲取。
HTTP/1.0中,If-Modified-Since頭域使用的是絕對時間戳,精確到秒,但使用絕對時間會帶來不同機器上的時鍾同步問題,文檔的 更新周期小於1s, 都會出現問題。
為了提供更好的緩存機制,HTTP1.1對以往的機制進行了一個漸進性的改進。HTTP/1.1提倡的緩存機制是,對比文檔的hash值,文檔內容變,則hash變,用相對時間代替絕對時間,這樣就可以解決使用絕對時間戳帶來一些問題。
HTTP/1.1 繼承 HTTP/1.0 所以HTTP/1.0的相關字段仍然有效,保留的這些字段就是為了兼容那些僅支持HTTP/1.0的客戶端。 HTTP/1.1服務器不應該設置與1.0矛盾的過期策略, 1.1的服務器在沒有文檔hash值時,也可以使用If-Modified-Since進行判斷文檔過期。
HTTP1.1中新增了以下字段:
-
Cache-Control: 用來控制瀏覽器的緩存行為;
-
ETag: 文檔的Hash值;
-
If-None-Match: 用來驗證資源是否過期,即文檔Hash值是否變化。
而HTTP/1.1中引入了一個ETag頭域用於重激活機制,它的值ETag可以用來唯一的描述一個資源。請求消息中可以使用If-None-Match頭域來匹配資源的entitytag是否有變化。
為了使caching機制更加靈活,HTTP/1.1增加了Cache-Control頭域(請求消息和響應消息都可使用),它支持一個可擴展的指令子集:例如max-age指令支持相對時間戳;private和no-store指令禁止對象被緩存;no-transform阻止Proxy進行任何改變響應的行為;no-cache瀏覽器緩存,但是認為是過期緩存;no-store瀏覽器不緩存;max-age:緩存有效時間段等等。
如果想要瀏覽器每次發送請求,還啟用緩存,那就使用Cache-Control: no-cache, 每次訪問圖片,瀏覽器都會去驗證Etag。
Cache使用關鍵字索引在磁盤中緩存的對象,在HTTP/1.0中使用資源的URL作為關鍵字。但可能存在不同的資源基於同一個URL的情況,要區別它們還需要客戶端提供更多的信息,如Accept-Language和Accept-Charset頭域。為了支持這種內容協商機制(content negotiation mechanism),HTTP/1.1在響應消息中引入了Vary頭域,該頭域列出了請求消息中需要包含哪些頭域用於內容協商。
4.分塊編碼傳輸
HTTP消息中可以包含任意長度的實體,通常它們使用Content-Length來給出消息結束標志。但是,對於很多動態產生的響應,只能通過緩沖完整的消息來判斷消息的大小,但這樣做會加大延遲。如果不使用長連接,還可以通過連接關閉的信號來判定一個消息的結束。
HTTP/1.1中引入了Chunke dtransfer-coding來解決上面這個問題,發送方將消息分割成若干個任意大小的數據塊,每個數據塊在發送時都會附上塊的長度,最后用一個零長度的塊作為消息結束的標志。這種方法允許發送方只緩沖消息的一個片段,避免緩沖整個消息帶來的過載。
在HTTP/1.0中,有一個Content-MD5的頭域,要計算這個頭域需要發送方緩沖完整個消息后才能進行。而HTTP/1.1中,采用chunked分塊傳遞的消息在最后一個塊(零長度)結束之后會再傳遞一個拖尾(trailer),它包含一個或多個頭域,這些頭域是發送方在傳遞完所有塊之后再計算出值的。發送方會在消息中包含一個Trailer頭域告訴接收方這個拖尾的存在。
5.狀態碼擴充
狀態碼是試圖理解和滿足請求的三位數字的整數碼。HTTP/1.0中只定義了16個狀態響應碼,對錯誤或警告的提示不夠具體。HTTP/1.1引入了一個Warning頭域,增加對錯誤或警告信息的描述。
截止到HTTP1.1中包含的狀態碼大致如下:
狀態代碼 狀態信息 含義
消息(1**)
100 Continue 初始的請求已經接受,客戶應當繼續發送請求的其余部分。(HTTP 1.1新)
101 Switching Protocols 服務器將遵從客戶的請求轉換到另外一種協議(HTTP 1.1新)
成功(2**)
200 OK 一切正常,對GET和POST請求的應答文檔跟在后面。
201 Created 服務器已經創建了文檔,Location頭給出了它的URL。
202 Accepted 已經接受請求,但處理尚未完成。
203 Non-Authoritative Information 文檔已經正常地返回,但一些應答頭可能不正確,因為使用的是文檔的拷貝(HTTP 1.1新)。
204 No Content 沒有新文檔,瀏覽器應該繼續顯示原來的文檔。如果用戶定期地刷新頁面,而Servlet可以確定用戶文檔足夠新,這個狀態代碼是很有用的。
205 Reset Content 沒有新的內容,但瀏覽器應該重置它所顯示的內容。用來強制瀏覽器清除表單輸入內容(HTTP 1.1新)。
206 Partial Content 客戶發送了一個帶有Range頭的GET請求,服務器完成了它(HTTP 1.1新)。
重定向(3**)
300 Multiple Choices 客戶請求的文檔可以在多個位置找到,這些位置已經在返回的文檔內列出。如果服務器要提出優先選擇,則應該在Location應答頭指明。
301 Moved Permanently 客戶請求的文檔在其他地方,新的URL在Location頭中給出,瀏覽器應該自動地訪問新的URL。
302 Found 類似於301,但新的URL應該被視為臨時性的替代,而不是永久性的。注意,在HTTP1.0中對應的狀態信息是“Moved Temporatily”。
出現該狀態代碼時,瀏覽器能夠自動訪問新的URL,因此它是一個很有用的狀態代碼。
注意這個狀態代碼有時候可以和301替換使用。例如,如果瀏覽器錯誤地請求 http://host/~user (缺少了后面的斜杠), 有的服務器返回301,有的則返回302。
嚴格地說,我們只能假定只有當原來的請求是GET時瀏覽器才會自動重定向。請參見307。
303 See Other 類似於301/302,不同之處在於,如果原來的請求是POST,Location頭指定的重定向目標文檔應該通過GET提取(HTTP 1.1新)。
304 Not Modified 客戶端有緩沖的文檔並發出了一個條件性的請求(一般是提供If-Modified-Since頭表示客戶只想比指定日期更新的文檔)。服務器告訴客戶,原來緩沖的文檔還可以繼續使用。
305 Use Proxy 客戶請求的文檔應該通過Location頭所指明的代理服務器提取(HTTP 1.1新)。
307 Temporary Redirect 和302(Found)相同。許多瀏覽器會錯誤地響應302應答進行重定向,即使原來的請求是POST,即使它實際上只能在POST請求的應答是303時 才能重定向。由於這個原因,HTTP 1.1新增了307,以便更加清除地區分幾個狀態代碼:當出現303應答時,瀏覽器可以跟隨重定向的GET和POST請求;如果是307應答,則瀏覽器只能跟隨對GET請求的重定向。(HTTP 1.1新)
請求錯誤(4**)
400 Bad Request 請求出現語法錯誤。
401 Unauthorized 客戶試圖未經授權訪問受密碼保護的頁面。應答中會包含一個WWW-Authenticate頭,瀏覽器據此顯示用戶名字/密碼對話框,然后在填寫合適的Authorization頭后再次發出請求。
403 Forbidden 資源不可用。服務器理解客戶的請求,但拒絕處理它。通常由於服務器上文件或目錄的權限設置導致。
404 Not Found 無法找到指定位置的資源。這也是一個常用的應答。
405 Method Not Allowed 請求方法(GET、POST、HEAD、DELETE、PUT、TRACE等)對指定的資源不適用。(HTTP 1.1新)
406 Not Acceptable 指定的資源已經找到,但它的MIME類型和客戶在Accpet頭中所指定的不兼容(HTTP 1.1新)。
407 Proxy Authentication Required 類似於401,表示客戶必須先經過代理服務器的授權。(HTTP 1.1新)
408 Request Timeout 在服務器許可的等待時間內,客戶一直沒有發出任何請求。客戶可以在以后重復同一請求。(HTTP 1.1新)
409 Conflict 通常和PUT請求有關。由於請求和資源的當前狀態相沖突,因此請求不能成功。(HTTP 1.1新)
410 Gone 所請求的文檔已經不再可用,而且服務器不知道應該重定向到哪一個地址。它和404的不同在於,返回407表示文檔永久地離開了指定的位置,而404表示由於未知的原因文檔不可用。(HTTP 1.1新)
411 Length Required 服務器不能處理請求,除非客戶發送一個Content-Length頭。(HTTP 1.1新)
412 Precondition Failed 請求頭中指定的一些前提條件失敗(HTTP 1.1新)。
413 Request Entity Too Large 目標文檔的大小超過服務器當前願意處理的大小。如果服務器認為自己能夠稍后再處理該請求,則應該提供一個Retry-After頭(HTTP 1.1新)。
414 Request URI Too Long URI太長(HTTP 1.1新)。
416 Requested Range Not Satisfiable 服務器不能滿足客戶在請求中指定的Range頭。(HTTP 1.1新)
服務器錯誤(5**)
500 Internal Server Error 服務器遇到了意料不到的情況,不能完成客戶的請求。
501 Not Implemented 服務器不支持實現請求所需要的功能。例如,客戶發出了一個服務器不支持的PUT請求。
502 Bad Gateway 服務器作為網關或者代理時,為了完成請求訪問下一個服務器,但該服務器返回了非法的應答。
503 Service Unavailable 服務器由於維護或者負載過重未能應答。例如,Servlet可能在數據庫連接池已滿的情況下返回503。服務器返回503時可以提供一個Retry-After頭。
504 Gateway Timeout 由作為代理或網關的服務器使用,表示不能及時地從遠程服務器獲得應答。(HTTP 1.1新)
505 HTTP Version Not Supported 服務器不支持請求中所指明的HTTP版本。(HTTP 1.1新)
6.Host頭域
在HTTP1.0中認為每台服務器都綁定一個唯一的IP地址,因此,請求消息中的URL並沒有傳遞主機名(hostname)。但隨着虛擬主機技術的發展,在一台物理服務器上可以存在多個虛擬主機(Multi-homed Web Servers),並且它們共享一個IP地址。由於HTTP 1.0不支持Host請求頭字段,WEB瀏覽器無法使用主機頭名來明確表示要訪問服務器上的哪個WEB站點,這樣就無法使用WEB服務器在同一個IP地址和端口號上配置多個虛擬WEB站點。在HTTP 1.1中增加Host請求頭字段后,WEB瀏覽器可以使用主機頭名來明確表示要訪問服務器上的哪個WEB站點,這才實現了在一台WEB服務器上可以在同一個IP地址和端口號上使用不同的主機名來創建多個虛擬WEB站點。
7.請求方式新增
客戶程序向服務器發送的請求可以有不同的類型,這樣服務器可以根據不同的請求類型進行不同的處理。在HTTP1.0中,定義了三種最基本的請求類 型,GET、POST和HEAD,當使用GET和POST方法時,服務器最后都將結果文檔返回給客戶程序,瀏覽器將刷新顯示;而HEAD請求則不同,HEAD請求在客戶程序和服務器之間進行交流,而不會返回具體的文檔,它僅僅交流一些內部數據,這些數據不會影響瀏覽的過程。因此HEAD方法通常不單獨使用,而是和其他的請求方法一起起到輔助作用。一些搜尋引擎使用的自動搜索機器人使用這個方法來獲得網頁的標志信息,或者進行安全認證時,使用這個方法來傳遞認證信息。
除了上述三種最常見的訪問方法之外,在HTTP1.1中還定義了更多的訪問方法類型,具體如下:
-
PUT:向指定資源位置上傳其最新內容;
-
DELETE:請求服務器刪除Request-URI所標識的資源;
-
OPTIONS:返回服務器針對特定資源所支持的HTTP請求方法。也可以利用向Web服務器發送'*'的請求來測試服務器的功能性;
-
TRACE:回顯服務器收到的請求,主要用於測試或診斷;
-
CONTENT:HTTP/1.1協議中預留給能夠將連接改為管道方式的代理服務器。
這些方法並不常用,因而大部分Web服務器軟件並沒有實現他們。然而對於特定場合他們還是非常有用的,如果服務器不支持客戶發送的請求方法,服務器將返回錯誤並立即關閉連接。
8.帶寬優化
HTTP/1.0中,存在一些浪費帶寬的現象,例如客戶端只是需要某個對象的一部分,而服務器卻將整個對象送過來了,又比如下載大文件時需要支持斷點續傳功能,而不是在發生斷連后不得不重新下載完整的包。HTTP/1.1中在請求消息中引入了range頭域,它允許只請求資源的某個部分。在響應消息中Content-Range頭域聲明了返回的這部分對象的偏移值和長度。如果服務器相應地返回了對象所請求范圍的內容,則響應碼為206(Partial Content),它可以防止Cache將響應誤以為是完整的一個對象。
另外一種情況是請求消息中如果包含比較大的實體內容,但不確定服務器是否能夠接收該請求(如是否有權限),此時若貿然發出帶實體的請求,如果被拒絕也會浪費帶寬。HTTP/1.1加入了一個新的狀態碼100(Continue)。客戶端事先發送一個只帶頭域的請求,如果服務器因為權限拒絕了請求,就回送響應碼401(Unauthorized);如果服務器接收此請求就回送響應碼100,客戶端就可以繼續發送帶實體的完整請求了。注意,HTTP/1.0的客戶端不支持100響應碼。但可以讓客戶端在請求消息中加入Expect頭域,並將它的值設置為100-continue。
節省帶寬資源的一個非常有效的做法就是壓縮要傳送的數據。Content-Encoding是對消息進行端到端(end-to-end)的編碼,它可能是資源在服務器上保存的固有格式(如jpeg圖片格式);在請求消息中加入Accept-Encoding頭域,它可以告訴服務器客戶端能夠解碼的編碼方式。而Transfer-Encoding是逐段式(hop-by-hop)的編碼,如Chunked編碼。在請求消息中加入TE頭域用來告訴服務器能夠接收的transfer-coding方式。
9.性能優化
9.1使用多個TCP連接
上文已經說明,HTTP1.X並不支持多路復用,請求需要在客戶端排隊等待發送,而且容易遇到隊首阻塞的問題,並不能很好的提高數據傳輸速率。那么,既然不能對單一連接進行多路復用,那是不是可以同時打開多個連接進行數據傳輸呢?答案是肯定的,瀏覽器開發商為了解決這個問題,使瀏覽器支持客戶端最多打開六個連接,這樣我們就可以更快速的進行通信。任何事情都兩面性,同時打開多個連接勢必帶來一些優化和問題,具體如下:
優點:
-
客戶端可以並行發起多個請求;
-
服務器可以並行處理多個請求;
-
第一次往返可以發送的累計分組數量是原來的6倍;
缺點:
-
更多的套接字會占用更多的資源;
-
並行TCP流之間競爭共享的帶寬;
-
處理多個套接字,實現更為復雜;
-
即使並行TCP流,應用的並發能力也受限制。
這種打開多個連接的方式,也帶來了一些壞處,那為什么現在還使用的如此廣泛呢?主要由以下三個原因:
-
作為繞過HTTP限制的一個權宜之計;
-
作為繞過TCP中低起始擁塞窗口的一個權宜之計;
-
作為客戶端繞過不能使用TCP窗口縮放的一個權益之計。
9.2域名分區
由於HTTP1.1協議不支持多路復用,迫使瀏覽器開發商為了提高通信效率,引入並維護着連接池,每個主機可以有6個TCP流。根據HTTP Archive統計,目前平均每個頁面要包含90個左右的資源,如果這些資源都來自於同一個主機,即使可以同時打開6個TCP流,依然會導致明顯的排隊情形。我們並不需要把所有的資源都放在同一個主機上,可以分開放置到不同的域名下,這樣就可以增加可以同時打開的TCP流總數,可以突破瀏覽器的連接限制,實現更高的並發能力。
理論上來說,使用的域名越多,並行能力也就越強。但是,在發送請求之前都需要進行DNS解析,不同的域名需要分別進行解析,都需要進行額外的DNS查詢,如果域名數量過多,會導致大量的額外解析;在TCP連接中存在的慢啟動機制,有時候也會降低性能;而且每多一個套接字都需要客戶端和服務端消耗資源進行維護;更糟糕的是,開發者需要手動的把這些資源進行分區,部署到不同的域名下。域名分區的數量太大或太小都會影響性能,但如何確定最優的分區數量並是個很好回答的問題,因為頁面中資源的數量、客戶端連接的可用帶寬及延遲等都會影響分區數量的合理性。
要想確定合適的域名分區數量,只能用最原始的方式從最小分區開始不斷的測試,觀察不同分區數目對應用的影響,然后選擇最優的一個值作為固定分區數目。
9.3連接與合並
最快的請求就是不用請求,不管什么協議或什么類型的應用,減少請求次數總是最好的優化手段。如果每個資源都是必不可少的,那你可以考慮把這些資源打包到一塊,通過一次網絡請求獲取。連接和拼合技術屬於以內容為中心的應用層優化,通過減少網絡往返開銷,可以明顯的提升性能。可是這些技術也需要額外的處理、部署和編碼,也會帶來額外的復雜性。把多個資源打包到一起,也可能給緩存帶來負擔,一些細微的更新都需要重新請求資源並緩存,有時候當前頁面並不需要文件中的其他一些資源,這都會影響頁面的執行速度。
在采用連接與合並技術時,雖然會減少網絡往返開銷和提升性能,但也會增加應用的復雜度,以致緩存、更新、執行速度、渲染頁面等問題。所以,采用這種優化時,應綜合考慮,尋求一種最佳的文件打包粒度。
9.4嵌入資源
嵌入資源也是一種很常見的優化方法,把資源嵌入文檔可以減少請求的次數。嵌入頁面的資源適合特別小的,使用次數很少,最好是一次的資源。實踐中,一個經驗規則是只考慮嵌入1-2KB以下的資源,因為小於這個標准的資源會導致比它自身更高的HTTP開銷。然而,如果嵌入資源頻繁變更,也會導致宿主文檔的無效緩存率升高。如果應用中要使用很小的、個別的文件,在考慮是否嵌入時,可以參考以下建議:
-
如果文件很小,而且只有個別頁面使用,可以考慮嵌入;
-
如果文件很小,但需要在多個頁面中使用,應該考慮集中打包;
-
如果文件經常需要更新,就不要嵌入了;
-
通過減少HTTP cookie的大小將協議開銷最小化。
參考資料:
《WEB性能權威指南》
《HTTP/1.1與HTTP/1.0的區別》
《HTTP 1.1與HTTP 1.0的比較》
http://www.faqs.org/rfcs/rfc1945.html
http://www.faqs.org/rfcs/rfc2616.html