HTTP 長連接的那些事

前言

本文更多地面向后端開發(fā)同學皿伺。雖然在后端開發(fā)中蚣常,服務間調(diào)用已經(jīng)廣泛采用 RPC 技術饥侵。但是由于一些原因,例如像 Dubbo 停止維護哩簿、各廠自己的 RPC 框架還存在這樣那樣的問題宵蕉、Open API 場景的大量存在等原因,HTTP 協(xié)議在后端開發(fā)中依然被廣泛采用节榜。為了提高 HTTP 接口的調(diào)用效率羡玛,HTTP 長連接是常見的優(yōu)化手段。因此本文選擇介紹一下 HTTP 和 TCP 長連接這個老生常談的話題宗苍。

本文介紹了 TCP 和 HTTP Keep Alive 的相關概念和知識稼稿,以及常用的 HTTP 客戶端薄榛、服務器技術在 Keep Alive 方面相關的配置。目的在于幫助大家正確地理解相關概念让歼,用好相關技術敞恋。

本文提到的示例代碼和 TCPDUMP 文件可以在這里找到。

下文中的 Keep Alive 與長連接指同一概念谋右,Keep Alive 是通用的稱謂硬猫,但為了方便交流,有時會使用長連接

TCP 長連接 (Keep Alive)

TCP 長連接是一種保持 TCP 連接的機制改执。當一個 TCP 連接建立之后啸蜜,啟用 TCP Keep Alive 的一端便會啟動一個計時器,當這個計時器到達 0 之后天梧,一個 TCP 探測包便會被發(fā)出。這個 TCP 探測包是一個純 ACK 包霞丧,但是其 Seq 與上一個包是重復的呢岗。

打個比喻,TCP Keep Alive 是這樣的:

TCP 連接兩端好比兩個人蛹尝,這兩個人之間保持通信往來(建立 TCP 連接)后豫。如果他倆經(jīng)常通信(經(jīng)常發(fā)送 TCP 數(shù)據(jù)),那這個 TCP 連接自然是建立著的突那。但如果兩人只是偶爾通信挫酿。那么,其中一個人(或兩人同時)想知道對方是否還在愕难,就會定期發(fā)送一份郵件(Keep Alive 探測包)早龟,這個郵件沒有實質(zhì)內(nèi)容,只是問對方是否還在猫缭,如果對方收到葱弟,就會回復說還在(對這個探測包的 ACK 回應)。

需要注意的是猜丹,keep alive 技術只是 TCP 技術中的一個可選項芝加。因為不當?shù)呐渲每赡軙鹬T如一個正在被使用的 TCP 連接被提前關閉這樣的問題,所以默認是關閉的

HTTP 長連接 (Keep Alive)

在 HTTP 1.0 時期射窒,每個 TCP 連接只會被一個 HTTP Transaction(請求加響應)使用藏杖。之后,這個 TCP 連接便會被關閉脉顿。當網(wǎng)頁內(nèi)容越來越復雜蝌麸,包含大量圖片、CSS 等資源之后艾疟,這種模式效率就顯得太低了祥楣。所以开财,在 HTTP 1.1 中,引入了 HTTP persistent connection 的概念误褪,也稱為 HTTP keep-alive(后面統(tǒng)一稱呼為 HTTP 長連接)责鳍。

HTTP 1.0 和 1.1 在 TCP 連接使用方面的差異可見下圖:

image

當需要建立 HTTP 長連接時,HTTP 請求頭將包含如下內(nèi)容:

Connection: Keep-Alive

如果服務端同意建立長連接兽间,HTTP 響應頭也將包含如下內(nèi)容:

Connection: Keep-Alive

當需要關閉連接時历葛,HTTP 頭中會包含如下內(nèi)容:

Connection: Close

也打個比方:

兩個人互相通信,一個人發(fā)信嘀略,并附上一句恤溶,我們保持聯(lián)系好嗎。另一個人在回信中寫到帜羊,好的咒程,我們繼續(xù)保持聯(lián)系。此后讼育,他們每封信里都寫上相同的內(nèi)容帐姻。直到有一天,友誼的小船翻了奶段,一個人在回信里寫到饥瓷,我們不要再聯(lián)系了。另一個人信守承諾痹籍,回道:好的呢铆,我們不聯(lián)系了。從此兩人天各一方蹲缠。棺克。。

