影響HTTP性能的常見因素

影響HTTP性能的常見因素

咱們這裏討論HTTP性能是創建在一個最簡單模型之上就是單臺服務器的HTTP性能,固然對於大規模負載均衡集羣也適用畢竟這種集羣也是由多個HTTTP服務器的個體所組成。另外咱們也排除客戶端或者服務器自己負載太高或者HTTP協議實現的軟件使用不一樣的IO模型,另外咱們也忽略DNS解析過程和web應用程序開發自己的缺陷。node

從TCP/IP模型來看,HTTP下層就是TCP層,因此HTTP的性能很大程度上取決於TCP性能,固然若是是HTTPS的就還要加上TLS/SSL層,不過能夠確定的是HTTPS性能確定比HTTP要差,其通信過程這裏都很少說總之層數越多性能損耗就越嚴重。web

在上述條件下,最多見的影響HTTP性能的包括:算法

  • TCP鏈接創建,也就是三次握手階段數據庫

  • TCP慢啓動後端

  • TCP延遲確認瀏覽器

  • Nagle算法緩存

  • TIME_WAIT積累與端口耗盡bash

  • 服務端端口耗盡服務器

  • 服務端HTTP進程打開文件數量達到最大網絡

TCP鏈接創建

一般若是網絡穩定TCP鏈接創建不會消耗不少時間並且也都會在合理耗時範圍內完成三次握手過程,可是因爲HTTP是無狀態的屬於短鏈接,一次HTTP會話接受後會斷開TCP鏈接,一個網頁一般有不少資源這就意味着會進行不少次HTTP會話,而相對於HTTP會話來講TCP三次握手創建鏈接就會顯得太耗時了,固然能夠經過重用現有鏈接來減小TCP鏈接創建次數。

TCP慢啓動

TCP擁塞控制手段1,在TCP剛創建好以後的最初傳輸階段會限制鏈接的最大傳輸速度,若是數據傳輸成功,後續會逐步提升傳輸速度,這就TCP慢啓動。慢啓動限制了某一時刻能夠傳輸的IP分組2數量。那麼爲何會有慢啓動呢?主要是爲了不網絡由於大規模的數據傳輸而癱瘓,在互聯網上數據傳輸很重要的一個環節就是路由器,而路由器自己的速度並不快加之互聯網上的不少流量均可能發送過來要求其進行路由轉發,若是在某一時間內到達路由器的數據量遠大於其發送的數量那麼路由器在本地緩存耗盡的狀況下就會丟棄數據包,這種丟棄行爲被稱做擁塞,一個路由器出現這種情況就會影響不少條鏈路,嚴重的會致使大面積癱瘓。因此TCP通訊的任何一方須要進行擁塞控制,而慢啓動就是擁塞控制的其中一種算法或者叫作機制。
你設想一種狀況,咱們知道TCP有重傳機制,假設網絡中的一個路由器由於擁塞而出現大面積丟包狀況,做爲數據的發送方其TCP協議棧確定會檢測到這種狀況,那麼它就會啓動TCP重傳機制,並且該路由器影響的發送方確定不止你一個,那麼大量的發送方TCP協議棧都開始了重傳那這就等於在本來擁塞的網絡上發送更多的數據包,這就等於火上澆油。

經過上面的描述得出即使是在正常的網絡環境中,做爲HTTP報文的發送方與每一個請求創建TCP鏈接都會受到慢啓動影響,那麼又根據HTTP是短鏈接一次會話結束就會斷開,能夠想象客戶端發起HTTP請求剛獲取完web頁面上的一個資源,HTTP就斷開,有可能尚未經歷完TCP慢啓動過程這個TCP鏈接就斷開了,那web頁面其餘的後續資源還要繼續創建TCP鏈接,而每一個TCP鏈接都會有慢啓動階段,這個性能可想而知,因此爲了提高性能,咱們能夠開啓HTTP的持久鏈接也就是後面要說的keepalive。

另外咱們知道TCP中有窗口的概念,這個窗口在發送方和接收方都有,窗口的做用一方面保證了發送和接收方在管理分組時變得有序;另外在有序的基礎上能夠發送多個分組從而提升了吞吐量;還有一點就是這個窗口大小能夠調整,其目的是避免發送方發送數據的速度比接收方接收的速度快。窗口雖然解決了通訊雙發的速率問題,但是網絡中會通過其餘網絡設備,發送方怎麼知道路由器的接收能力呢?因此就有了上面介紹的擁塞控制。

TCP延遲確認

