|
引言 在一個(gè)完善的即時(shí)通訊應(yīng)用中,websocket是極其關(guān)鍵的一環(huán),它為web應(yīng)用的客戶端和服務(wù)端提供了一種全雙工的通信機(jī)制,但由于它本身以及其底層依賴的TCP連接的不穩(wěn)定性,開發(fā)者不得不為其設(shè)計(jì)一套完整的?;?、驗(yàn)活、重連方案,才能在實(shí)際應(yīng)用中保證應(yīng)用的即時(shí)性和高可用性。就重連而言,其速度嚴(yán)重影響了上層應(yīng)用的“即時(shí)性”和用戶體驗(yàn),試想打開網(wǎng)絡(luò)一分鐘后,微信還不能收發(fā)消息的話,是不是要抓狂? 因此,如何在網(wǎng)絡(luò)變更時(shí)快速恢復(fù)websocket的可用,就變得尤為重要。 快速了解 websocet Websocket誕生于2008年,在2011年成為國(guó)際標(biāo)準(zhǔn),現(xiàn)在所有的瀏覽器都已支持。它是一種全新的應(yīng)用層協(xié)議,是專門為web客戶端和服務(wù)端設(shè)計(jì)的真正的全雙工通信協(xié)議, 可以類比HTTP協(xié)議來(lái)了解websocket協(xié)議。它們的不同點(diǎn): · HTTP的協(xié)議標(biāo)識(shí)符是http,websocket的是ws · HTTP請(qǐng)求只能由客戶端發(fā)起,服務(wù)器無(wú)法主動(dòng)向客戶端推送消息,而websocket可以 · HTTP請(qǐng)求有同源限制,不同源之間通信需要跨域,而websocket沒(méi)有同源限制 相同點(diǎn): · 都是應(yīng)用層的通信協(xié)議 · 默認(rèn)端口一樣,都是80或443 · 都可以用于瀏覽器和服務(wù)器間的通信 · 都基于TCP協(xié)議 兩者和TCP的關(guān)系圖:
圖片來(lái)源 重連過(guò)程拆解 首先考慮一個(gè)問(wèn)題,何時(shí)需要重連? 最容易想到的是websocket連接斷了,為了接下來(lái)能收發(fā)消息,我們需要再發(fā)起一次連接。但在很多場(chǎng)景下,即便websocket連接沒(méi)有斷開,實(shí)際上也不可用了,比如設(shè)備切換網(wǎng)絡(luò)、鏈路中間路由崩潰、服務(wù)器負(fù)載持續(xù)過(guò)高無(wú)法響應(yīng)等,這些場(chǎng)景下的websocket都沒(méi)有斷開,但對(duì)上層來(lái)說(shuō),都沒(méi)辦法正常的收發(fā)數(shù)據(jù)了。因此在重連前,我們需要一種機(jī)制來(lái)感知連接是否可用、服務(wù)是否可用,而且要能快速感知,以便能夠快速?gòu)牟豢捎脿顟B(tài)中恢復(fù)。 一旦感知到了連接不可用,那便可以棄舊圖新了,棄用并斷開舊連接,然后發(fā)起一次新連接。這兩個(gè)步驟看似簡(jiǎn)單,但若想達(dá)到快,且不是那么容易的。 首先是斷開舊連接,對(duì)客戶端來(lái)說(shuō),如何快速快速斷開?協(xié)議規(guī)定客戶端必須要和服務(wù)器協(xié)商后才能斷開websocket連接,但是當(dāng)客戶端已經(jīng)聯(lián)系不上服務(wù)器、無(wú)法協(xié)商時(shí),如何斷開并快速恢復(fù)? 其次是快速發(fā)起新連接。此快非彼快,這里的快并非是立即發(fā)起連接,立即發(fā)起連接會(huì)對(duì)服務(wù)器帶來(lái)不可預(yù)估的影響。重連時(shí)通常會(huì)采用一些退避算法,延遲一段時(shí)間后再發(fā)起重連。但如何在重連間隔和性能消耗間做出權(quán)衡?如何在“恰當(dāng)?shù)臅r(shí)間點(diǎn)”快速發(fā)起連接? 帶著這些疑問(wèn),我們來(lái)細(xì)看下這三個(gè)過(guò)程。
快速感知何時(shí)需要重連 需要重連的場(chǎng)景可以細(xì)分為三種,一是連接斷開了,二是連接沒(méi)斷但是不可用,三是連接對(duì)端的服務(wù)不可用了。 第一種場(chǎng)景很簡(jiǎn)單,連接直接斷開了,肯定需要重連了。 而對(duì)于后兩者,無(wú)論是連接不可用,還是服務(wù)不可用,對(duì)上層應(yīng)用的影響都是不能再收發(fā)即時(shí)消息了,所以從這個(gè)角度出發(fā),感知何時(shí)需要重連的一種簡(jiǎn)單粗暴的方法就是通過(guò)心跳包超時(shí):發(fā)送一個(gè)心跳包,如果超過(guò)特定的時(shí)間后還沒(méi)有收到服務(wù)器回包,則認(rèn)為服務(wù)不可用,如下圖中左側(cè)的方案;這種方法最直接。那如果想要 快速感知 呢,就只能多發(fā)心跳包,加快心跳頻率。但是心跳太快對(duì)移動(dòng)端流量、電量的消耗又會(huì)太多,所以使用這種方法沒(méi)辦法做到快速感知,可以作為檢測(cè)連接和服務(wù)可用的兜底機(jī)制。
如果要檢測(cè)連接不可用,除了用心跳檢測(cè),還可以通過(guò)判斷網(wǎng)絡(luò)狀態(tài)來(lái)實(shí)現(xiàn),因?yàn)閿嗑W(wǎng)、切換 wifi、切換網(wǎng)絡(luò)是導(dǎo)致連接不可用的最直接原因,所以在網(wǎng)絡(luò)狀態(tài)由offline變?yōu)閛nline時(shí),大多數(shù)情況下需要重連下,但也不一定,因?yàn)閣ebscoket底層是基于TCP的,TCP連接不能敏銳的感知到應(yīng)用層的網(wǎng)絡(luò)變化,所以有時(shí)候即便網(wǎng)絡(luò)斷開了一小會(huì),對(duì)websocket連接是不會(huì)有影響的,網(wǎng)絡(luò)恢復(fù)后,仍然能夠正常地進(jìn)行通信。因此在網(wǎng)絡(luò)由斷開到連接上時(shí),立即判斷下連接是否可用,可以通過(guò)發(fā)一個(gè)心跳包判斷,如果能夠正常收到服務(wù)器的心跳回包,則說(shuō)明連接仍是可用的,如果等待超時(shí)后仍沒(méi)有收到心跳回包,則需要重連,如上圖中的右側(cè)。這種方法的優(yōu)點(diǎn)是速度快,在網(wǎng)絡(luò)恢復(fù)后能夠第一時(shí)間感知連接是否可用,不可用的話可以快速執(zhí)行恢復(fù),但它只能覆蓋應(yīng)用層網(wǎng)絡(luò)變化導(dǎo)致websocket不可用的情況。 綜上,定時(shí)發(fā)送心跳包檢測(cè)的方案貴在穩(wěn)定,能夠覆蓋所有場(chǎng)景,但速度不太可;而判斷網(wǎng)絡(luò)狀態(tài)的方案速度快,無(wú)需等待心跳間隔,較為靈敏,但覆蓋場(chǎng)景較為局限。因此,我們可以結(jié)合兩種方案:定時(shí)以不太快的頻率發(fā)送心跳包,比如40s/次、60s/次等,具體可以根據(jù)應(yīng)用場(chǎng)景來(lái)定,然后在網(wǎng)絡(luò)狀態(tài)由offline變?yōu)閛nline時(shí)立即發(fā)送一次心跳,檢測(cè)當(dāng)前連接是否可用,不可用的話立即進(jìn)行恢復(fù)處理。這樣在大多數(shù)情況下,上層的應(yīng)用通信都能較快從不可用狀態(tài)中恢復(fù),對(duì)于少部分場(chǎng)景,有定時(shí)心跳作為兜底,在一個(gè)心跳周期內(nèi)也能夠恢復(fù)。 快速斷開舊連接 通常情況下,在發(fā)起下一次連接前,如果舊連接還存在的話,應(yīng)該先把舊連接斷開,這樣一來(lái)可以釋放客戶端和服務(wù)器的資源,二來(lái)可以避免之后誤從舊連接收發(fā)數(shù)據(jù)。 我們知道websocket底層是基于TCP協(xié)議傳輸數(shù)據(jù)的,連接兩端分別是服務(wù)器和客戶端,而TCP的TIME_WAIT狀態(tài)是由服務(wù)器端維持的,因此在大多數(shù)正常情況下,應(yīng)該由服務(wù)器發(fā)起斷開底層TCP連接,而不是客戶端。也就是說(shuō),要斷開websocket連接時(shí),如果是服務(wù)器收到指示要斷開websocket,那它應(yīng)該立即發(fā)起斷開TCP連接;如果是客戶端收到指示要斷開websocket,那它應(yīng)該發(fā)信號(hào)給服務(wù)器,然后等待底層TCP連接被服務(wù)器斷開或直至超時(shí)。 那如果客戶端想要斷開舊的websocket,可以分websocket連接可用和不可用兩種情況來(lái)討論。當(dāng)舊連接可用時(shí),客戶端可以直接給服務(wù)器發(fā)送斷開信號(hào),然后服務(wù)器發(fā)起斷開連接即可;當(dāng)舊連接不可用時(shí),比如客戶端切換了wifi,客戶端發(fā)送了斷開信號(hào),但是服務(wù)器收不到,客戶端只能遲遲等待,直至超時(shí)才能被允許斷開。超時(shí)斷開的過(guò)程相對(duì)來(lái)說(shuō)是比較久的,那有沒(méi)有辦法可以快點(diǎn)斷開? 上層應(yīng)用無(wú)法改變只能由服務(wù)器發(fā)起斷開連接這種協(xié)議層面的規(guī)則,所以只能從應(yīng)用邏輯入手,比如在上層通過(guò)業(yè)務(wù)邏輯保證舊連接完全失效,模擬連接斷開,然后在發(fā)起新連接,恢復(fù)通訊。這種方法相當(dāng)于嘗試斷開舊連接不行時(shí),直接棄之,然后就能快速進(jìn)入下一流程,所以在使用時(shí)一定要確保在業(yè)務(wù)邏輯上舊連接已完全失效,比如:保證丟掉從舊連接收到所有數(shù)據(jù)、舊連接不能阻礙新連接的建立,舊連接超時(shí)斷開后不能影響新連接和上層業(yè)務(wù)邏輯等等。 快速發(fā)起新連接 有IM開發(fā)經(jīng)驗(yàn)的同學(xué)應(yīng)該有所了解,遇到因網(wǎng)絡(luò)原因?qū)е碌闹剡B時(shí),是萬(wàn)萬(wàn)不能立即發(fā)起一次新連接的,否則當(dāng)出現(xiàn)網(wǎng)絡(luò)抖動(dòng)時(shí),所有的設(shè)備都會(huì)立即同時(shí)向服務(wù)器發(fā)起連接,這無(wú)異于黑客通過(guò)發(fā)起大量請(qǐng)求消耗網(wǎng)絡(luò)帶寬引起的拒絕服務(wù)攻擊,這對(duì)服務(wù)器來(lái)說(shuō)簡(jiǎn)直是災(zāi)難。所以在重連時(shí)通常采用一些退避算法,延遲一段時(shí)間再發(fā)起重連,如下圖中左側(cè)的流程。
如果要快速連上呢?最直接的做法就是縮短重試間隔,重試間隔越短,在網(wǎng)絡(luò)恢復(fù)后就能越快的恢復(fù)通訊。但是太頻繁的重試對(duì)性能、帶寬、電量的消耗就比較嚴(yán)重。如何在這之間做一個(gè)較好的權(quán)衡呢? 一種比較合理的方式是隨著重試次數(shù)增多,逐漸增大重試間隔;另一方面監(jiān)聽(tīng)網(wǎng)絡(luò)變化,在網(wǎng)絡(luò)狀態(tài)由offline變?yōu)閛nline這種比較可能重連上的時(shí)刻,可以適當(dāng)?shù)販p小重連間隔,如上圖中的右側(cè)(隨重試次數(shù)的增多,重連間隔也會(huì)變大),兩種方式配合使用。 除此之外,還可以結(jié)合業(yè)務(wù)邏輯,根據(jù)成功重連上的可能性適當(dāng)?shù)恼{(diào)整間隔,如網(wǎng)絡(luò)未連接時(shí)或應(yīng)用在后臺(tái)時(shí)重連間隔可以調(diào)大一些,網(wǎng)絡(luò)正常的狀態(tài)下可以適當(dāng)調(diào)小一些等等,加快重連上的速度。 結(jié)尾 最后總結(jié)一下,本文在開頭將websocket斷網(wǎng)重連細(xì)分為三個(gè)步驟:確定何時(shí)需要重連、斷開舊連接和發(fā)起新連接。然后分別分析了在websocket的不同狀態(tài)下、不同的網(wǎng)絡(luò)狀態(tài)下,如何快速完成這個(gè)三個(gè)步驟:首先通過(guò)定時(shí)發(fā)送心跳包的方式檢測(cè)當(dāng)前連接是否可用,同時(shí)監(jiān)測(cè)網(wǎng)絡(luò)恢復(fù)事件,在恢復(fù)后立即發(fā)送一次心跳,快速感知當(dāng)前狀態(tài),判斷是否需要重連;其次正常情況下由服務(wù)器斷開舊連接,與服務(wù)器失去聯(lián)系時(shí)直接棄用舊連接,上層模擬斷開,來(lái)實(shí)現(xiàn)快速斷開;最后發(fā)起新連接時(shí)使用退避算法延遲一段時(shí)間再發(fā)起連接,同時(shí)考慮到資源浪費(fèi)和重連速度,可以在網(wǎng)絡(luò)離線時(shí)調(diào)大重連間隔,在網(wǎng)絡(luò)正?;蚓W(wǎng)絡(luò)由offline變?yōu)閛nline時(shí)縮小重連間隔,使之盡可能快地重連上。
|
|
|
來(lái)自: 鴻蛟家平 > 《網(wǎng)站設(shè)置》