TCP Keep Alive 與 HTTP Keep Alive 的關系

如上文的解釋线定,TCP Keep Alive 和 HTTP Keep Alive 是兩個目的不同的技術逆航,不存在誰依賴于誰的關系。TCP Keep Alive 用于探測對端是否存在渔肩,而 HTTP Keep Alive 用于協(xié)商以復用 TCP 連接因俐。即便一個 TCP 連接未啟用 Keep Alive 功能,也不妨礙 HTTP 層面開啟長連接周偎。

但如果 TCP Keep Alive 的 interval 數(shù)值設置果斷抹剩,就可能導致 HTTP 無法重復利用已建立的 TCP 連接。

HTTP 客戶端配置

Apache HTTP Client

Apache HTTP Client 存在兩種版本蓉坎,在 3.x 版本時被稱為 Commons HttpClient澳眷,4.x 后 Apache 創(chuàng)建了一個新的獨立項目:Apache HttpComponents。這里指的是后者蛉艾。

話不多說钳踊,看代碼:

HttpClient buildHttpClient() {
    PoolingHttpClientConnectionManager connectionManager =
            new PoolingHttpClientConnectionManager(2, TimeUnit.MINUTES);
    connectionManager.setMaxTotal(DEFAULT_MAX_PER_ROUTE * 2);
    connectionManager.setDefaultMaxPerRoute(DEFAULT_MAX_PER_ROUTE);

    HttpClientBuilder builder = HttpClients.custom()
            .setConnectionManager(connectionManager);

    return builder.build();
}

上面這段代碼展示了如何創(chuàng)建一個使用連接池的 HttpClient 的方法衷敌。當使用連接池后,HttpClient 就不必為每個請求創(chuàng)建一個單獨的 TCP 連接了拓瞪。

OkHttp

OkHttp 是國外的互聯(lián)網(wǎng)支付公司 Square 的產(chǎn)品缴罗,優(yōu)點是比較輕量,主要面向 Android 開發(fā) 使用祭埂,也可以使用在服務端開發(fā)場景中面氓。使用 OkHttp 設置 HTTP 長連接比較簡單,默認就會開啟蛆橡。只需創(chuàng)建一個 OkHttpClient 即可舌界。

OkHttpClient client = new OkHttpClient();

這個 client 應該是單例的,因為它有自己的連接池泰演。

不同于 Apache HTTP Client 的連接池呻拌,OkHttp 的連接池無法設置開啟最大的連接數(shù)。有多少個線程使用 OkHttpClient 發(fā)送請求睦焕,其就會創(chuàng)建多少個連接藐握。

OkHttp 實現(xiàn) HTTP 連接池的組件是 ConnectionPool。雖然其不能設置最大連接數(shù)复亏,但是可以設置最大空閑連接數(shù)和連接超時時間趾娃。

RestTemplate

RestTemplate 是 Spring 框架提供的 HTTP 客戶端組件缭嫡,適合調(diào)用 RESTful API 的場景缔御。其并沒有直接實現(xiàn) HTTP 客戶端功能,而是利用現(xiàn)有的組件妇蛀。下面僅給出 RestTemplate 使用 Apache HTTP Client 時如何配置連接池:

@Profile("apache")
@Bean
public ClientHttpRequestFactory apacheHttpClientFactory() {
    return new CustomHttpComponentsClientHttpRequestFactory();
}

@Bean
public RestTemplate restTemplate(ClientHttpRequestFactory clientHttpRequestFactory) {
    RestTemplate restTemplate = new RestTemplate();
    restTemplate.setRequestFactory(clientHttpRequestFactory);
    return restTemplate;
}

static class CustomHttpComponentsClientHttpRequestFactory extends
        HttpComponentsClientHttpRequestFactory {
    CustomHttpComponentsClientHttpRequestFactory() {
        setHttpClient(buildHttpClient());
    }

    private HttpClient buildHttpClient() {
        PoolingHttpClientConnectionManager connectionManager =
                new PoolingHttpClientConnectionManager(2, TimeUnit.MINUTES);
        connectionManager.setMaxTotal(CONN_POOL_SIZE * 2);
        connectionManager.setDefaultMaxPerRoute(CONN_POOL_SIZE);

        HttpClientBuilder builder = HttpClients.custom()
                .setConnectionManager(connectionManager);

        return builder.build();
    }
}