首先要知道什麼是確認,它的意思就是發送方給接收方發送一個TCP分段,接收方收到之後要回傳一個確認表示收到,若是在必定時間內發送方沒有收到該確認那麼就須要重發該TCP分段。

確認報文一般都比較小,也就是一個IP分組能夠承載多個確認報文,因此爲了不過多的發送小報文,那麼接收方在回傳確認報文的時候會等待看看有沒有發給接收方的其餘數據,若是有那麼就把確認報文和數據一塊兒放在一個TCP分段中發送過去,若是在必定時間內容一般是100-200毫秒沒有須要發送的其餘數據那麼就將該確認報文放在單獨的分組中發送。其實這麼作的目的也是爲了儘量下降網絡負擔。

一個通俗的例子就是物流,卡車的載重是必定的,假如是10噸載重,從A城市到B城市,你確定但願它能儘量的裝滿一車貨而不是來了一個小包裹你就馬上起身開向B城市。

因此TCP被設計成不是來一個數據包就立刻返回ACK確認,它一般都會在緩存中積攢一段時間,若是還有相同方向的數據就捎帶把前面的ACK確認回傳過去,可是也不能等的時間過長不然對方會認爲丟包了從而引起對方的重傳。

對因而否使用以及如何使用延遲確認不一樣操做系統會有不一樣,好比Linux能夠啓用也能夠關閉,關閉就意味着來一個就確認一個,這也是快速確認模式。

須要注意的是是否啓用或者說設置多少毫秒,也要看場景。好比在線遊戲場景下確定是儘快確認,SSH會話可使用延遲確認。

對於HTTP來講咱們能夠關閉或者調整TCP延遲確認。

Nagle算法

這個算法其實也是爲了提升IP分組利用率以及下降網絡負擔而設計,這裏面依然涉及到小報文和全尺寸報文(按以太網的標準MTU1500字節一個的報文計算,小於1500的都算非全尺寸報文),可是不管小報文怎麼小也不會小於40個字節,由於IP首部和TCP首部就各佔用20個字節。若是你發送一個50個字節小報文,其實這就意味着有效數據太少,就像延遲確認同樣小尺寸包在局域網問題不大,主要是影響廣域網。

這個算法其實就是若是發送方當前TCP鏈接中有發出去但尚未收到確認的報文的時候,那麼此時若是發送方還有小報文要發送的話就不能發送而是要放到緩衝區等待以前發出報文的確認,收到確認以後,發送方會收集緩存中同方向的小報文組裝成一個報文進行發送。其實這也就意味着接收方返回ACK確認的速度越快,發送方發送數據也就越快。

如今咱們說說延遲確認和Nagle算法結合將會帶來的問題。其實很容易看出,由於有延遲確認,那麼接收方則會在一段時間內積攢ACK確認,而發送方在這段時間內收不到ACK那麼也就不會繼續發送剩下的非全尺寸數據包(數據被分紅多個IP分組,發送方要發送的響應數據的分組數量不可能必定是1500的整數倍,大機率會發生數據尾部的一些數據就是小尺寸IP分組),因此你就看出這裏的矛盾所在,那麼這種問題在TCP傳輸中會影響傳輸性能那麼HTTP又依賴TCP因此天然也會影響HTTP性能,一般咱們會在服務器端禁用該算法,咱們能夠在操做系統上禁用或者在HTTP程序中設置TCP_NODELAY來禁用該算法。好比在Nginx中你可使用tcp_nodelay on;來禁用。

TIME_WAIT積累與端口耗盡3

這裏指的是做爲客戶端的一方或者說是在TCP鏈接中主動關閉的一方,雖然服務器也能夠主動發起關閉,可是咱們這裏討論的是HTTP性能,因爲HTTP這種鏈接的特性,一般都是客戶端發起主動關閉,

客戶端發起一個HTTP請求(這裏說的是對一個特定資源的請求而不是打開一個所謂的主頁,一個主頁有N多資源因此會致使有N個HTTP請求的發起)這個請求結束後就會斷開TCP鏈接,那麼該鏈接在客戶端上的TCP狀態會出現一種叫作TIME_WAIT的狀態,從這個狀態到最終關閉一般會通過2MSL4的時長,咱們知道客戶端訪問服務端的HTTP服務會使用本身本機隨機高位端口來鏈接服務器的80或者443端口來創建HTTP通訊(其本質就是TCP通訊)這就意味着會消耗客戶端上的可用端口數量,雖然客戶端斷開鏈接會釋放這個隨機端口,不過客戶端主動斷開鏈接後,TCP狀態從TIME_WAIT到真正CLOSED之間的這2MSL時長內,該隨機端口不會被使用(若是客戶端又發起對相同服務器的HTTP訪問),其目的之一是爲了防止相同TCP套接字上的髒數據。經過上面的結論咱們就知道若是客戶端對服務器的HTTP訪問過於密集那麼就有可能出現端口使用速度高於端口釋放速度最終致使因沒有可用隨機端口而沒法創建鏈接。

