一個完整的軟件系統(tǒng)大多數(shù)情況下是由多個進(jìn)程共同協(xié)作進(jìn)行的价脾,哪怕它們在同一臺服務(wù)器上贱纠。所以伞访,進(jìn)程之間如何進(jìn)行高效的通信至關(guān)重要蝶涩。
單個應(yīng)用程序 + 單個數(shù)據(jù)庫這套基礎(chǔ)開發(fā)套餐我相信每個人都經(jīng)歷過,甚至在初期它們還有可能部署在同一臺服務(wù)器上绩蜻。既然應(yīng)用程序和數(shù)據(jù)庫分屬于兩個不同的進(jìn)程铣墨,所以這個問題本質(zhì)上還是兩個進(jìn)程之間的通信問題。
兩個進(jìn)程之間如果要通信办绝,很顯然必須要建立一個連接伊约,通過它來相互傳輸數(shù)據(jù)。原則上孕蝉,如果兩個進(jìn)程在同一臺服務(wù)器上屡律,有很多種方式可以進(jìn)行相互通信。不過在分布式系統(tǒng)中降淮,不同的進(jìn)程很多時候被部署在不同的服務(wù)器上超埋。所以我們這次只聊基于 TCP/IP 的通信方式,因為對大家來說這是最普遍會用到的方式佳鳖,不管是應(yīng)用程序間的遠(yuǎn)程調(diào)用(RPC)還是應(yīng)用程序與數(shù)據(jù)庫間的調(diào)用(DAL)霍殴,皆是如此。
通過套接字(socket)建立連接
socket 與 TCP/IP 之間是唇齒相依般的關(guān)系系吩,聯(lián)系緊密来庭,先來看下維基百科對 socket 的定義。
socket 是計算機(jī)網(wǎng)絡(luò)中用于在節(jié)點內(nèi)發(fā)送或接收數(shù)據(jù)的內(nèi)部端點穿挨。具體來說月弛,它是網(wǎng)絡(luò)軟件 (協(xié)議棧) 中這個端點的一種表示,包含通信協(xié)議絮蒿、目標(biāo)地址尊搬、狀態(tài)等,是系統(tǒng)資源的一種形式土涝。
它在網(wǎng)絡(luò)中所處的位置大致就是下面的黑色部分,應(yīng)用層與傳輸層之間幌墓。
其中的傳輸層就是 TCP/IP 所在的地方但壮,而你平時通過代碼編寫的應(yīng)用程序大多屬于應(yīng)用層范疇冀泻,socket 在這里起到就是連接應(yīng)用層與傳輸層的作用。
socket 的誕生是為了應(yīng)用程序能夠更方便的將數(shù)據(jù)經(jīng)由傳輸層來傳輸蜡饵,所以它本質(zhì)上就是對 TCP/IP 的運用進(jìn)行了一層封裝弹渔,然后應(yīng)用程序直接調(diào)用 socket API 即可進(jìn)行通信。那么它是如何工作的呢溯祸?它分為 2 個部分肢专,服務(wù)端需要建立 socket 來監(jiān)聽指定的地址,然后等待客戶端來連接焦辅。而客戶端則需要建立 socket 并與服務(wù)端的 socket 地址進(jìn)行連接博杖。
這圖展示的就是建立 TCP/IP 連接的過程,經(jīng)典的叫法為“三次握手”的過程筷登。顧名思義剃根,這個過程中來回產(chǎn)生了三次網(wǎng)絡(luò)通信。
接下來的數(shù)據(jù)傳輸過程就簡單很多前方,發(fā)送數(shù)據(jù)就是客戶端往服務(wù)端通信狈醉,服務(wù)端處理完之后的數(shù)據(jù)返回則相反。
值得注意的是惠险,傳輸?shù)倪^程涉及到數(shù)據(jù) Copy苗傅,不過這些 Copy 是必不可少的。其中的發(fā)送緩沖區(qū)和接收緩沖區(qū)就是套接字緩存 (socket buffer)班巩。
連接使用完之后需要關(guān)閉金吗,不過 TCP/IP 連接關(guān)閉過程比創(chuàng)建更復(fù)雜一些,次數(shù)多了一次趣竣,這就是經(jīng)典的“四次握手”過程摇庙。
簡單總結(jié)一下 socket。socket 是進(jìn)程間數(shù)據(jù)傳輸?shù)拿浇橐B疲瑸榱吮WC連接的可靠卫袒,你需要特別注意建立連接和關(guān)閉連接的過程。為了確保準(zhǔn)確单匣、完整的數(shù)據(jù)傳輸夕凝,客戶端和服務(wù)端來回進(jìn)行了多次網(wǎng)絡(luò)通信才得以完成連接的創(chuàng)建和關(guān)閉,這同時也是你在運用一個連接時所花費的額外成本户秤。
基于 socket 我們可以選擇建立長連接或者短連接码秉,在實際運用中兩者都有可能被用到。
長連接和短連接的區(qū)別
先帶你來認(rèn)識一下它倆的區(qū)別鸡号。
長連接意味著進(jìn)行一次數(shù)據(jù)傳輸后转砖,不關(guān)閉連接,長期保持連通狀態(tài)。如果兩個應(yīng)用程序之間有新的數(shù)據(jù)需要傳輸府蔗,則直接復(fù)用這個連接晋控,無需再建立一個新的連接。就像下圖這樣姓赤。
它的優(yōu)勢是在多次通信中可以省去連接建立和關(guān)閉連接的開銷赡译,并且從總體上來看,進(jìn)行多次數(shù)據(jù)傳輸?shù)目偤臅r更少不铆。缺點是需要花費額外的精力來保持這個連接一直是可用的蝌焚,因為網(wǎng)絡(luò)抖動、服務(wù)器故障等都會導(dǎo)致這個連接不可用誓斥,甚至是由于防火墻的原因只洒。所以,一般我們會通過下面這幾種方式來做“贬常活”工作红碑,確保連接在被使用的時候是可用狀態(tài):
- 利用 TCP 自身的保活(Keepalive)機(jī)制來實現(xiàn)泡垃,蔽錾海活機(jī)制會定時發(fā)送探測報文來識別對方是否可達(dá)。一般的默認(rèn)定時間隔是 2 小時蔑穴,你可以根據(jù)自己的需要在操作系統(tǒng)層面去調(diào)整這個間隔忠寻,不管是 Linux 還是 Windows 系統(tǒng)。
- 上層應(yīng)用主動的定時發(fā)送一個小數(shù)據(jù)包作為“心跳”存和,探測是否能成功送達(dá)到另外一端奕剃。 保活功能大多數(shù)情況下用于服務(wù)端探測客戶端的場景捐腿,一旦識別客戶端不可達(dá)纵朋,則斷開連接,緩解服務(wù)端壓力茄袖。
提前多說一句操软,如果在做了高可用的分布式系統(tǒng)場景中運用長連接會更麻煩一些。因為高可用必然包含自動故障轉(zhuǎn)移宪祥、故障隔離等機(jī)制聂薪。這恰恰導(dǎo)致了一旦發(fā)生故障,客戶端需要及時發(fā)現(xiàn)哪些連接已處于不可用狀態(tài)蝗羊,并進(jìn)行相應(yīng)的重連藏澳,包括重新做負(fù)載均衡等工作。
了解完了長連接耀找,那么短連接就很容易理解了翔悠。短連接意味著每一次的數(shù)據(jù)傳輸都需要建立一個新的連接,用完再馬上關(guān)閉它。下次再用的時候重新建立一個新的連接凉驻,如此反復(fù)腻要。
它的優(yōu)勢是由于每次使用的連接都是新建的复罐,所以基本上只要能夠建立連接涝登,數(shù)據(jù)就大概率能送達(dá)到對方。并且哪怕這次傳輸出現(xiàn)異常也不用擔(dān)心影響后續(xù)新的數(shù)據(jù)傳輸效诅,因為屆時又是一個新的連接胀滚。缺點是每個連接都需要經(jīng)過三次握手和四次握手的過程,耗時大大增加乱投。
另外咽笼,短連接還有一個致命的缺點。我們回到前面提到的維基百科對 socket 的定義戚炫,其中說到socket 包含通信協(xié)議剑刑、目標(biāo)地址、狀態(tài)等双肤。實際當(dāng)你在基于 socket 進(jìn)行開發(fā)的時候施掏,這些包含的具體資源主要就是這 5 個:源 IP、源端口茅糜、目的 IP七芭、目的端口、協(xié)議蔑赘,有個專業(yè)的叫法稱之為“五元組”狸驳。在一臺計算機(jī)上只要這五元組的值不重復(fù),那么連接就可以被建立缩赛。然而一臺計算機(jī)最多只能開啟 65535 個端口耙箍,如果現(xiàn)在兩個進(jìn)程之間需要通信,作為服務(wù)端的 IP 和端口必然是固定的酥馍,因此單個客戶端理論上最多只能與服務(wù)端同時建立 65535 個 socket 連接辩昆。如果除去操作系統(tǒng)和其它進(jìn)程所占用的端口,實際還會更少物喷。所以卤材,一旦使用不當(dāng),在很短的時間內(nèi)建立了大量連接峦失,端口很容易被占用完扇丛。這不但會導(dǎo)致自身無法正常工作,還會影響到同一臺計算機(jī)上的其它進(jìn)程尉辑。
我猜你在項目中大多數(shù)情況使用的是短連接的方式帆精,因為這對我們編程來說可以少考慮很多問題,潛在的這些缺點可能是你沒有遇到或者意識到而已。存在必有其價值卓练,接下去我們根據(jù)實際的案例讓你清楚知道如何來選擇它們。
長連接和短連接的選擇
我想你肯定見過一些監(jiān)控或者實時報價類系統(tǒng)襟企,比如股票軟件嘱么,它需要在幾秒之內(nèi)刷新最新的價格。像這種場景中同時包含了需要運用長連接的三個主要因素:高頻曼振、服務(wù)端主動推送和有狀態(tài)。
- 高頻的原因我想你根據(jù)前面的內(nèi)容也明白了甲雅,因為頻次越高的話抛人,使用短連接帶來的建立連接和關(guān)閉連接的總開銷越大函匕。
- 而服務(wù)端主動推送也需要長連接的原因是,由于服務(wù)端往往是“中心化”的掠剑,一般都是 1 個服務(wù)端為多個客戶端提供服務(wù)。所以例嘱,如果使用短連接的方式拼卵,那么在客戶端未主動連接到服務(wù)端的情況下腋腮,服務(wù)端并不知道需要往哪些客戶端去推送數(shù)據(jù)低葫,這是原因之一水泉。所以此時蟹漓,長連接成為了一個很好的選擇。另外一個原因是拾枣,哪怕客戶端通過定時的短連接輪詢方式進(jìn)行主動連接梅肤,除了增加了額外的建立連接和關(guān)閉連接的開銷外,還可能遇到通信完成后結(jié)果數(shù)據(jù)并未發(fā)生變化邑茄,做了無用功姨蝴。
- 成熟股票軟件的服務(wù)端,為了支撐更多的用戶以及做高可用撩扒,必然部署了多臺似扔。但是這個業(yè)務(wù)場景吨些,用戶無法容忍由于多個服務(wù)端之間數(shù)據(jù)同步的誤差導(dǎo)致他在客戶端看到的價格刷新產(chǎn)生“回退”現(xiàn)象。所以炒辉,只能盡量保持一直連接在同一臺服務(wù)器上豪墅,才能避免這個情況。這種場景被稱之為“有狀態(tài)”黔寇,也可以理解為是“串行”的偶器,因為多次請求的前后需要保持“連續(xù)性”。
短連接則更適用于諸如閱讀類軟件的場景中缝裤,例如屏轰,很多時候用戶點開一篇文章后需要花一些時間進(jìn)行閱讀,這個時間有長有短憋飞,并且直到用戶下一次操作之前都沒有數(shù)據(jù)傳輸發(fā)生霎苗。這個場景中包含了運用短連接的兩個主要因素:低頻、無狀態(tài)榛做。
- 因為低頻唁盏,所以更能容忍建立連接和關(guān)閉連接的開銷。
- 用戶的下一次點擊往往跳轉(zhuǎn)到了其它文章检眯,并且新打開的與當(dāng)前文章并不需要具有“連續(xù)性”厘擂,所以這種場景我們稱之為“無狀態(tài)”的。另外锰瘸,理論上同一時刻打開幾篇文章也不會存在什么不妥刽严。
通過這兩個案例我們可以總結(jié)出一個決定何時運用長連接和短連接的最佳實踐。
長連接適用于:兩個進(jìn)程之間需要高頻通信并且具備服務(wù)端主動推送或者有狀態(tài)(需串行)兩者之一的場景避凝,否則并不是必選項舞萄。
短連接適用于:兩個進(jìn)程之間通信頻率較低,或者屬于無狀態(tài)(可并行)的場景恕曲,否則并不是必選項鹏氧。
其它情況就根據(jù)所需的側(cè)重點來,比如側(cè)重性能就長連接佩谣,側(cè)重編碼的便捷性就選擇短連接把还。
總結(jié)
至此,相信你應(yīng)該清楚了長連接和短連接在跨進(jìn)程通信中該如何選擇茸俭,而且還對通過 socket 建立 TCP 連接有了一定的認(rèn)識吊履。你在實際的工作中可能遇到的場景千奇百怪,只需要保持先識別所處場景的特點调鬓,再基于這些特點來作出選擇的習(xí)慣艇炎,必然至少是個不錯的決策。
不過有時候我們可能需要一個中庸的方案來作為默認(rèn)選擇腾窝,因為很多場景中的請求并不是平穩(wěn)的缀踪,甚至波動會較大居砖,而且可能同時存在有狀態(tài)和無狀態(tài)的場景,此時如果單方面的選擇長連接或者短連接都會產(chǎn)生較多的資源浪費驴娃。那么我們可以通過增加一些復(fù)雜度來實現(xiàn)一個能夠綜合長連接和短連接各自優(yōu)點的方案:建立多個長連接奏候,每次數(shù)據(jù)傳輸?shù)臅r候獨占使用,用完之后放回唇敞,再給后續(xù)使用蔗草。這種方案被稱之為“連接池”。例如, 很多的數(shù)據(jù)庫訪問框架都內(nèi)置了連接池機(jī)制疆柔,因為作為底層框架的它不知道會被使用到何種場景的系統(tǒng)中咒精,所以提供了這個選項。
連接池的運作流程大致如下圖旷档。除了上面所說的模叙,獨占使用,用完放回之外彬犯,一般都會在應(yīng)用程序啟動時預(yù)先建立好指定數(shù)據(jù)量的連接向楼,以更好應(yīng)對冷啟動后請求數(shù)快速上升帶來的資源競爭問題,這個數(shù)量一般稱之為最小連接數(shù)谐区。另外,如果新的請求進(jìn)來時逻卖,所有已建立的連接都在使用中宋列,但是連接數(shù)的上限未達(dá)到指定數(shù)量,可以再建立新的長連接來使用评也,用完依舊放回到空閑池炼杖,相當(dāng)于把連接池擴(kuò)大了,這個上限數(shù)量一般稱之為最大連接數(shù)盗迟。
轉(zhuǎn)載地址:https://cloud.tencent.com/developer/article/1470024