操作系統(tǒng)設置

因為服務器操作系統(tǒng)很少采用 Windows耕突,所以這里之談 Linux 的相關配置。

在 Linux 操作系統(tǒng)中评架,TCP Keep Alive 相關的配置可以在 /proc/sys/net/ipv4/ 目錄中找到眷茁,具體有下面三個(括號中的是默認值):

  • tcp_keepalive_time (7200)
  • tcp_keepalive_intvl (75)
  • tcp_keepalive_probes (9)

tcp_keepalive_time 的含義是在空閑相應時間(單位是秒)之后,TCP 協(xié)議棧將發(fā)送 Keep Alive 探測包纵诞;tcp_keepalive_intvl 的含義是開始探測之后每個探測包所間隔的時長上祈,單位同樣是秒;tcp_keepalive_probes 是探測包的個數(shù)浙芙。

默認的配置通常不適合一般的 Web 服務器登刺,實際參數(shù)會遠小于上述默認值。

正如前文所說嗡呼,TCP Keep Alive 是用來檢測 TCP 連接有效性的一種機制纸俭,如果一個空閑的 TCP 連接如果失效,那究竟多久能發(fā)現(xiàn)南窗?假設采用如下配置:

  • tcp_keepalive_time = 60
  • tcp_keepalive_intvl = 10
  • tcp_keepalive_probes = 6

那將在 60 + 10 * 6 = 120s揍很,即兩分鐘之后發(fā)現(xiàn)一個失效的 TCP 連接郎楼。

Nginx Keep Alive 配置

相較于有著豐富選擇的 HTTP 客戶端,在負載均衡服務器方面選擇不是那么多窒悔,Nginx 是較為常見的選擇呜袁。下面一段是啟用了 HTTP 長連接的配置:

events {
    use                 epoll;
    worker_connections  102400;
}

http {
    upstream keepAliveService {
        server 10.10.131.149:8080;
        keepalive 20;
    }

    server {
        listen 80;
        server_name keepAliveService;
        location /keep-alive/hello {
            proxy_http_version 1.1;
            proxy_set_header Connection "";
            proxy_pass http://keepAliveService;
        }
    }
}

上面這段配置中,對于 HTTP 長連接來說至關重要的有這么幾項:

  1. keepalive
  2. proxy_http_verion
  3. proxy_set_header

如果想要啟用 HTTP 長連接蛉迹,那這三項配置都是必須的傅寡。下面分別介紹。

keepalive

顧名思義北救,這個參數(shù)用于控制可連接整個 upstream servers 的 HTTP 長連接的個數(shù)荐操,即控制總數(shù)。但需要注意的是珍策,這個參數(shù)并不控制所有 upstream servers 連接的個數(shù)托启。

proxy_http_verion

這個用于控制代理后端鏈接時使用的 HTTP 版本,默認為 1.0攘宙。要想使用長連接屯耸,必須配置為 1.1。除了 location 以外蹭劈,這項配置還可以放在 http 和 server 這兩級中疗绣。

proxy_set_header

除了要將 HTTP 協(xié)議設置為 1.1 以外,還要清空 Connection header 中的值铺韧。如果不配置 proxy_set_header Connection ""多矮,則發(fā)往 upstream servers 的請求中,Connection header 的值將為 close哈打,導致無法建立長連接塔逃。

proxy_http_version 一樣,這項配置也可以放在 http 和 server 這兩級中料仗。

應用服務器配置

因為本文的示例程序使用 Spring Boot(版本為 1.5.2)開發(fā)湾盗,所以介紹 Spring Boot 中的兩種應用服務:Tomcat 和 Undertow。

Tomcat

Spring Boot 使用的 Tomcat 版本為 8.5.11立轧,在使用默認配置情況下格粪,每一個 HTTP 長連接會保持 10s 中,10s 之后氛改,Tomcat 會主動斷開連接帐萎。

