HTTP,是Web工程師每天打交道最多的一個(gè)基本協(xié)議。很多工作流程、性能優(yōu)化都圍繞HTTP協(xié)議來進(jìn)行,但是我們對HTTP的理解是否全面呢?如果前端工程師和后臺(tái)工程師坐在一起玩捉鬼游戲,他們對HTTP的描述可能會(huì)截然不同,從這兩個(gè)角色的視角看過去,HTTP呈現(xiàn)出截然不同的形態(tài)。
HTTP簡介
超文本傳輸協(xié)議(HyperText Transfer Protocol,HTTP)是互聯(lián)網(wǎng)上應(yīng)用最為廣泛的一種網(wǎng)絡(luò)協(xié)議。設(shè)計(jì)HTTP的最初目的是提供一種發(fā)布和接收HTML頁面的方法。OSI模型1義了整個(gè)世界計(jì)算機(jī)相互連接的標(biāo)準(zhǔn),總共分為7層,其中最上層(也就是第7層)就是應(yīng)用層,HTTP、HTTPS、FTP、TELNET、SSH、SMTP和POP3都屬于應(yīng)用層。這是軟件工程師最關(guān)心的一層。
SI模型越靠近底層,就越接近硬件。在HTTP協(xié)議中,并沒有規(guī)定必須使用它或它支持的層。事實(shí)上,HTTP可以在任何互聯(lián)網(wǎng)協(xié)議或其他網(wǎng)絡(luò)上實(shí)現(xiàn)。HTTP假定其下層協(xié)議提供可靠的傳輸,因此,任何能夠提供這種保證的協(xié)議都可以被其使用,也就是其在TCP/IP協(xié)議族使用TCP作為其傳輸層。
關(guān)于HTTP版本
HTTP已經(jīng)演化出了很多版本,它們中的大部分都是向下兼容的。客戶端在請求的開始告訴服務(wù)器它采用的協(xié)議版本號,而后者則在響應(yīng)中采用相同或者更早的協(xié)議版本。
當(dāng)前應(yīng)用最廣泛的HTTP版本為HTTP/1.1,它自從1999年發(fā)布以來,距寫作本書時(shí)已有16年的時(shí)間。比起HTTP/1,它增加了幾個(gè)重要特性,比如緩存處理(在下一章介紹)和持續(xù)連接,以及其他一些性能優(yōu)化。
2015年2月,HTTP/2正式發(fā)布。新的HTTP版本有一些重大更新,除了一如既往地向下兼容HTTP/1以外,還有一些優(yōu)化,比如減小網(wǎng)絡(luò)傳輸延遲,并簡化服務(wù)器向?yàn)g覽器傳輸內(nèi)容的過程。主流的服務(wù)器(Apache、Nginx等)和瀏覽器(Firefox、Chrome、Safari以及iOS和Android的瀏覽器等)的最新版都已經(jīng)支持HTTP/2,剩下的就需要網(wǎng)站管理員把服務(wù)器升級到最新版了。
例子
下面是一個(gè)HTTP客戶端與服務(wù)器之間會(huì)話的例子,運(yùn)行于www.google.com,端口80。
客戶端首先發(fā)出請求。
第一行指定方法、資源路徑、協(xié)議版本。當(dāng)然這是一個(gè)簡化后的例子,實(shí)際請求中還會(huì)有當(dāng)前Google登錄賬戶的cookie、HTTPS頭、瀏覽器接受何種類型的壓縮格式和UA2代碼等。
服務(wù)器隨之應(yīng)答。
在這一串HTTPS頭之后,會(huì)緊跟著一個(gè)空行,然后是HTML格式的文本組成的Google主頁。
介紹完關(guān)于HTTP的基本知識,我們來分別看看前端工程師和后臺(tái)工程師分別是怎樣看待這個(gè)最熟悉的小伙伴的。
前端視角
前端工程師的職責(zé)之一是,讓網(wǎng)站又快又好地展現(xiàn)在用戶的瀏覽器中。
從這個(gè)角度來說,對HTTP的理解是這樣的:打開HttpWatch3,然后隨意訪問一個(gè)網(wǎng)站。HttpWatch會(huì)按照瀏覽器請求的次序,列出打開這個(gè)網(wǎng)站的時(shí)候發(fā)生的請求細(xì)節(jié)。
發(fā)出的請求列表。
每個(gè)請求的開始時(shí)間。
每個(gè)請求從開始到結(jié)束花費(fèi)的時(shí)間。
每個(gè)請求的類型(比如是文本、CSS、JS,還是圖片或者字體等)。
每個(gè)請求的狀態(tài)碼(比如是200、還是from cache、304、404等)。
每個(gè)請求產(chǎn)生的流量消耗。
每個(gè)請求gzip壓縮前的體積,以及在本地gzip解壓后的體積。
通過查看站點(diǎn)的HTTP請求信息,可以得到很多優(yōu)化信息。每一個(gè)前端工程師都知道的基本優(yōu)化方法是:盡量減少同一域下的HTTP請求數(shù),以及盡量減少每一個(gè)資源的體積。
盡量減少同一域下的HTTP請求數(shù)
瀏覽器常常限定了對同一域名發(fā)起的并發(fā)連接數(shù)的上限。IE6/7和Firefox2的設(shè)計(jì)規(guī)則是,同時(shí)只能對一個(gè)域名發(fā)起兩個(gè)并發(fā)連接。新版本的各種瀏覽器普遍把這一上限設(shè)定為4至8個(gè)。如果瀏覽器需要對某個(gè)域進(jìn)行更多的連接,則需要在用完了當(dāng)前連接之后,重復(fù)使用或者重新建立TCP連接。
QQ空間的CSS貼圖由程序自動(dòng)生成,保證最佳的圖片質(zhì)量、最合理的圖片擺放和最小的體積。
由于瀏覽器針對資源的域名限制并發(fā)連接數(shù),而不是針對瀏覽器地址欄中的頁面域名,所以很多靜態(tài)資源可以放在其他域名下(不同的子域名也被認(rèn)為是不同的域名)。如果您只有一臺(tái)服務(wù)器,可以把這些不同的域名同時(shí)指向一個(gè)IP,也就提高了對這臺(tái)服務(wù)器的并發(fā)連接數(shù)限制(不過要小心服務(wù)器壓力過大)。
把靜態(tài)資源放在非主域名下,這種做法除了可以增加瀏覽器并發(fā),還有一個(gè)好處是,減少HTTP請求中攜帶的不必要的cookie數(shù)據(jù)。cookie是某些網(wǎng)站為了辨別用戶身份而儲(chǔ)存在用戶瀏覽器中的數(shù)據(jù)。cookie的作用域是整個(gè)域名,也就是說如果某個(gè)cookie存放在google.com域名下,那么對于google.com域名下的所有HTTP請求頭都會(huì)帶上cookie數(shù)據(jù)。如果Google把所有的資源都放在google.com下,那么所有資源的請求都會(huì)帶上cookie數(shù)據(jù)。對于靜態(tài)資源來說,這是毫無必要的,因?yàn)檫@對帶寬和鏈接速度都造成了影響。所以我們一般把靜態(tài)資源放在單獨(dú)的域名下。
除此之外,前端工程師經(jīng)常做的優(yōu)化是合并同一域名下的資源,比如把多個(gè)CSS合并為一個(gè)CSS,或者將圖片組合為CSS貼圖。
還有一些優(yōu)化建議是省掉不必要的HTTP請求,比如內(nèi)嵌小型CSS、內(nèi)嵌小型JavaScript、設(shè)置緩存,以及減少重定向。這些做法雖然各不相同,但是如果了解HTTP請求的過程,就知道這些優(yōu)化方法的最終目的都是最大化利用有限的請求數(shù)。
盡量減少每一個(gè)資源的體積
我們不光要限制請求數(shù),還要盡量減少每一個(gè)資源的體積。因?yàn)橘Y源的體積越大,在傳輸中消耗的流量就越多,等待時(shí)間也越久。
在面試應(yīng)聘者的時(shí)候,我會(huì)問的一個(gè)基礎(chǔ)題目是“常用的圖片格式有哪些,它們的使用場景是什么”。如果能選擇合適的圖片格式,就能夠用更小的體積,達(dá)到更好的顯示效果。對圖片格式的敏感,能反映出工程師對帶寬和速度的不懈追求。
此外,對于比較大的文本資源,必須開啟gzip壓縮。因?yàn)間zip對于含有重復(fù)“單詞”的文本文件,壓縮率非常高,能有效提高傳輸過程。
對于一個(gè)CSS資源的請求耗時(shí),我想說明兩個(gè)細(xì)節(jié)。
這個(gè)CSS資源請求的體積是36.4KB(這是gzip壓縮過的體積),解壓縮之后,CSS內(nèi)容實(shí)際上是263KB,可以算出壓縮后體積是原來的13.8%。
整個(gè)連接的建立花費(fèi)了30%的時(shí)間,發(fā)出請求到等待收到第一個(gè)字節(jié)回復(fù)花費(fèi)了20%的時(shí)間,下載CSS資源的內(nèi)容花費(fèi)了50%的時(shí)間。
如果沒有設(shè)置gzip,下載這個(gè)CSS文件會(huì)需要好幾倍的時(shí)間。
后臺(tái)視角
前端工程師對HTTP的關(guān)注點(diǎn)在于盡量減少同一域下的HTTP請求數(shù),以及盡量減少每一個(gè)資源的體積。與之不同,后臺(tái)工程師對于HTTP的關(guān)注在于讓服務(wù)器盡快響應(yīng)請求,以及減少請求對服務(wù)器的開銷。
后臺(tái)工程師知道,瀏覽器限定對某個(gè)域的并發(fā)連接數(shù),很大程度上是瀏覽器對服務(wù)器的一種保護(hù)行為。瀏覽器作為一種善意的客戶端,為了保護(hù)服務(wù)器不被大量的并發(fā)請求弄得崩潰,才限定了對同一個(gè)域的最大并發(fā)連接數(shù)。而一些“惡意”的客戶端,比如一些下載軟件,它作為一個(gè)HTTP協(xié)議客戶端,不考慮到服務(wù)器的壓力,而發(fā)起大量的并發(fā)請求(雖然用戶感覺到下載速度很快),但是由于它違反了規(guī)則,所以經(jīng)常被服務(wù)器端“防范”和屏蔽。
那么為什么服務(wù)器對并發(fā)請求數(shù)這么敏感?
雖然服務(wù)器的多個(gè)進(jìn)程看上去是在同時(shí)運(yùn)行,但是對于單核CPU的架構(gòu)來說,實(shí)際上是計(jì)算機(jī)系統(tǒng)同一段時(shí)間內(nèi),以進(jìn)程的形式,將多個(gè)程序加載到存儲(chǔ)器中,并借由時(shí)間共享,以在一個(gè)處理器上表現(xiàn)出同時(shí)運(yùn)行的感覺。由于在操作系統(tǒng)中,生成進(jìn)程、銷毀進(jìn)程、進(jìn)程間切換都很消耗CPU和內(nèi)存,因此當(dāng)負(fù)載高時(shí),性能會(huì)明顯降低。
提高服務(wù)器的請求處理能力
在早期系統(tǒng)中(如Linux 2.4以前),進(jìn)程是基本運(yùn)作單位。在支持線程的系統(tǒng)(Linux2.6)中,線程才是基本的運(yùn)作單位,而進(jìn)程只是線程的容器。由于線程開銷明顯小于進(jìn)程,而且部分資源還可以共享,因此效率較高。
Apache是市場份額最大的服務(wù)器,超過50%的網(wǎng)站運(yùn)行在Apache上。Apache 通過模塊化的設(shè)計(jì)來適應(yīng)各種環(huán)境,其中一個(gè)模塊叫做多處理模塊(MPM),專門用來處理多請求的情況。Apache安裝在不同系統(tǒng)上的時(shí)候會(huì)調(diào)用不同的默認(rèn)MPM,我們不用關(guān)心具體的細(xì)節(jié),只需要了解Unix上默認(rèn)的MPM是prefork。為了優(yōu)化,我們可以改成worker模式。
prefork和worker模式的最大區(qū)別就是,prefork的一個(gè)進(jìn)程維持一個(gè)連接,而worker的一個(gè)線程維持一個(gè)連接。所以prefork更穩(wěn)定但內(nèi)存消耗也更大,worker沒有那么穩(wěn)定,因?yàn)楹芏噙B接的線程共享一個(gè)進(jìn)程,當(dāng)一個(gè)線程崩潰的時(shí)候,整個(gè)進(jìn)程和所有線程一起死掉。但是worker的內(nèi)存使用要比prefork低得多,所以很適合用在高HTTP請求的服務(wù)器上。
近年來Nginx越來越受到市場的青睞。在高連接并發(fā)的情況下,Nginx是Apache服務(wù)器不錯(cuò)的替代品或者補(bǔ)充:一方面是Nginx更加輕量級,占用更少的資源和內(nèi)存;另一方面是Nginx 處理請求是異步非阻塞的,而Apache 則是阻塞型的,在高并發(fā)下Nginx 能保持低資源、低消耗和高性能。
由于Apache和Nginx各有所長,所以經(jīng)常的搭配是Nginx處理前端并發(fā),Apache處理后臺(tái)請求。
值得一提的是,新秀Node.js也是采用基于事件的異步非阻塞方式處理請求,所以在處理高并發(fā)請求上有天然的優(yōu)勢。
DDoS攻擊
DDoS是Distributed Denial of Service的縮寫,DDoS攻擊翻譯成中文就是“分布式拒絕服務(wù)”攻擊。
簡單來說,就是黑客入侵并控制了大量用戶的計(jì)算機(jī)(俗稱“肉雞”),然后在這些計(jì)算機(jī)上安裝了DDoS攻擊軟件。我們知道瀏覽器作為一種“善意”的客戶端,限制了HTTP并發(fā)連接數(shù)。但是DDoS就沒有這樣的道德準(zhǔn)則,每一個(gè)DDoS攻擊客戶端都可以自由設(shè)置TCP/IP并發(fā)連接數(shù),并且連接上服務(wù)器之后,它不會(huì)馬上斷開連接,而是保持這個(gè)連接一段時(shí)間,直到同時(shí)連接的數(shù)量大于最大連接數(shù),才斷開之前的連接。
就這樣,攻擊者通過海量的請求,讓目標(biāo)服務(wù)器癱瘓,無法響應(yīng)正常的用戶請求,以此達(dá)到攻擊的效果。
對于這樣的攻擊,幾乎沒有什么特別好的防護(hù)方法。除了增加帶寬和提高服務(wù)器能同時(shí)接納的客戶數(shù),另一種方法就是讓首頁靜態(tài)化。DDoS攻擊者喜歡攻擊的頁面一般是會(huì)對數(shù)據(jù)庫進(jìn)行寫操作的頁面,這樣的頁面無法靜態(tài)化,服務(wù)器更容易宕機(jī)。DDoS攻擊者一般不會(huì)攻擊靜態(tài)化的頁面或者圖片,因?yàn)殪o態(tài)資源對服務(wù)器壓力小,而且能夠部署在CDN上。
這里介紹的只是最簡單的TCP/IP攻擊,而DDoS是一個(gè)概稱,具體來說,有各種攻擊方式,比如CC攻擊、SYN攻擊、NTP攻擊、TCP攻擊和DNS攻擊等。
BigPipe
前端跟后端在HTTP上也能有交集,BigPipe就是一個(gè)例子。
現(xiàn)有的HTTP數(shù)據(jù)請求流程是:客戶端建立連接,服務(wù)器同意連接,客戶端發(fā)起請求,服務(wù)器返回?cái)?shù)據(jù),客戶端接受并處理數(shù)據(jù)。這個(gè)處理流程有兩個(gè)問題。
現(xiàn)有的阻塞模型,黃色代表服務(wù)器生成頁面,白色代表網(wǎng)絡(luò)傳輸,紫色代表瀏覽器渲染頁面。
第一,HTTP協(xié)議的底層是TCP/IP,而TCP/IP規(guī)定3次握手才建立一次連接。每一個(gè)新增的請求都要重新建立TCP/IP連接,從而消耗服務(wù)器的資源,并且浪費(fèi)連接時(shí)間。對于幾種不同的服務(wù)器程序(Apache、Nginx和Node.js等),所消耗的內(nèi)存和CPU資源也不太一樣,但是新的連接無法避免,沒有從本質(zhì)上解決問題。
第二個(gè)問題是,在現(xiàn)有的阻塞模型中,服務(wù)器計(jì)算生成頁面需要時(shí)間。等服務(wù)器完全生成好整個(gè)頁面,才開始網(wǎng)絡(luò)傳輸,網(wǎng)絡(luò)傳輸也需要時(shí)間。整個(gè)頁面都完全傳輸?shù)綖g覽器中之后,在瀏覽器中最后渲染還是需要時(shí)間。三者是阻塞式的,每一個(gè)環(huán)節(jié)都在等上一個(gè)環(huán)節(jié)100%完成才開始。頁面作為一個(gè)整體,需要完整地經(jīng)歷3個(gè)階段才能出現(xiàn)在瀏覽器中,效率很低。
BigPipe是Facebook公司科學(xué)家Changhao Jiang發(fā)明的一種非阻塞式模型,這種模型能完美解決上面的兩個(gè)問題。
通俗來解釋,BigPipe首先把HTML頁面分為很多部分,然后在服務(wù)器和瀏覽器之間建立一條管道(BigPipe就是“大管道”的意思),HTML的不同部分可以源源不斷地從服務(wù)器傳輸?shù)綖g覽器。BigPipe首先輸送的內(nèi)容是框架性HTML結(jié)構(gòu),這個(gè)框架結(jié)構(gòu)可能會(huì)定義每個(gè)Pagelet模塊的位置和寬高,但是這些pagelet都是空的,就像只有鋼筋混泥土骨架的毛坯房。
BigPipe頁面的渲染流程。
服務(wù)器傳輸完框架性HTML結(jié)構(gòu)之后,對瀏覽器說:“我這個(gè)請求還沒結(jié)束,我們保持這個(gè)連接不要斷開,不過您可以先用我給您的這部分來渲染。”
所以瀏覽器就開始渲染這個(gè)“不完整的HTML”,毛坯房頁面很快出現(xiàn)在用戶眼前,具體的頁面模塊都顯示“正在加載”。
接下來管道里源源不斷地傳輸過來很多模塊,這時(shí)候最開始加載在服務(wù)器中的JS代碼開始工作,它會(huì)負(fù)責(zé)把每一個(gè)模塊依次渲染到頁面上。
在用戶的感知上,頁面非常快地出現(xiàn)在眼前,但是所有的模塊都顯示正在加載中,然后主要的區(qū)域(比如重要的用戶動(dòng)態(tài))優(yōu)先出現(xiàn),接下來是logo、邊欄和各種掛件等。
為什么BigPipe能夠讓服務(wù)器對瀏覽器說“我這個(gè)請求還沒結(jié)束,我們保持這個(gè)連接不要斷開”呢?答案是HTTP1.1的分塊傳輸編碼。
HTTP 1.1引入分塊傳輸編碼,允許服務(wù)器為動(dòng)態(tài)生成的內(nèi)容維持HTTP持久鏈接。如果一個(gè)HTTP消息(請求消息或應(yīng)答消息)的Transfer-Encoding消息頭的值為chunked,那么消息體由數(shù)量不確定的塊組成——也就是說想發(fā)送多少塊就發(fā)送多少塊——并以最后一個(gè)大小為0的塊為結(jié)束。
實(shí)現(xiàn)這個(gè)架構(gòu)需要深刻理解HTTP 1.1的規(guī)則,而且要有前端的知識。在我看來,這就是一個(gè)極佳的全棧工程師改變世界的例子。
截止寫書時(shí),Chrome、Safari和Opera已經(jīng)支持HTTP/2并默認(rèn)開啟,它允許服務(wù)器向?yàn)g覽器“推送”內(nèi)容。也就是說,返回的條目數(shù)可以比請求的條目數(shù)多,這樣服務(wù)器可以在一開始就推送所有它認(rèn)為瀏覽器“應(yīng)該需要”的資源,而不需要瀏覽器接受并解析完HTML頁面才開始請求下載CSS、JavaScript等。而且,后面的請求可以復(fù)用之前已經(jīng)建立的底層連接。
-
工程師
+關(guān)注
關(guān)注
59文章
1570瀏覽量
68514 -
HTTP
+關(guān)注
關(guān)注
0文章
505瀏覽量
31212 -
前端
+關(guān)注
關(guān)注
1文章
192瀏覽量
17750
發(fā)布評論請先 登錄
相關(guān)推薦
評論