天天看點

長連接配接和短連接配接詳細解析

長連接配接和短連接配接詳細解析

一個完整的軟體系統大多數情況下是由多個程序共同協作進行的,哪怕它們在同一台伺服器上。是以,程序之間如何進行高效的通信至關重要。

單個應用程式 + 單個資料庫這套基礎開發套餐我相信每個人都經曆過,甚至在初期它們還有可能部署在同一台伺服器上。既然應用程式和資料庫分屬于兩個不同的程序,是以這個問題本質上還是兩個程序之間的通信問題。

兩個程序之間如果要通信,很顯然必須要建立一個連接配接,通過它來互相傳輸資料。原則上,如果兩個程序在同一台伺服器上,有很多種方式可以進行互相通信。不過在分布式系統中,不同的程序很多時候被部署在不同的伺服器上。是以我們這次隻聊基于 TCP/IP 的通信方式,因為對大家來說這是最普遍會用到的方式,不管是應用程式間的遠端調用(RPC)還是應用程式與資料庫間的調用(DAL),皆是如此。

socket 與 TCP/IP 之間是唇齒相依般的關系,聯系緊密,先來看下維基百科對 socket 的定義。

socket 是計算機網絡中用于在節點内發送或接收資料的内部端點。具體來說,它是網絡軟體 (協定棧) 中這個端點的一種表示,包含通信協定、目标位址、狀态等,是系統資源的一種形式。

它在網絡中所處的位置大緻就是下面的黑色部分,應用層與傳輸層之間。

長連接配接和短連接配接詳細解析

其中的傳輸層就是 TCP/IP 所在的地方,而你平時通過代碼編寫的應用程式大多屬于應用層範疇,socket 在這裡起到就是連接配接應用層與傳輸層的作用。

socket 的誕生是為了應用程式能夠更友善的将資料經由傳輸層來傳輸,是以它本質上就是對 TCP/IP 的運用進行了一層封裝,然後應用程式直接調用 socket API 即可進行通信。那麼它是如何工作的呢?它分為 2 個部分,服務端需要建立 socket 來監聽指定的位址,然後等待用戶端來連接配接。而用戶端則需要建立 socket 并與服務端的 socket 位址進行連接配接。

長連接配接和短連接配接詳細解析

這圖展示的就是建立 TCP/IP 連接配接的過程,經典的叫法為“三次握手”的過程。顧名思義,這個過程中來回産生了三次網絡通信。

接下來的資料傳輸過程就簡單很多,發送資料就是用戶端往服務端通信,服務端處理完之後的資料傳回則相反。

長連接配接和短連接配接詳細解析

值得注意的是,傳輸的過程涉及到資料 Copy,不過這些 Copy 是必不可少的。其中的發送緩沖區和接收緩沖區就是套接字緩存 (socket buffer)。

長連接配接和短連接配接詳細解析

連接配接使用完之後需要關閉,不過 TCP/IP 連接配接關閉過程比建立更複雜一些,次數多了一次,這就是經典的“四次握手”過程。

簡單總結一下 socket。socket 是程序間資料傳輸的媒介,為了保證連接配接的可靠,你需要特别注意建立連接配接和關閉連接配接的過程。為了確定準确、完整的資料傳輸,用戶端和服務端來回進行了多次網絡通信才得以完成連接配接的建立和關閉,這同時也是你在運用一個連接配接時所花費的額外成本。

基于 socket 我們可以選擇建立長連接配接或者短連接配接,在實際運用中兩者都有可能被用到。

先帶你來認識一下它倆的差別。

長連接配接意味着進行一次資料傳輸後,不關閉連接配接,長期保持連通狀态。如果兩個應用程式之間有新的資料需要傳輸,則直接複用這個連接配接,無需再建立一個新的連接配接。就像下圖這樣。

長連接配接和短連接配接詳細解析

