全棧工程師眼中的HTTP

  

  HTTP,是Web工程師天天打交道最多的一個基本協議。不少工做流程、性能優化都圍繞HTTP協議來進行,可是咱們對HTTP的理解是否全面呢?若是前端工程師和後臺工程師坐在一塊兒玩捉鬼遊戲,他們對HTTP的描述可能會大相徑庭,從這兩個角色的視角看過去,HTTP呈現出大相徑庭的形態。html

HTTP簡介

超文本傳輸協議(HyperText Transfer Protocol,HTTP)是互聯網上應用最爲普遍的一種網絡協議。設計HTTP的最初目的是提供一種發佈和接收HTML頁面的方法。OSI模型1義了整個世界計算機相互鏈接的標準,總共分爲7層,其中最上層(也就是第7層)就是應用層,HTTP、HTTPS、FTP、TELNET、SSH、SMTP和POP3都屬於應用層。這是軟件工程師最關心的一層。前端

 

SI模型越靠近底層,就越接近硬件。在HTTP協議中,並無規定必須使用它或它支持的層。事實上,HTTP能夠在任何互聯網協議或其餘網絡上實現。HTTP假定其下層協議提供可靠的傳輸,所以,任何可以提供這種保證的協議均可以被其使用,也就是其在TCP/IP協議族使用TCP做爲其傳輸層。面試

                                 OSI模型,圖片來自維基百科。數據庫

 

關於HTTP版本

  HTTP已經演化出了不少版本,它們中的大部分都是向下兼容的。客戶端在請求的開始告訴服務器它採用的協議版本號,然後者則在響應中採用相同或者更早的協議版本。後端

  當前應用最普遍的HTTP版本爲HTTP/1.1,它自從1999年發佈以來,距寫做本書時已有16年的時間。比起HTTP/1,它增長了幾個重要特性,好比緩存處理(在下一章介紹)和持續鏈接,以及其餘一些性能優化。瀏覽器

  2015年2月,HTTP/2正式發佈。新的HTTP版本有一些重大更新,除了一如既往地向下兼容HTTP/1之外,還有一些優化,好比減少網絡傳輸延遲,並簡化服務器向瀏覽器傳輸內容的過程。主流的服務器(Apache、Nginx等)和瀏覽器(Firefox、Chrome、Safari以及iOS和Android的瀏覽器等)的最新版都已經支持HTTP/2,剩下的就須要網站管理員把服務器升級到最新版了。緩存

 

例子

下面是一個HTTP客戶端與服務器之間會話的例子,運行於www.google.com,端口80。性能優化

 

客戶端首先發出請求。服務器

 

GET / HTTP/1.1 Host:www.google.com

 

第一行指定方法、資源路徑、協議版本。固然這是一個簡化後的例子,實際請求中還會有當前Google登陸帳戶的cookie、HTTPS頭、瀏覽器接受何種類型的壓縮格式和UA2代碼等。cookie

 

服務器隨之應答。

 

HTTP/1.1 200 OK Content-Length: 3059 Server: GWS/2.0 Date: Mon, 20 Apr 2015 20:30:45 GMT Content-Type: text/html Cache-control: private Set-cookie: PREF=ID=73d4aef52e57bae9:TM=1042253044:LM=1042253044:S= SMCc_HRPCQiqy X9j; expires=Sun, 17-Jan-2038 19:14:07 GMT; path=/; domain=.google.com Connection: keep-alive

 

在這一串HTTPS頭以後,會緊跟着一個空行,而後是HTML格式的文本組成的Google主頁。

介紹完關於HTTP的基本知識,咱們來分別看看前端工程師和後臺工程師分別是怎樣看待這個最熟悉的小夥伴的。

 

前端視角

前端工程師的職責之一是,讓網站又快又好地展示在用戶的瀏覽器中。

從這個角度來講,對HTTP的理解是這樣的:打開HttpWatch3,而後隨意訪問一個網站。HttpWatch會按照瀏覽器請求的次序,列出打開這個網站的時候發生的請求細節。

  • 發出的請求列表。

  • 每一個請求的開始時間。

  • 每一個請求從開始到結束花費的時間。

  • 每一個請求的類型(好比是文本、CSS、JS,仍是圖片或者字體等)。

  • 每一個請求的狀態碼(好比是200、仍是from cache、30四、404等)。

  • 每一個請求產生的流量消耗。

  • 每一個請求gzip壓縮前的體積,以及在本地gzip解壓後的體積。

