TCP和HTTP中的KeepAlive機制總結

{"type":"doc","content":[{"type":"heading","attrs":{"align":null,"level":4},"content":[{"type":"text","text":"什麼是KeepAlive"}]},{"type":"bulletedlist","content":[{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"KeepAlive可以簡單理解爲一種狀態保持或重用機制,比如當一條連接建立後,我們不想它立刻被關閉,如果實現了KeepAlive機制,就可以通過它來實現連接的保持"}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"HTTP的KeepAlive在HTTP 1.0版本默認是關閉的,但在HTTP1.1是默認開啓的;操作系統裏TCP的KeepAlive默認也是關閉,但一般應用都會修改設置來開啓。因此網上TCP流量中基於KeepAlive的是主流"}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"HTTP的KeepAlive和TCP的KeepAlive有一定的依賴關係,名稱又一樣,因此經常被混淆,但其實是不同的東西,下面具體分析一下"}]}]}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":4},"content":[{"type":"text","text":"TCP爲什麼要做KeepAlive"}]},{"type":"bulletedlist","content":[{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"我們都知道TCP的三次握手和四次揮手。當兩端通過三次握手建立TCP連接後,就可以傳輸數據了,數據傳輸完畢,連接並不會自動關閉,而是一直保持。只有兩端分別通過發送各自的 "},{"type":"codeinline","content":[{"type":"text","text":"FIN"}]},{"type":"text","text":" 報文時,纔會關閉自己側的連接。"}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"這個關閉機制看起來簡單明瞭,但實際網絡環境千變萬化,衍生出了各種問題。假設因爲實現缺陷、突然崩潰、惡意攻擊或網絡丟包等原因,一方一直沒有發送 "},{"type":"codeinline","content":[{"type":"text","text":"FIN"}]},{"type":"text","text":" 報文,則連接會一直保持並消耗着資源,爲了防止這種情況,一般接收方都會主動中斷一段時間沒有數據傳輸的TCP連接,比如LVS會默認中斷90秒內沒有數據傳輸的TCP連接,F5會中斷5分鐘內沒有數據傳輸的TCP連接"}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"但有的時候我們的確不希望中斷空閒的TCP連接,因爲建立一次TCP連接需要經過一到兩次的網絡交互,且由於TCP的 "},{"type":"codeinline","content":[{"type":"text","text":"slow start"}]},{"type":"text","text":" 機制,新的TCP連接開始數據傳輸速度是比較慢的,我們希望通過連接池模式,保持一部分空閒連接,當需要傳輸數據時,可以從連接池中直接拿一個空閒的TCP連接來全速使用,這樣對性能有很大提升"}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"爲了支持這種情況,TCP實現了KeepAlive機制。KeepAlive機制並不是TCP規範的一部分,但無論Linux和Windows都實現實現了該機制。TCP實現裏KeepAlive默認都是關閉的,且是每個連接單獨設置的,而不是全局設置"}]}]}]},{"type":"blockquote","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"Implementors MAY include \"keep-alives\" in their TCP implementations, although this practice is not universally accepted. If keep-alives are included, the application MUST be able to turn them on or off for each TCP connection, and they "},{"type":"text","marks":[{"type":"strong"}],"text":"MUST default to off"},{"type":"text","text":"."}]}]},{"type":"bulletedlist","content":[{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"另外有一個特殊情況就是,當某應用進程關閉後,如果還有該進程相關的TCP連接,一般來說操作系統會自動關閉這些連接"}]}]}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":4},"content":[{"type":"text","text":"如何開啓TCP的KeepAlive"}]},{"type":"bulletedlist","content":[{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"TCP的KeepAlive默認不是開啓的,如果想使用,需要在自己的應用中爲每個TCP連接設置"},{"type":"codeinline","content":[{"type":"text","text":"SO_KEEPALIVE"}]},{"type":"text","text":" 纔會生效"}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"在Java中,應用程序一般通過設置 "},{"type":"codeinline","content":[{"type":"text","text":"java.net.SocketOptions"}]},{"type":"text","text":" 來開啓TCP連接的KeepAlive"}]}]}]},{"type":"codeblock","attrs":{"lang":"java"},"content":[{"type":"text","text":"/**\n * When the keepalive option is set for a TCP socket and no data\n * has been exchanged across the socket in either direction for\n * 2 hours (NOTE: the actual value is implementation dependent),\n * TCP automatically sends a keepalive probe to the peer. This probe is a\n * TCP segment to which the peer must respond.\n * One of three responses is expected:\n * 1. The peer responds with the expected ACK. The application is not\n * notified (since everything is OK). TCP will send another probe\n * following another 2 hours of inactivity.\n * 2. The peer responds with an RST, which tells the local TCP that\n * the peer host has crashed and rebooted. The socket is closed.\n * 3. There is no response from the peer. The socket is closed.\n *\n * The purpose of this option is to detect if the peer host crashes.\n *\n * Valid only for TCP socket: SocketImpl\n *\n * @see Socket#setKeepAlive\n * @see Socket#getKeepAlive\n */\n@Native public final static int SO_KEEPALIVE = 0x0008;"}]},{"type":"bulletedlist","content":[{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"Java Docs裏對 "},{"type":"codeinline","content":[{"type":"text","text":"SO_KEEPALIVE"}]},{"type":"text","text":" 的工作機制做了比較詳細的說明,具體來說就是,如果某連接開啓了TCP KeepAlive,當連接空閒了兩個小時(依賴操作系統的 "},{"type":"codeinline","content":[{"type":"text","text":"net.ipv4.tcp_keepalive_time"}]},{"type":"text","text":" 設置),TCP會自動發送一個KeepAlive探測報文給對端。對端必須回覆這個探測報文,假設對端正常,就可以回覆ACK報文,收到ACK後該連接就會繼續維持,直到再次出現兩個小時空閒然後探測;假設對端不正常,比如重啓了,應該回復一個RST報文來關閉該連接。假設對端沒有任何響應,TCP會每隔75秒(依賴操作系統的 "},{"type":"codeinline","content":[{"type":"text","text":"net.ipv4.tcp_keepalive_intvl"}]},{"type":"text","text":" 設置)再次重試,重試9次(依賴OS的 "},{"type":"codeinline","content":[{"type":"text","text":"net.ipv4.tcp"},{"type":"text","marks":[{"type":"italic"}],"text":"keepalive"},{"type":"text","text":"probes"}]},{"type":"text","text":" 設置)後如果依然沒有回覆則關閉連接"}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"Linux中KeepAlive相關的配置可以通過如下方式查看"}]}]}]},{"type":"codeblock","attrs":{"lang":"bash"},"content":[{"type":"text","text":"chendw@chendw-PC:~$ sysctl -a | grep keepalive\nnet.ipv4.tcp_keepalive_time = 7200\nnet.ipv4.tcp_keepalive_intvl = 75\nnet.ipv4.tcp_keepalive_probes = 9"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":4},"content":[{"type":"text","text":"HTTP爲什麼要做KeepAlive"}]},{"type":"bulletedlist","content":[{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"HTTP雖然是基於有連接狀態的TCP,但本身卻是一個無連接狀態的協議,客戶端建立連接,發出請求,獲取響應,關閉連接,然後整個流程就結束了;當有新的HTTP請求,則使用新建立的TCP連接。老的連接一般會被客戶端瀏覽器或服務器關閉,此時由於是兩端主動發的 "},{"type":"codeinline","content":[{"type":"text","text":"FIN"}]},{"type":"text","text":" 報文,因此即使TCP已經設置了KeepAlive,TCP連接也會被正常關閉"}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"這種模式下每個HTTP請求都會經過三次握手創建新的TCP,再加上TCP慢啓動的影響,以及單個網頁裏包含越來越多的資源請求,因此效果並不理想。爲了提升性能,HTTP規範也提出了KeepAlive機制,HTTP請求攜帶頭部 "},{"type":"codeinline","content":[{"type":"text","text":"Connection: Keep-Alive"}]},{"type":"text","text":" 信息,告知服務器不要關閉該TCP連接,當服務器收到該請求,完成響應後,不會主動主動關閉該TCP連接。而瀏覽器當然也不會主動關閉,而是在後續請求裏複用該TCP連接來發送下一個HTTP請求"}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"HTTP1.0默認不開啓KeepAlive,因此要使用的話需要瀏覽器支持,在發送HTTP請求時主動攜帶 "}]}]}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"codeinline","content":[{"type":"text","text":"Connection: Keep-Alive"}]},{"type":"codeinline","content":[{"type":"text","text":"頭部,應用服務器同樣也要支持;而HTTP1.1規範明確規定了要默認開啓KeepAlive,所以支持HTTP1.1的瀏覽器不需要顯式指定,發送請求時會自動攜帶該頭部,只有在想關閉時可以通過設置 "}]},{"type":"codeinline","content":[{"type":"text","text":"Connection: Close"}]},{"type":"text","text":" 頭部告知對端"}]},{"type":"bulletedlist","content":[{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"另外,HTTP的KeepAlive機制還提供了頭部 "},{"type":"codeinline","content":[{"type":"text","text":"Keep-Alive: max=5, timeout=120"}]},{"type":"text","text":" 來控制連接關閉時間,比如如上頭部就表示該TCP連接還會保持120秒,max表示可以發送的請求數,不過在非管道連接下會被忽略,我們基本都是非管道連接,因此可以忽略"}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"HTTP/2爲每個域名使用單個TCP連接,本身就是連接複用,因此請求不再需要攜帶頭部來開啓KeepAlive"}]}]}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":4},"content":[{"type":"text","text":"HTTP的KeepAlive和TCP的KeepAlive的關係"}]},{"type":"bulletedlist","content":[{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"從上面可以看出,雖然都叫KeepAlive且有依賴關係,但HTTP的KeepAlive和TCP的KeepAlive是兩個完全不同的概念"}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"TCP的KeepAlive是由操作系統內核來控制,通過 "},{"type":"codeinline","content":[{"type":"text","text":"keep-alive"}]},{"type":"text","text":" 報文來防止TCP連接被對端、防火牆或其他中間設備意外中斷,和上層應用沒有任何關係,只負責維護單個TCP連接的狀態,其上層應用可以複用該TCP長連接,也可以關閉該TCP長連接"}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"HTTP的KeepAlive機制則是和自己的業務密切相關的,瀏覽器通過頭部告知服務器要複用這個TCP連接,請不要隨意關閉。只有到了 "},{"type":"codeinline","content":[{"type":"text","text":"keepalive"}]},{"type":"text","text":" 頭部規定的 "},{"type":"codeinline","content":[{"type":"text","text":"timeout"}]},{"type":"text","text":" 纔會關閉該TCP連接,不過這具體依賴應用服務器,應用服務器也可以根據自己的設置在響應後主動關閉這個TCP連接,只要在響應的時候攜帶 "},{"type":"codeinline","content":[{"type":"text","text":"Connection: Close"}]},{"type":"text","text":" 告知對方"}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"所以很多時候我們可以把HTTP連接理解爲TCP連接,但HTTP KeepAlive則不能當成TCP的KeepAlive看待"}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"假設我們不開啓TCP長連接而只開啓HTTP長連接,是不是HTTP的KeepAlive就不起作用了?並不是的,此時HTTP的KeepAlive還會正常起作用,TCP連接還會被複用,但被複用的TCP連接出現故障的概率就高很多。由於沒有開啓TCP的KeepAlive,防火牆或負載轉發服務等中間設備可能因爲該TCP空閒太長而悄悄關閉該連接,當HTTP從自己的連接池拿出該TCP連接時,可能並不知道該連接被關閉,繼續使用就會出現錯誤"}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"爲了減少錯誤,一般來說開啓HTTP的KeepAlive的應用都會開啓TCP的KeepAlive"}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"默認的 "},{"type":"codeinline","content":[{"type":"text","text":"net.ipv4.tcp_keepalive_time"}]},{"type":"text","text":" 爲2個小時,是不是太長了?感覺太長了,2小時監測一次感覺黃花菜都涼了。我們公司F5後面的Nginx服務器配置了30分鐘,但應該也是太長了吧,F5維持空閒連接5分鐘,那超時監測不應該低於這個值嗎 "},{"type":"text","marks":[{"type":"strong"}],"text":"???"},{"type":"text","text":",比如"},{"type":"link","attrs":{"href":"https://cloud.google.com/compute/docs/troubleshooting/general-tips#communicatewithinternet","title":""},"content":[{"type":"text","text":"Google Cloud"}]},{"type":"text","text":"說其防火牆允許10分鐘空閒連接,因此建議 "},{"type":"codeinline","content":[{"type":"text","text":"net.ipv4.tcp_keepalive_time"}]},{"type":"text","text":" 設置爲6分鐘"}]}]}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":4},"content":[{"type":"text","text":"如何使用HTTP的KeepAlive"}]},{"type":"bulletedlist","content":[{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"很明顯,開啓HTTP KeepAlive不需要用戶做任何操作,只要瀏覽器和應用服務器支持即可,不過需要注意的是,HTTP KeepAlive的相關頭部都是 "},{"type":"codeinline","content":[{"type":"text","text":"hop-by-hop"}]},{"type":"text","text":" 類型的"}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"和TCP連接不同,一個完整的HTTP事務,可能會橫跨多個TCP連接,比如瀏覽器請求某個網頁,請求可能先通過瀏覽器與負載均衡之間的TCP連接傳輸,再經過負載均衡到Nginx的TCP連接,最後在經過Nginx與業務Tomcat服務器的TCP連接,Tomcat處理完請求並返回響應後,響應沿着同樣的TCP連接路線返回"}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"因此HTTP的頭部被分爲了兩部分:"},{"type":"codeinline","content":[{"type":"text","text":"End-to-end"}]},{"type":"text","text":" 頭部和 "},{"type":"codeinline","content":[{"type":"text","text":"Hop-by-hop"}]},{"type":"text","text":" 頭部,"},{"type":"codeinline","content":[{"type":"text","text":"End-to-end"}]},{"type":"text","text":" 頭部會被中間的代理原樣轉發,比如瀏覽器請求報文中的 "},{"type":"codeinline","content":[{"type":"text","text":"host"}]},{"type":"text","text":" 頭部,會被負載均衡、反向代理原樣轉發到Tomcat裏,除非特意修改。而 "},{"type":"codeinline","content":[{"type":"text","text":"Hop-by-hop"}]},{"type":"text","text":" 頭部則只在當前TCP連接裏有效,大部分頭部都是 "},{"type":"codeinline","content":[{"type":"text","text":"End-to-end"}]},{"type":"text","text":" ,但KeepAlive相關頭部很明顯和TCP連接有密切關係,因此是 "},{"type":"codeinline","content":[{"type":"text","text":"Hop-by-hop"}]},{"type":"text","text":" 的"}]}]}]},{"type":"blockquote","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":" * End-to-end headers which are transmitted to the ultimate recipient of a request or response. End-to-end headers in responses MUST be stored as part of a cache entry and MUST be transmitted in any response formed from a cache entry."}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":" * Hop-by-hop headers which are meaningful only for a single transport-level connection and are not stored by caches or forwarded by proxies."}]}]},{"type":"bulletedlist","content":[{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"也就是說,即使瀏覽器請求時攜帶了 "},{"type":"codeinline","content":[{"type":"text","text":"Connection: Keep-Alive"}]},{"type":"text","text":" ,也只表示瀏覽器到負載均衡之間是長連接,但負載均衡到nginx、nginx到tomcat是否是長連接則需要具體分析。比如Nginx雖然支持HTTP的Keep-Alive,但由Nginx發起的HTTP請求默認不是長連接"}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"由於這種 "},{"type":"codeinline","content":[{"type":"text","text":"Hop-by-hop"}]},{"type":"text","text":" 的特性,HTTP長連接中的 "},{"type":"codeinline","content":[{"type":"text","text":"timeout"}]},{"type":"text","text":" 設置就十分可疑了,不過一般來說應用服務器都是根據自己的設置來管理TCP連接的,因此HTTP長連接中 "},{"type":"codeinline","content":[{"type":"text","text":"Connection"}]},{"type":"text","text":" 頭部每個請求都攜帶, "},{"type":"codeinline","content":[{"type":"text","text":"keepalive"}]},{"type":"text","text":" 頭部用的就比較少"}]}]}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":4},"content":[{"type":"text","text":"Nginx的KeepAlive配置"}]},{"type":"bulletedlist","content":[{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"Nginx與客戶端的長連接"}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":1,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"Nginx是支持HTTP KeepAlive的,因此只要client發送的http請求攜帶了KeepAlive頭部,客戶端和Nginx的長連接就能正常保持"}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":1,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"可以使用keepalive"},{"type":"text","marks":[{"type":"italic"}],"text":"requests和keepalive"},{"type":"text","text":"timeout調整對client的長連接的單個連接承受的最大請求數,以及長連接最大空閒時長"}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":1,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"從上面可知,服務端可以根據客戶端的 "},{"type":"codeinline","content":[{"type":"text","text":"keepalive"}]},{"type":"text","text":" 頭部來管理TCP連接,也可以根據自己的設置來管理,Nginx一般根據自己的設置來管理"}]}]}]},{"type":"blockquote","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":" Syntax:\tkeepalive_requests number;"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":" Default:\tkeepalive_requests 100;"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":" Context:\thttp, server, location"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":" This directive appeared in version 0.8.0."}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":" "}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":" Sets the maximum number of requests that can be served through one keep-alive connection. After the maximum number of requests are made, the connection is closed."}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":" Closing connections periodically is necessary to free per-connection memory allocations. Therefore, using too high maximum number of requests could result in excessive memory usage and not recommended."}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":" Syntax:\tkeepalive"},{"type":"text","marks":[{"type":"italic"}],"text":"timeout timeout [header"},{"type":"text","text":"timeout];"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":" Default:\tkeepalive_timeout 75s;"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":" Context:\thttp, server, location"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":" "}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":" The first parameter sets a timeout during which a keep-alive client connection will stay open on the server side. The zero value disables keep-alive client connections. The optional second parameter sets a value in the “Keep-Alive: timeout=time” response header field. Two parameters may differ."}]}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"bulletedlist","content":[{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"客戶端修改默認值具體配置如下"}]}]}]},{"type":"codeblock","attrs":{"lang":""},"content":[{"type":"text","text":"http {\n keepalive_requests 100;\n keepalive_timeout 75s;\n upstream backend {\n server 192.167.61.1:8080;\n }\n}"}]},{"type":"bulletedlist","content":[{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"Nginx與Upstream Server的長連接"}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":1,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"Nginx作爲發起方的時候,默認還是不開啓HTTP的KeepAlive的,因此需要主動設置"}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":1,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"在 "},{"type":"codeinline","content":[{"type":"text","text":"upstream"}]},{"type":"text","text":" 區塊使用 "},{"type":"codeinline","content":[{"type":"text","text":"keepalive"}]},{"type":"text","text":" 開啓,數字表示每個work開啓的最大長連接數"}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":1,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"Nginx和上游交互時,默認 "},{"type":"codeinline","content":[{"type":"text","text":"proxy_http_version"}]},{"type":"text","text":" 爲1.0,因此需要配置 "},{"type":"codeinline","content":[{"type":"text","text":"proxy_http_version"}]},{"type":"text","text":" ,並清空 "},{"type":"codeinline","content":[{"type":"text","text":"connection"}]},{"type":"text","text":",這樣即使前一跳是短連接,Nginx與上游也可以是長連接"}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":1,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"另外 "},{"type":"codeinline","content":[{"type":"text","text":"upstream"}]},{"type":"text","text":" 裏的 "},{"type":"codeinline","content":[{"type":"text","text":"keepalive_requests"}]},{"type":"text","text":" 和 "},{"type":"codeinline","content":[{"type":"text","text":"http"}]},{"type":"text","text":" 區塊裏的一樣是100,但 "},{"type":"codeinline","content":[{"type":"text","text":"keepalive_timeout"}]},{"type":"text","text":" 默認爲60秒,比 "},{"type":"codeinline","content":[{"type":"text","text":"http"}]},{"type":"text","text":" 區塊裏的少15秒,不過也正常,畢竟是裏層,這個設置是比較合理的,使用默認的就可以"}]}]}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":""},"content":[{"type":"text","text":"upstream backend {\n server 192.167.61.1:8080;\n server 192.167.61.1:8082 back;\n keepalive 100;\n # keepalive_requests 100;\n # keepalive_timeout 60s;\n}\n\nlocal /test {\n proxy_http_version 1.1;\n proxy_set_header Connection \"\"; // 傳遞給上游服務器的頭信息\n\n proxy_pass http://backend;\n}"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"bulletedlist","content":[{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"另外,Nginx還在 "},{"type":"codeinline","content":[{"type":"text","text":"listner"}]},{"type":"text","text":" 指令上提供了一個 "},{"type":"codeinline","content":[{"type":"text","text":"so_keepalive"}]},{"type":"text","text":" 選項,來開啓Nginx對TCP長連接的支持,應該開啓的是客戶端與Nginx之間的TCP長連接,但一般沒有人使用,那負載均衡和Nginx、Nginx和Tomcat之間是"},{"type":"text","marks":[{"type":"strong"}],"text":"不需要TCP長連接嗎"},{"type":"text","text":"?因爲中間沒有網絡設備?否則TCP長連接是由誰來做檢測?"}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"長連接的資源佔用問題"}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":1,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":" 長連接帶來的一個很明顯的問題就是資源的佔用,瀏覽器對同一個域名一般能併發建立6個連接,一般這些都是長連接,而這些連接會維護75秒,但客戶端獲得響應以後一般就結束了,下一次的客戶是不同的源地址,因此無法複用前一個瀏覽器與服務器之間維護的長連接,這會造成服務端維護了大量不再被使用的連接,所以長連接的意義在於有大量資源持續請求的場景"}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":1,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":" 假設你就一個靜態頁面,裏面包含幾個資源,使用短連接對服務器併發更好"}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":1,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":" 另外,注意Nginx中 "},{"type":"codeinline","content":[{"type":"text","text":"keepalive_requests"}]},{"type":"text","text":" 默認的100表示的是單個長連接能處理的最大請求數,而並不是Nginx能維護的長連接數。Nginx能維護的TCP連接數,爲工作進程個數 "},{"type":"codeinline","content":[{"type":"text","text":"worker_processes"}]},{"type":"text","text":" 乘以每個工作進程允許維護的最大連接數 "},{"type":"codeinline","content":[{"type":"text","text":"worker_connections"}]},{"type":"text","text":"(默認512);如果想計算Nginx能服務的最大請求數,還需要在最大TCP連接數外,加上操作系統允許的排隊等待數 "},{"type":"codeinline","content":[{"type":"text","text":"net.core.somaxconn"}]},{"type":"text","text":",默認128"}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":1,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":" Nginx通過事件驅動來實現大量長連接的維護,具體可以查看Nginx文檔"}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"端口號與文件數"}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":1,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"由於端口在傳輸層使用16位來傳輸,因此取值範圍只能是0到65535,再加上TCP連接關閉後端口並不能立刻被重用,而是要經過2MSL的TIME_WAIT閒置,所以經常有人以爲一個服務器同時最大能維持的TCP數是 "},{"type":"codeinline","content":[{"type":"text","text":"65000/2*60"}]},{"type":"text","text":" ,大約500左右"}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":1,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":" 這個理解是有偏頗的。端口的限制只是對發起方來說的,即源端口。比如Nginx作爲反向代理,和上游Tomcat建立連接時,源IP和目的IP肯定是固定的,目的端口也是固定的,比如Tomcat的8080端口,只有源端口可變,所以Nginx和上游Tomcat最多隻能建立500左右的TCP連接,不過兩端IP都是固定的,所以TCP連接重用效果非常好,並不會造成性能問題"}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":1,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":" 當Nginx作爲接收方和客戶端瀏覽器建立連接時,Nginx服務器提供固定的IP和端口,而客戶端瀏覽器IP和端口都會正常變動,因此Nginx服務器上維護的與客戶端的長連接是不受端口限制的,不過此時服務器又會遇到著名的C10K問題"}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":1,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":" 此時限制服務器維持TCP連接數的是操作系統允許打開的最大文件數,要修改的主要有以下幾處"}]}]}]},{"type":"numberedlist","attrs":{"start":1,"normalizeStart":1},"content":[{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":2,"number":1,"align":null,"origin":null},"content":[{"type":"text","text":"/proc/sys/fs/file-max:操作系統所有進程一共可以打開的文件數"}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":2,"number":2,"align":null,"origin":null},"content":[{"type":"text","text":"/proc/sys/fs/nr_open:單個進程能分配的最大文件數"}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":2,"number":3,"align":null,"origin":null},"content":[{"type":"text","text":"ulimit的open files:當前shell以及由它啓動的進程可以打開的最大文件數,如果超過了nr"},{"type":"text","marks":[{"type":"italic"}],"text":"open,要先調整nr"},{"type":"text","text":"open的值"}]}]}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":4},"content":[{"type":"text","text":"Tomcat的KeepAlive配置"}]},{"type":"bulletedlist","content":[{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"Tomcat7以上都默認開啓了keepalive支持。兩個主要參數maxKeepAliveRequest和KeepAliveTimeout"}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":1,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":" maxKeepAliveRequest:一個長連接能接受的最大請求數,默認100"}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":1,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":" KeepAliveTimeout:一個長連接最長空閒時間,否則被關閉,默認爲connectionTimeout的值,默認60s"}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"Tomcat裏的應用作爲發起方的時候,是否支持KeepAlive是由應用自行決定的,和Tomcat無關"}]}]}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":4},"content":[{"type":"text","text":"參考資料"}]},{"type":"bulletedlist","content":[{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"link","attrs":{"href":"https://tools.ietf.org/html/rfc1122#page-101","title":""},"content":[{"type":"text","text":"RFC1122 - TCP Keep-Alives"}]}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"link","attrs":{"href":"https://tools.ietf.org/html/rfc2616#section-13.5.1","title":""},"content":[{"type":"text","text":"HTTP Header Type"}]}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"link","attrs":{"href":"http://nginx.org/en/docs/http/ngxhttpcore_module.html","title":""},"content":[{"type":"text","text":"Nginx ngx"},{"type":"text","marks":[{"type":"italic"}],"text":"http"},{"type":"text","text":"core_module"}]}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"link","attrs":{"href":"https://www.nginx.com/blog/http-keepalives-and-web-performance/","title":""},"content":[{"type":"text","text":"HTTP Keepalive Connections and Web Performance"}]}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"link","attrs":{"href":"https://www.nginx.com/blog/tuning-nginx/","title":""},"content":[{"type":"text","text":"Tuning NGINX for Performance"}]}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"link","attrs":{"href":"http://www.kegel.com/c10k.html","title":""},"content":[{"type":"text","text":"The C10K problem"}]}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"link","attrs":{"href":"https://juejin.im/post/6844903916887343118","title":""},"content":[{"type":"text","text":"ulimit的探討"}]}]}]}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}}]}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章