它的優勢是在多次通信中可以省去連接配接建立和關閉連接配接的開銷,并且從總體上來看,進行多次資料傳輸的總耗時更少。缺點是需要花費額外的精力來保持這個連接配接一直是可用的,因為網絡抖動、伺服器故障等都會導緻這個連接配接不可用,甚至是由于防火牆的原因。是以,一般我們會通過下面這幾種方式來做“保活”工作,確定連接配接在被使用的時候是可用狀态:

利用 TCP 自身的保活(Keepalive)機制來實作,保活機制會定時發送探測封包來識别對方是否可達。一般的預設定時間隔是 2 小時,你可以根據自己的需要在作業系統層面去調整這個間隔,不管是 Linux 還是 Windows 系統。

用戶端的長連接配接不可能無限期的拿着,會有一個逾時時間,伺服器有時候會告訴用戶端逾時時間,

長連接配接和短連接配接詳細解析

上圖中的Keep-Alive: timeout=20,表示這個TCP通道可以保持20秒。另外還可能有max=XXX,表示這個長連接配接最多接收XXX次請求就斷開。對于用戶端來說,如果伺服器沒有告訴用戶端逾時時間也沒關系,服務端可能主動發起四次握手斷開TCP連接配接,用戶端能夠知道該TCP連接配接已經無效;另外TCP還有心跳包來檢測目前連接配接是否還活着,方法很多,避免浪費資源。

上層應用主動的定時發送一個小資料包作為“心跳”,探測是否能成功送達到另外一端。 保活功能大多數情況下用于服務端探測用戶端的場景,一旦識别用戶端不可達,則斷開連接配接,緩解服務端壓力。

提前多說一句,如果在做了高可用的分布式系統場景中運用長連接配接會更麻煩一些。因為高可用必然包含自動故障轉移、故障隔離等機制。這恰恰導緻了一旦發生故障,用戶端需要及時發現哪些連接配接已處于不可用狀态,并進行相應的重連,包括重新做負載均衡等工作。

了解完了長連接配接,那麼短連接配接就很容易了解了。短連接配接意味着每一次的資料傳輸都需要建立一個新的連接配接,用完再馬上關閉它。下次再用的時候重建立立一個新的連接配接,如此反複。

長連接配接和短連接配接詳細解析

它的優勢是由于每次使用的連接配接都是建立的,是以基本上隻要能夠建立連接配接,資料就大機率能送達到對方。并且哪怕這次傳輸出現異常也不用擔心影響後續新的資料傳輸,因為屆時又是一個新的連接配接。缺點是每個連接配接都需要經過三次握手和四次握手的過程,耗時大大增加。

另外,短連接配接還有一個緻命的缺點。我們回到前面提到的維基百科對 socket 的定義,其中說到socket 包含通信協定、目标位址、狀态等。實際當你在基于 socket 進行開發的時候,這些包含的具體資源主要就是這 5 個:源 IP、源端口、目的 IP、目的端口、協定,有個專業的叫法稱之為“五元組”。在一台計算機上隻要這五元組的值不重複,那麼連接配接就可以被建立。然而一台計算機最多隻能開啟 65535 個端口,如果現在兩個程序之間需要通信,作為服務端的 IP 和端口必然是固定的,是以單個用戶端理論上最多隻能與服務端同時建立 65535 個 socket 連接配接。如果除去作業系統和其它程序所占用的端口,實際還會更少。是以,一旦使用不當,在很短的時間内建立了大量連接配接,端口很容易被占用完。這不但會導緻自身無法正常工作,還會影響到同一台計算機上的其它程序。

我猜你在項目中大多數情況使用的是短連接配接的方式,因為這對我們程式設計來說可以少考慮很多問題,潛在的這些缺點可能是你沒有遇到或者意識到而已。存在必有其價值,接下去我們根據實際的案例讓你清楚知道如何來選擇它們。

我想你肯定見過一些監控或者實時報價類系統,比如股票軟體,它需要在幾秒之内重新整理最新的價格。像這種場景中同時包含了需要運用長連接配接的三個主要因素:高頻、服務端主動推送和有狀态。

高頻的原因我想你根據前面的内容也明白了,因為頻次越高的話,使用短連接配接帶來的建立連接配接和關閉連接配接的總開銷越大。