經過查看站點的HTTP請求信息,能夠獲得不少優化信息。每個前端工程師都知道的基本優化方法是:儘可能減小同一域下的HTTP請求數,以及儘可能減小每個資源的體積。

 

儘可能減小同一域下的HTTP請求數

  瀏覽器經常限定了對同一域名發起的併發鏈接數的上限。IE6/7和Firefox2的設計規則是,同時只能對一個域名發起兩個併發鏈接。新版本的各類瀏覽器廣泛把這一上限設定爲4至8個。若是瀏覽器須要對某個域進行更多的鏈接,則須要在用完了當前鏈接以後,重複使用或者從新創建TCP鏈接。

 

QQ空間的CSS貼圖由程序自動生成,保證最佳的圖片質量、最合理的圖片擺放和最小的體積。

  因爲瀏覽器針對資源的域名限制併發鏈接數,而不是針對瀏覽器地址欄中的頁面域名,因此不少靜態資源能夠放在其餘域名下(不一樣的子域名也被認爲是不一樣的域名)。若是您只有一臺服務器,能夠把這些不一樣的域名同時指向一個IP,也就提升了對這臺服務器的併發鏈接數限制(不過要當心服務器壓力過大)。

  把靜態資源放在非主域名下,這種作法除了能夠增長瀏覽器併發,還有一個好處是,減小HTTP請求中攜帶的沒必要要的cookie數據。cookie是某些網站爲了辨別用戶身份而儲存在用戶瀏覽器中的數據。cookie的做用域是整個域名,也就是說若是某個cookie存放在google.com域名下,那麼對於google.com域名下的全部HTTP請求頭都會帶上cookie數據。若是Google把全部的資源都放在google.com下,那麼全部資源的請求都會帶上cookie數據。對於靜態資源來講,這是毫無必要的,由於這對帶寬和連接速度都形成了影響。因此咱們通常把靜態資源放在單獨的域名下。

  除此以外,前端工程師常常作的優化是合併同一域名下的資源,好比把多個CSS合併爲一個CSS,或者將圖片組合爲CSS貼圖。

  還有一些優化建議是省掉沒必要要的HTTP請求,好比內嵌小型CSS、內嵌小型JavaScript、設置緩存,以及減小重定向。這些作法雖然各不相同,可是若是瞭解HTTP請求的過程,就知道這些優化方法的最終目的都是最大化利用有限的請求數。

 

儘可能減小每個資源的體積

咱們不光要限制請求數,還要儘可能減小每個資源的體積。由於資源的體積越大,在傳輸中消耗的流量就越多,等待時間也越久。

  在面試應聘者的時候,我會問的一個基礎題目是「經常使用的圖片格式有哪些,它們的使用場景是什麼」。若是能選擇合適的圖片格式,就可以用更小的體積,達到更好的顯示效果。對圖片格式的敏感,能反映出工程師對帶寬和速度的不懈追求。

  此外,對於比較大的文本資源,必須開啓gzip壓縮。由於gzip對於含有重複「單詞」的文本文件,壓縮率很是高,能有效提升傳輸過程。

  對於一個CSS資源的請求耗時,我想說明兩個細節。

  • 這個CSS資源請求的體積是36.4KB(這是gzip壓縮過的體積),解壓縮以後,CSS內容其實是263KB,能夠算出壓縮後體積是原來的13.8%。

  • 整個鏈接的創建花費了30%的時間,發出請求到等待收到第一個字節回覆花費了20%的時間,下載CSS資源的內容花費了50%的時間。

若是沒有設置gzip,下載這個CSS文件會須要好幾倍的時間。

 

後臺視角

  前端工程師對HTTP的關注點在於儘可能減小同一域下的HTTP請求數,以及儘可能減小每個資源的體積。與之不一樣,後臺工程師對於HTTP的關注在於讓服務器儘快響應請求,以及減小請求對服務器的開銷。

  後臺工程師知道,瀏覽器限定對某個域的併發鏈接數,很大程度上是瀏覽器對服務器的一種保護行爲。瀏覽器做爲一種善意的客戶端,爲了保護服務器不被大量的併發請求弄得崩潰,才限定了對同一個域的最大併發鏈接數。而一些「惡意」的客戶端,好比一些下載軟件,它做爲一個HTTP協議客戶端,不考慮到服務器的壓力,而發起大量的併發請求(雖然用戶感受到下載速度很快),可是因爲它違反了規則,因此常常被服務器端「防範」和屏蔽。

 