上面咱們說過一般都是客戶端主動關閉鏈接,

TCP/IP詳解 卷1 第二版,P442,最後的一段寫到 對於交互式應用程序而言,客戶端一般執行主動關閉操做並進入TIME_WAIT狀態,服務器一般執行被動關閉操做而且不會直接進入TIME_WAIT狀態。

不過若是web服務器而且開啓了keep-alive的話,當達到超時時長服務器也會主動關閉。(我這裏並非說TCP/IP詳解錯了,而是它在那一節主要是針對TCP來講,並無引入HTTP,並且它說的是一般而不是必定)

我使用Nginx作測試,而且在配置文件中設置了keepalive_timeout 65s;,Nginx的默認設置是75s,設置爲0表示禁用keepalive,以下圖:

下面我使用Chrom瀏覽器訪問這個Nginx默認提供的主頁,並經過抓包程序來監控整個通訊過程,以下圖:

從上圖能夠看出來在有效數據傳送完畢後,中間出現了Keep-Alive標記的通訊,而且在65秒內沒有請求後服務器主動斷開鏈接,這種狀況你在Nginx的服務器上就會看到TIME_WAIT的狀態。

服務端端口耗盡

有人說Nginx監聽80或者443,客戶端都是鏈接這個端口,服務端怎麼會端口耗盡呢?就像下圖同樣(忽略圖中的TIME_WAIT,產生這個的緣由上面已經說過了是由於Nginx的keepalive_timeout設置致使的)

其實,這取決於Nginx工做模式,咱們使用Nginx一般都是讓其工做在代理模式,這就意味着真正的資源或者數據在後端Web應用程序上,好比Tomcat。代理模式的特色是代理服務器代替用戶去後端獲取數據,那麼此時相對於後端服務器來講,Nginx就是一個客戶端,這時候Nginx就會使用隨機端口來向後端發起請求,而系統可用隨機端口範圍是必定的,可使用sysctl net.ipv4.ip_local_port_range命令來查看服務器上的隨機端口範圍。

經過咱們以前介紹的延遲確認、Nagle算法以及代理模式下Nginx充當後端的客戶端角色並使用隨機端口鏈接後端,這就意味着服務端的端口耗盡風險是存在的。隨機端口釋放速度若是比與後端創建鏈接的速度慢就有可能出現。不過通常不會出現這個狀況,至少咱們公司的Nginx我沒有發現有這種現象產生。由於首先是靜態資源都在CDN上;其次後端大部分都是REST接口提供用戶認證或者數據庫操做,這些操做其實後端若是沒有瓶頸的話基本都很快。不過話說回來若是後端真的有瓶頸且擴容或者改架構成本比較高的話,那麼當面對大量併發的時候你應該作的是限流防止後端被打死。

服務端HTTP進程打開文件數量達到最大

咱們說過HTTP通訊依賴TCP鏈接,一個TCP鏈接就是一個套接字,對於類Unix系統來講,打開一個套接字就是打開一個文件,若是有100個請求鏈接服務端,那麼一旦鏈接創建成功服務端就會打開100個文件,而Linux系統中一個進程能夠打開的文件數量是有限的ulimit -f,因此若是這個數值設置的過小那麼也會影響HTTP鏈接。而對以代理模式運行的Nginx或者其餘HTTP程序來講,一般一個鏈接它就要打開2個套接字也就會佔用2個文件(命中Nginx本地緩存或者Nginx直接返回數據的除外)。因此對於代理服務器這個進程可打開的文件數量也要設置的大一點。

持久鏈接Keepalive

首先咱們要知道keepalive能夠設置在2個層面上,且2個層面意義不一樣。TCP的keepalive是一種探活機制,好比咱們常說的心跳信息,表示對方還在線,而這種心跳信息的發送由有時間間隔的,這就意味着彼此的TCP鏈接要始終保持打開狀態;而HTTP中的keep-alive是一種複用TCP鏈接的機制,避免頻繁創建TCP鏈接。因此必定明白TCP的Keepalive和HTTP的Keep-alive不是一回事

HTTP的keep-alive機制