從 HTTP 消息內(nèi)容來看,雖然發(fā)往 Tomcat 的 HTTP 請求中帶有 Connection: Keep-Alive平窘,但是 Tomcat 提供的相應中并沒有帶有 Connection: Keep-Alive header吓肋。所以,嚴格來說瑰艘,HTTP 長連接并沒有建立成功(響應中沒有包含 Connection: Keep-Alive 說明服務器端并沒有同意建立長連接)是鬼。

但是肤舞,與 Tomcat 建立的連接確實被復用了,只是沒有持續(xù)太長時間(10s)

Undertow

Undertow 是 JBoss 新推出的 Web 容器均蜜,特點在于高性能李剖,有著遠好于 Tomcat 的性能。

在對 HTTP 長連接支持方面囤耳,Undertow 的行為同 Tomcat 不同篙顺,不會主動關閉 HTTP 連接,也減少了建立充择、關閉 TCP 連接所帶來的性能消耗德玫。

長連接與短連接的性能對比

追求更好的性能通常是使用 HTTP 長連接的目的,那采用 HTTP 長連接之后椎麦,究竟有哪些性能提高宰僧?

接口響應時間

測試使用 JDK 8 編寫的應用,應用服務器采用 Undertow观挎,接口使用 Spring MVC 開發(fā)琴儿,接口耗時 100ms(Sleep 100ms),TPS 為 1000 左右嘁捷,HTTP 客戶端采用 Apache HTTP Client造成,配置 100 個連接的連接池,經(jīng)過 Nginx 轉(zhuǎn)發(fā)雄嚣。非長連接模式是在 Nginx 配置中將 proxy_set_header Connection "" 去掉晒屎。

經(jīng)過三次測試,在接口平均響應時間方面现诀,使用長連接比不使用長連接少 1ms夷磕。

采用長連接:

c.github.yanglifan.KeepAliveApplication  : Average time is 106

不采用長連接:

c.github.yanglifan.KeepAliveApplication  : Average time is 107

可見履肃,在平均響應時間方面仔沿,長連接和短連接差距很小。

服務器負載

雖然接口平均響應時間方面差距不大尺棋,但是在服務器負載方面封锉,應用長連接確有實實在在的好處。以 Nginx 服務器為例

使用長連接時:

image

不使用長連接時:

image

平均來看膘螟,在上述負載的情況下成福,使用長連接時 Nginx 的 CPU 占用率為 5% 左右,不使用長連接時為 9% 左右荆残,可見差距還是比較明顯的奴艾。也說明了使用長連接的好處,就是通過避免服務器頻繁建立連接内斯,降低服務器的負載蕴潦。

附:Linux 命令

netstat 命令

netstat 是常用的用來查看網(wǎng)絡相關數(shù)據(jù)的命令像啼,常用的參數(shù)有 anp。這三個參數(shù)比較常用潭苞,不在復述其用途忽冻。

不太常用的參數(shù)由 o,o 的意思是顯示 TCP Timers此疹。當增加 o 這個參數(shù)時僧诚,可能看到的結果是這樣的:

keepalive (6176.47/0/0)  

也可能是這樣的

off (0.00/0/0)

當為 keepalive 時,表明 TCP 協(xié)議棧開啟了 keep alive timers蝗碎,后面括號中的值分別是 timer 所剩時長/重傳次數(shù)/keepalive probe 次數(shù)湖笨。當為 off 時,表明沒有開啟 TCP keepalive timer蹦骑。當然赶么,正如前面所說,這個 timer 是否開啟脊串,和 HTTP keep alive 沒有必然聯(lián)系辫呻。

watch 命令

當查看服務器和客戶端之間是否啟用 HTTP keep alive 時,除了抓包以外琼锋,更為快速的方法是使用 netstat 命令看連接兩次的端口是否不變放闺。如果客戶端端口一直不變,說明服務端和客戶端之間一直保持著同一個連接缕坎。

但是怖侦,手工不停執(zhí)行 netstat 命令不是個好辦法,這里就要清楚 watch 命令谜叹。

watch 命令可以實現(xiàn)命令的反復執(zhí)行匾寝,示例如下:

watch -n1 "netstat -anpo | grep :8080 | grep EST | grep 10.110.24.202"

引號內(nèi)的便是需要執(zhí)行的命令。-n 的參數(shù)用來指定重復執(zhí)行的間隔荷腊,默認為 2秒艳悔。

總結

總結一下,首先 HTTP 和 TCP 的長連接(keep alive)是不同的機制女仰,之間沒有必然聯(lián)系猜年。而在 HTTP 方面,不同的客戶端疾忍、服務器端產(chǎn)品的行為也是不同的:Apache HTTP Client 可以通過配置連接池乔外、OkHttp 同樣可以使用連接池,但是可配置的參數(shù)不多一罩、RestTemplate 基于其它 HTTP Client 的實現(xiàn)杨幼;Nginx 默認不開啟 HTTP 長連接,也需要配置,使用 HTTP 長連接可以降低 Nginx 負載差购;應用服務器對 HTTP 長連接的行為也有不同补疑,需要分別對待。

最后編輯于
?著作權歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末歹撒,一起剝皮案震驚了整個濱河市莲组,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌暖夭,老刑警劉巖锹杈,帶你破解...
    沈念sama閱讀 211,639評論 6 492
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異迈着,居然都是意外死亡竭望,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,277評論 3 385
  • 文/潘曉璐 我一進店門裕菠,熙熙樓的掌柜王于貴愁眉苦臉地迎上來咬清,“玉大人,你說我怎么就攤上這事奴潘【缮眨” “怎么了?”我有些...
    開封第一講書人閱讀 157,221評論 0 348
  • 文/不壞的土叔 我叫張陵画髓,是天一觀的道長掘剪。 經(jīng)常有香客問我,道長奈虾,這世上最難降的妖魔是什么夺谁? 我笑而不...
    開封第一講書人閱讀 56,474評論 1 283
  • 正文 為了忘掉前任,我火速辦了婚禮肉微,結果婚禮上匾鸥,老公的妹妹穿的比我還像新娘。我一直安慰自己碉纳,他們只是感情好勿负,可當我...
    茶點故事閱讀 65,570評論 6 386
  • 文/花漫 我一把揭開白布缴川。 她就那樣靜靜地躺著件余,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上厚者,一...
    開封第一講書人閱讀 49,816評論 1 290
  • 那天,我揣著相機與錄音迫吐,去河邊找鬼库菲。 笑死,一個胖子當著我的面吹牛志膀,可吹牛的內(nèi)容都是我干的熙宇。 我是一名探鬼主播鳖擒,決...
    沈念sama閱讀 38,957評論 3 408
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼烫止!你這毒婦竟也來了蒋荚?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 37,718評論 0 266
  • 序言:老撾萬榮一對情侶失蹤馆蠕,失蹤者是張志新(化名)和其女友劉穎期升,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體互躬,經(jīng)...
    沈念sama閱讀 44,176評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡播赁,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,511評論 2 327
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了吼渡。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片容为。...
    茶點故事閱讀 38,646評論 1 340
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖寺酪,靈堂內(nèi)的尸體忽然破棺而出坎背,到底是詐尸還是另有隱情,我是刑警寧澤寄雀,帶...
    沈念sama閱讀 34,322評論 4 330
  • 正文 年R本政府宣布沼瘫,位于F島的核電站,受9級特大地震影響咙俩,放射性物質(zhì)發(fā)生泄漏耿戚。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 39,934評論 3 313
  • 文/蒙蒙 一阿趁、第九天 我趴在偏房一處隱蔽的房頂上張望膜蛔。 院中可真熱鬧,春花似錦脖阵、人聲如沸皂股。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,755評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽呜呐。三九已至,卻和暖如春悍募,著一層夾襖步出監(jiān)牢的瞬間蘑辑,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,987評論 1 266
  • 我被黑心中介騙來泰國打工坠宴, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留洋魂,地道東北人。 一個月前我還...
    沈念sama閱讀 46,358評論 2 360
  • 正文 我出身青樓,卻偏偏與公主長得像副砍,于是被迫代替她去往敵國和親衔肢。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 43,514評論 2 348

推薦閱讀更多精彩內(nèi)容