那麼爲何服務器對併發請求數這麼敏感?

  雖然服務器的多個進程看上去是在同時運行,可是對於單核CPU的架構來講,其實是計算機系統同一段時間內,以進程的形式,將多個程序加載到存儲器中,並藉由時間共享,以在一個處理器上表現出同時運行的感受。因爲在操做系統中,生成進程、銷燬進程、進程間切換都很消耗CPU和內存,所以當負載高時,性能會明顯下降。

 

提升服務器的請求處理能力

  在早期系統中(如Linux 2.4之前),進程是基本運做單位。在支持線程的系統(Linux2.6)中,線程纔是基本的運做單位,而進程只是線程的容器。因爲線程開銷明顯小於進程,並且部分資源還能夠共享,所以效率較高。

  Apache是市場份額最大的服務器,超過50%的網站運行在Apache上。Apache 經過模塊化的設計來適應各類環境,其中一個模塊叫作多處理模塊(MPM),專門用來處理多請求的狀況。Apache安裝在不一樣系統上的時候會調用不一樣的默認MPM,咱們不用關心具體的細節,只須要了解Unix上默認的MPM是prefork。爲了優化,咱們能夠改爲worker模式。

  prefork和worker模式的最大區別就是,prefork的一個進程維持一個鏈接,而worker的一個線程維持一個鏈接。因此prefork更穩定但內存消耗也更大,worker沒有那麼穩定,由於不少鏈接的線程共享一個進程,當一個線程崩潰的時候,整個進程和全部線程一塊兒死掉。可是worker的內存使用要比prefork低得多,因此很適合用在高HTTP請求的服務器上。

  近年來Nginx愈來愈受到市場的青睞。在高鏈接併發的狀況下,Nginx是Apache服務器不錯的替代品或者補充:一方面是Nginx更加輕量級,佔用更少的資源和內存;另外一方面是Nginx 處理請求是異步非阻塞的,而Apache 則是阻塞型的,在高併發下Nginx 能保持低資源、低消耗和高性能。

  因爲Apache和Nginx各有所長,因此常常的搭配是Nginx處理前端併發,Apache處理後臺請求。

  值得一提的是,新秀Node.js也是採用基於事件的異步非阻塞方式處理請求,因此在處理高併發請求上有自然的優點。

 

DDoS攻擊

DDoS是Distributed Denial of Service的縮寫,DDoS攻擊翻譯成中文就是「分佈式拒絕服務」攻擊。

  簡單來講,就是黑客入侵併控制了大量用戶的計算機(俗稱「肉雞」),而後在這些計算機上安裝了DDoS攻擊軟件。咱們知道瀏覽器做爲一種「善意」的客戶端,限制了HTTP併發鏈接數。可是DDoS就沒有這樣的道德準則,每個DDoS攻擊客戶端均可以自由設置TCP/IP併發鏈接數,而且鏈接上服務器以後,它不會立刻斷開鏈接,而是保持這個鏈接一段時間,直到同時鏈接的數量大於最大鏈接數,才斷開以前的鏈接。

  就這樣,攻擊者經過海量的請求,讓目標服務器癱瘓,沒法響應正常的用戶請求,以此達到攻擊的效果。

  對於這樣的攻擊,幾乎沒有什麼特別好的防禦方法。除了增長帶寬和提升服務器能同時接納的客戶數,另外一種方法就是讓首頁靜態化。DDoS攻擊者喜歡攻擊的頁面通常是會對數據庫進行寫操做的頁面,這樣的頁面沒法靜態化,服務器更容易宕機。DDoS攻擊者通常不會攻擊靜態化的頁面或者圖片,由於靜態資源對服務器壓力小,並且可以部署在CDN上。

  這裏介紹的只是最簡單的TCP/IP攻擊,而DDoS是一個概稱,具體來講,有各類攻擊方式,好比CC攻擊、SYN攻擊、NTP攻擊、TCP攻擊和DNS攻擊等。

 

BigPipe

