這個詞看着有點熟,不少地方好像都見過。html
TCP的KeepAlive,Http的KeepAlive,如今就連一些前端框架都有相似KeepAlive的東西了(好比VUE.js,保持路由)。前端
本文介紹HTTP和TCP中的KeepAlive機制,其餘方面不在本文討論範圍。java
HTTP 持久鏈接(HTTP persistent connection,也稱做HTTP keep-alive或HTTP connection reuse,翻譯過來能夠是保持鏈接或者鏈接複用)是使用同一個TCP鏈接來發送和接收多個HTTP請求/應答,而不是爲每個新的請求/應答打開新的鏈接的方式。nginx
HTTP協議採用「請求-應答」模式,當使用普通模式,即非KeepAlive模式時,每一個請求/應答客戶和服務器都要新建一個鏈接,完成 以後當即斷開鏈接(HTTP協議爲無鏈接的協議),每次請求都會通過三次握手四次揮手過程,效率較低;當使用Keep-Alive
模式時,客戶端到服務器端的鏈接不會斷開,當出現對服務器的後繼請求時,客戶端就會複用已創建的鏈接。spring
下圖是每次新建鏈接和鏈接複用在通訊模型上的區別:apache
在Http 1.0中,Keep-Alive
是沒有官方支持的,可是也有一些Server端支持,這個年代比較久遠就不用考慮了。bootstrap
Http1.1之後,Keep-Alive
已經默認支持並開啓。客戶端(包括但不限於瀏覽器)發送請求時會在Header中增長一個請求頭Connection: Keep-Alive
,當服務器收到附帶有Connection: Keep-Alive
的請求時,也會在響應頭中添加Keep-Alive。這樣一來,客戶端和服務器之間的HTTP鏈接就會被保持,不會斷開(斷開方式下面介紹),當客戶端發送另一個請求時,就能夠複用已創建的鏈接。segmentfault
如今的Http協議基本都是Http 1.1版本了,不太須要考慮1.0的兼容問題後端
固然不是,Keep-Alive也有本身的優缺點,並非全部場景下都適用瀏覽器
對於某些低頻訪問的資源/服務,好比一個冷門的圖片服務器,一年下不了幾回,每下一次鏈接還保持就比較浪費了(這個場景舉的不是很恰當)。Keep-Alive可能會很是影響性能,由於它在文件被請求以後還保持了沒必要要的鏈接很長時間,額外佔用了服務端的鏈接數。
在沒有鏈接複用時,Http 接收端(注意這裏是接收端,並無特指Client/Server,由於Client/Server都同是發送端和接收端)只須要讀取Socket中全部的數據就能夠了,解決「拆包」問題便可;可是鏈接複用後,沒法區分單次Http報文的邊界,因此還須要額外處理報文邊界問題。固然這個經過Http中Header的長度字段,按需讀取便可解決。
粘包拆包的介紹能夠參考另外一篇文章細說 Netty 中的粘包和拆包
因爲Http中Header的存在,經過定義一些報文長度的首部字段,能夠很方便的處理包邊界問題。
在Http中,有兩種方式處理包邊界問題:
這個是最一般的處理方式,接收端處理報文時首先讀取完整首部(Header),而後經過Header中的Content-Length
來確認報文大小,讀取報文時按此長度讀取便可,超出長度的報文(「粘包」)不讀取,不夠長度的報文緩存等待繼續讀取(「拆包」)。
對於沒法確認總報文大小的狀況,可使用Chunked的方式來對報文進行分塊傳輸,每一塊內標示報文大小。好比Nginx,開啓Gzip壓縮後,就會開啓Chunked的傳輸方式。
經過Wireshark抓包,能夠很直觀的看初Chunked的原理:
注意,這裏的chunk包,和tcp segment不是一回事,chunk只是應用層的一個分包,而tcp的segment 是對應用層報文再次進行分組
每一個chunk報文前,會攜帶當前chunk的大小。
經過Keep-Alive已經作到鏈接複用了,但複用以後何時斷開鏈接呢,否則一直保持鏈接,形成資源的浪費。
Http協議規定了兩種關閉複用鏈接的方式:
若是服務端Response Header設置了Keep-Alive:timeout={timeout}
,客戶端會就會保持此鏈接timeout(單位秒)時間,超時以後關閉鏈接。
如今在服務端設置響應Header:
Keep-Alive:timeout=15
經過Wireshark來看下配置了timeout的效果:
從上圖能夠看出,客戶端發送請求後,在15S內(圖上沒有體現時間,就當15S吧)保持了鏈接不銷燬,超時後通過了4次揮手,斷開鏈接
可是若是在15S內再次請求,鏈接是能夠複用的,不會從新3次握手。
下圖是15S內再次請求的效果:
還有一種方式是接收端通在Response Header中增長Connection close
標識,來主動告訴發送端,鏈接已經斷開了,不能再複用了;客戶端接收到此標示後,會銷燬鏈接,再次請求時會從新創建鏈接。
注意:配置close配置後,並非說每次都新建鏈接,而是約定此鏈接能夠用幾回,達到這個最大次數時,接收端就會返回close標識(服務端配置方法下面會介紹)
下面來測試下效果,客戶端發送兩次請求:
經過wireshark截圖能夠發現,配置了Connection:close以後(服務端設置了請求只能夠用1次,所因此請求完成就銷燬鏈接),兩次請求都從新創建了鏈接。
Syntax: keepalive_timeout timeout [header_timeout]; Default: keepalive_timeout 75s; Context: http, server, location 第一個參數設置一個超時,在此期間保持活動的客戶機鏈接將在服務器端保持打開狀態。若是爲0則禁用保Keep-Alive。第二個可選參數在「Keep-Alive: timeout=time」響應頭字段中設置一個值。 「Keep-Alive: timeout=time」報頭字段被Mozilla和Konqueror識別。MSIE在大約60秒內自動關閉保持鏈接。
Syntax: keepalive_requests number; Default: keepalive_requests 100; Context: http, server, location 設置經過一個保持活動鏈接能夠服務的請求的最大數量。在發出最大數量的請求以後,鏈接關閉。
在<Connector>
標籤中配置屬性:
keepAliveTimeout="超時時間"
,默認值是使用爲connectionTimeout屬性設置的值 。值爲-1表示沒有(即無限)超時。
maxKeepAliveRequests="鏈接可用次數"
,-1爲永不失效。若是未指定,默認爲100。
例如:
<Connector port="8080" protocol="HTTP/1.1" connectionTimeout="20000" redirectPort="8443" keepAliveTimeout="超時時間(單位秒)" maxKeepAliveRequests="鏈接可用次數" />
此版本是基於springboot 2.0.2.release,其餘版本請自行測試
@Bean public TomcatServletWebServerFactory tomcatServletWebServerFactory(){ TomcatServletWebServerFactory tomcatServletWebServerFactory = new TomcatServletWebServerFactory(); tomcatServletWebServerFactory.addConnectorCustomizers((connector)->{ ProtocolHandler protocolHandler = connector.getProtocolHandler(); if(protocolHandler instanceof Http11NioProtocol){ Http11NioProtocol http11NioProtocol = (Http11NioProtocol)protocolHandler; http11NioProtocol.setKeepAliveTimeout(60000);//millisecond } }); return tomcatServletWebServerFactory; }
此版本是基於springboot 1.5.6.release,其餘版本請自行測試
@Bean public TomcatEmbeddedServletContainerFactory tomcatEmbeddedServletContainerFactory(){ TomcatEmbeddedServletContainerFactory tomcatServletWebServerFactory = new TomcatEmbeddedServletContainerFactory(); tomcatServletWebServerFactory.addConnectorCustomizers((connector)->{ ProtocolHandler protocolHandler = connector.getProtocolHandler(); if(protocolHandler instanceof Http11NioProtocol){ Http11NioProtocol http11NioProtocol = (Http11NioProtocol)protocolHandler; http11NioProtocol.setKeepAliveTimeout(60000);//millisecond } }); return tomcatServletWebServerFactory; }
上面列出了兩個版本的配置方式,理論上只要能找到對應的類,就可使用。只是由於版本更新致使的部分類移除
其實Nginx的反向代理,也無非是增長了一個節點而已client<->nginx<->tomcat
對於client,Nginx是Server,對於Tomcat,Nginx是client。
Nginx和client創建鏈接,和Tomcat端也創建了鏈接。
那麼若是在Nginx和Tomcat上同時配置Keep-Alive會是什麼結果呢?
此處較爲複雜,待補充……
Apache HttpClient算是Java中最強的HttpClient了,也是最主流的(後端方向),功能強大。
Apache HttpClient在處理KeepAlive的地方設計的比較靈活,提供了可配置的接口,使用者可使用Http標準的策略,也自定定製策略。
HttpClients.custom() //鏈接是否複用策略,經過此策略返回是否複用 //DefaultClientConnectionReuseStrategy是默認的Http策略,不設置也能夠 .setConnectionReuseStrategy(new DefaultClientConnectionReuseStrategy()) //鏈接複用後有效期(持久時間)策略,複用後經過此策略判斷複用超時時間 //DefaultConnectionKeepAliveStrategy是默認的判斷超時時間策略,讀取的是Keep-Alive:timeout=超時時間 .setKeepAliveStrategy(new DefaultConnectionKeepAliveStrategy()) .build();
這裏順帶說一下Apache HttpClient的使用,但願能幫助到有須要的人。(版本Apache HttpClient 4.x)
//建立客戶端,此客戶端最好保持單例,這是個線程安全的類,併發下也沒有問題。 //HttpClient中的鏈接池等組件都包含在內,若是每次都新建的話, //效率低,佔用資源大,鏈接複用固然也不會生效了。 HttpClients.custom() //禁用自動重試,默認有3次的重試策略 .disableAutomaticRetries() //不用默認的重試策略,自定義 .setRetryHandler() //設置默認請求配置,這裏能夠配置一些超時時間等參數 .setDefaultRequestConfig(requestConfig()) //全局Header,每次請求都會攜帶 .setDefaultHeaders() //當Https證書不受信任的時候,記得自定義此項 .setSSLHostnameVerifier() //設置UA .setUserAgent() //設置代理 .setProxy() //...還有不少配置,能夠自行查閱文檔 .build();
TCP中的KeepAlive和Http的Keep-Alive可不是一回事,HTTP中是作鏈接複用的,而TCP中的KeepAlive是「心跳監測」,定時發送一個空的TCP Segment,來監測鏈接是否存活。下面介紹下Java中設置TCP KeepAive的一些方式。
bootstrap.childOption(ChannelOption.SO_KEEPALIVE, true);
channel.setOption(StandardSocketOptions.SO_KEEPALIVE,true);
Socket socket = serverSocket.accept(); socket.setKeepAlive(true);