而服務端主動推送也需要長連接配接的原因是,由于服務端往往是“中心化”的,一般都是 1 個服務端為多個用戶端提供服務。是以,如果使用短連接配接的方式,那麼在用戶端未主動連接配接到服務端的情況下,服務端并不知道需要往哪些用戶端去推送資料,這是原因之一。是以此時,長連接配接成為了一個很好的選擇。另外一個原因是,哪怕用戶端通過定時的短連接配接輪詢方式進行主動連接配接,除了增加了額外的建立連接配接和關閉連接配接的開銷外,還可能遇到通信完成後結果資料并未發生變化,做了無用功。

成熟股票軟體的服務端,為了支撐更多的使用者以及做高可用,必然部署了多台。但是這個業務場景,使用者無法容忍由于多個服務端之間資料同步的誤差導緻他在用戶端看到的價格重新整理産生“回退”現象。是以,隻能盡量保持一直連接配接在同一台伺服器上,才能避免這個情況。這種場景被稱之為“有狀态”,也可以了解為是“串行”的,因為多次請求的前後需要保持“連續性”。

短連接配接則更适用于諸如閱讀類軟體的場景中,例如,很多時候使用者點開一篇文章後需要花一些時間進行閱讀,這個時間有長有短,并且直到使用者下一次操作之前都沒有資料傳輸發生。這個場景中包含了運用短連接配接的兩個主要因素:低頻、無狀态。

因為低頻,是以更能容忍建立連接配接和關閉連接配接的開銷。

使用者的下一次點選往往跳轉到了其它文章,并且新打開的與目前文章并不需要具有“連續性”,是以這種場景我們稱之為“無狀态”的。另外,理論上同一時刻打開幾篇文章也不會存在什麼不妥。

通過這兩個案例我們可以總結出一個決定何時運用長連接配接和短連接配接的最佳實踐。

長連接配接适用于:兩個程序之間需要高頻通信并且具備服務端主動推送或者有狀态(需串行)兩者之一的場景,否則并不是必選項。

短連接配接适用于:兩個程序之間通信頻率較低,或者屬于無狀态(可并行)的場景,否則并不是必選項。

其它情況就根據所需的側重點來,比如側重性能就長連接配接,側重編碼的便捷性就選擇短連接配接。

至此,相信你應該清楚了長連接配接和短連接配接在跨程序通信中該如何選擇,而且還對通過 socket 建立 TCP 連接配接有了一定的認識。你在實際的工作中可能遇到的場景千奇百怪,隻需要保持先識别所處場景的特點,再基于這些特點來作出選擇的習慣,必然至少是個不錯的決策。

不過有時候我們可能需要一個中庸的方案來作為預設選擇,因為很多場景中的請求并不是平穩的,甚至波動會較大,而且可能同時存在有狀态和無狀态的場景,此時如果單方面的選擇長連接配接或者短連接配接都會産生較多的資源浪費。那麼我們可以通過增加一些複雜度來實作一個能夠綜合長連接配接和短連接配接各自優點的方案:建立多個長連接配接,每次資料傳輸的時候獨占使用,用完之後放回,再給後續使用。這種方案被稱之為“連接配接池”。例如, 很多的資料庫通路架構都内置了連接配接池機制,因為作為底層架構的它不知道會被使用到何種場景的系統中,是以提供了這個選項。

連接配接池的運作流程大緻如下圖。除了上面所說的,獨占使用,用完放回之外,一般都會在應用程式啟動時預先建立好指定資料量的連接配接,以更好應對冷啟動後請求數快速上升帶來的資源競争問題,這個數量一般稱之為最小連接配接數。另外,如果新的請求進來時,所有已建立的連接配接都在使用中,但是連接配接數的上限未達到指定數量,可以再建立新的長連接配接來使用,用完依舊放回到空閑池,相當于把連接配接池擴大了,這個上限數量一般稱之為最大連接配接數。

長連接配接和短連接配接詳細解析

繼續閱讀