前端跟後端在HTTP上也能有交集,BigPipe就是一個例子。

  現有的HTTP數據請求流程是:客戶端創建鏈接,服務器贊成鏈接,客戶端發起請求,服務器返回數據,客戶端接受並處理數據。這個處理流程有兩個問題。

  現有的阻塞模型,黃色表明服務器生成頁面,白色表明網絡傳輸,紫色表明瀏覽器渲染頁面。

 

  第一,HTTP協議的底層是TCP/IP,而TCP/IP規定3次握手才創建一次鏈接。每個新增的請求都要從新創建TCP/IP鏈接,從而消耗服務器的資源,而且浪費鏈接時間。對於幾種不一樣的服務器程序(Apache、Nginx和Node.js等),所消耗的內存和CPU資源也不太同樣,可是新的鏈接沒法避免,沒有從本質上解決問題。

  第二個問題是,在現有的阻塞模型中,服務器計算生成頁面須要時間。等服務器徹底生成好整個頁面,纔開始網絡傳輸,網絡傳輸也須要時間。整個頁面都徹底傳輸到瀏覽器中以後,在瀏覽器中最後渲染仍是須要時間。三者是阻塞式的,每個環節都在等上一個環節100%完成纔開始。頁面做爲一個總體,須要完整地經歷3個階段才能出如今瀏覽器中,效率很低。

  BigPipe是Facebook公司科學家Changhao Jiang發明的一種非阻塞式模型,這種模型能完美解決上面的兩個問題。

  通俗來解釋,BigPipe首先把HTML頁面分爲不少部分,而後在服務器和瀏覽器之間創建一條管道(BigPipe就是「大管道」的意思),HTML的不一樣部分能夠源源不斷地從服務器傳輸到瀏覽器。BigPipe首先輸送的內容是框架性HTML結構,這個框架結構可能會定義每一個Pagelet模塊的位置和寬高,可是這些pagelet都是空的,就像只有鋼筋混泥土骨架的毛坯房。

BigPipe頁面的渲染流程。

 

服務器傳輸完框架性HTML結構以後,對瀏覽器說:「我這個請求還沒結束,咱們保持這個鏈接不要斷開,不過您能夠先用我給您的這部分來渲染。」

  因此瀏覽器就開始渲染這個「不完整的HTML」,毛坯房頁面很快出如今用戶眼前,具體的頁面模塊都顯示「正在加載」。

  接下來管道里源源不斷地傳輸過來不少模塊,這時候最開始加載在服務器中的JS代碼開始工做,它會負責把每個模塊依次渲染到頁面上。

  在用戶的感知上,頁面很是快地出如今眼前,可是全部的模塊都顯示正在加載中,而後主要的區域(好比重要的用戶動態)優先出現,接下來是logo、邊欄和各類掛件等。

  爲何BigPipe可以讓服務器對瀏覽器說「我這個請求還沒結束,咱們保持這個鏈接不要斷開」呢?答案是HTTP1.1的分塊傳輸編碼。

 

  HTTP 1.1引入分塊傳輸編碼,容許服務器爲動態生成的內容維持HTTP持久連接。若是一個HTTP消息(請求消息或應答消息)的Transfer-Encoding消息頭的值爲chunked,那麼消息體由數量不肯定的塊組成——也就是說想發送多少塊就發送多少塊——並以最後一個大小爲0的塊爲結束。

  實現這個架構須要深入理解HTTP 1.1的規則,並且要有前端的知識。在我看來,這就是一個極佳的全棧工程師改變世界的例子。

  截止寫書時,Chrome、Safari和Opera已經支持HTTP/2並默認開啓,它容許服務器向瀏覽器「推送」內容。也就是說,返回的條目數能夠比請求的條目數多,這樣服務器能夠在一開始就推送全部它認爲瀏覽器「應該須要」的資源,而不須要瀏覽器接受並解析完HTML頁面纔開始請求下載CSS、JavaScript等。並且,後面的請求能夠複用以前已經創建的底層鏈接。

 

【全棧工程師眼中的HTTP】一文摘自《Web全棧工程師的自我修養》一書,由異步社區出版。本書做者餘果是騰訊公司高級工程師,在前端、後端和APP開發方面都有豐富的經驗,在本書中分享了全棧工程師的技能要求、核心競爭力、將來發展方向、對移動端的思考。除此以外,本書還詳細記錄了做者從零開始、學習成長的心路歷程。

相關文章
相關標籤/搜索