非持久鏈接會在每一個HTTP事務完成後斷開TCP鏈接,下一個HTTP事務則會再從新創建TCP鏈接,這顯然不是一種高效機制,因此在HTTP/1.1以及HTTP/1.0的加強版本中容許HTTP在事務結束後將TCP鏈接保持打開狀態,以便後續的HTTP事務能夠複用這個鏈接,直到客戶端或者服務器主動關閉該鏈接。持久鏈接減小了TCP鏈接創建的次數同時也最大化的規避了TCP慢啓動帶來的流量限制。

再來看一下這張圖,圖中的keepalive_timeout 65s設置了開啓http的keep-alive特性而且設置了超時時長爲65秒,其實還有比較重要的選項是keepalive_requests 100;它表示同一個TCP鏈接最多能夠發起多少個HTTP請求,默認是100個。

在HTTP/1.0中keep-alive並非默認使用的,客戶端發送HTTP請求時必須帶有Connection: Keep-alive的首部來試圖激活keep-alive,若是服務器不支持那麼將沒法使用,全部請求將以常規形式進行,若是服務器支持那麼在響應頭中也會包括Connection: Keep-alive的信息。

在HTTP/1.1中默認就使用Keep-alive,除非特別說明,不然全部鏈接都是持久的。若是要在一個事務結束後關閉鏈接,那麼HTTP的響應頭中必須包含Connection: CLose首部,不然該鏈接會始終保持打開狀態,固然也不能老是打開,也必須關閉空閒鏈接,就像上面Nginx的設置同樣最多保持65秒的空閒鏈接,超事後服務端將會主動斷開該鏈接。

TCP的keepalive

在Linux上沒有一個統一的開關去開啓或者關閉TCP的Keepalive功能,查看系統keepalive的設置sysctl -a | grep tcp_keepalive,若是你沒有修改過,那麼在Centos系統上它會顯示:

net.ipv4.tcp_keepalive_intvl = 75   # 兩次探測直接間隔多少秒
net.ipv4.tcp_keepalive_probes = 9   # 探測頻率
net.ipv4.tcp_keepalive_time = 7200  # 表示多長時間進行一次探測,單位秒,這裏也就是2小時

按照默認設置,那麼上面的總體含義就是2小時探測一次,若是第一次探測失敗,那麼過75秒再探測一次,若是9次都失敗就主動斷開鏈接。

如何開啓Nginx上的TCP層面的Keepalive,在Nginx中有一個語句叫作listen它是server段裏面用於設置Nginx監聽在哪一個端口的語句,其實它後面還有其餘參數就是用來設置套接字屬性的,看下面幾種設置:

# 表示開啓,TCP的keepalive參數使用系統默認的
listen       80 default_server so_keepalive=on;
# 表示顯式關閉TCP的keepalive
listen       80 default_server so_keepalive=off;
# 表示開啓,設置30分鐘探測一次,探測間隔使用系統默認設置,總共探測10次,這裏的設
# 置將會覆蓋上面系統默認設置
listen       80 default_server so_keepalive=30m::10;

因此是否要在Nginx上設置這個so_keepalive,取決於特定場景,千萬不要把TCP的keepalive和HTTP的keepalive搞混淆,由於Nginx不開啓so_keepalive也不影響你的HTTP請求使用keep-alive特性。若是客戶端和Nginx直接或者Nginx和後端服務器之間有負載均衡設備的話並且是響應和請求都會通過這個負載均衡設備,那麼你就要注意這個so_keepalive了。好比在LVS的直接路由模式下就不受影響,由於響應不通過
LVS,不過要是NAT模式就須要留意,由於LVS保持TCP會話也有一個時長,若是該時長小於後端返回數據的時長那麼LVS就會在客戶端尚未收到數據的狀況下斷開這條TCP鏈接。


  1. TCP擁塞控制有一些算法,其中就包括TCP慢啓動、擁塞避免等算法

  2. 有些地方也叫作IP分片但都是一個意思,至於爲何分片簡單來講就是受限於數據鏈路層,不一樣的數據鏈路其MTU不一樣,以太網的是1500字節,在某些場景會是1492字節;FDDI的MTU又是另一種大小,單純考慮IP層那麼IP數據包最大是65535字節

  3. 在《HTTP權威指南》P90頁中並無說的特別清楚,這種狀況是相對於客戶端來講仍是服務端,由於頗有可能讓人誤解,固然並非說服務端不會出現端口耗盡的狀況,因此我這裏才增長了2項內容

  4. 最長不會超過2分鐘

相關文章
相關標籤/搜索