場景之心跳應用

2022-08-29 06:01:44

一、心跳概述

常見的IM類應用,比如遊戲,直播,聊天室或者客服系統,一般都要依靠伺服器端做訊息中轉,將從傳送方接受的訊息推播給接收方,為保證可靠,快速到達對端,⼤部分IM使⽤長連線建⽴通道,並且建⽴TCP連線和使用者裝置的對映關係,長連線⼀旦建⽴,就會⼀直存在,除非意外被中斷,並依靠該連結接受和推播訊息。
心跳機制存在的意義:這個長連線並不是物理意義上的連線,⽽是⼀個無感知的虛擬TCP連線,即使斷開兩端也不會感知,因此心跳機制來讓連線在出現問題的時候,迅速的讓對端感知到,心跳要做的就是快速不間斷的識別來探測連線可⽤性。

1、心跳機制在長連線維護的必要性

  • 降低伺服器端維持連線的開銷:伺服器端除了維持⽹絡連線和使用者裝置的對映之外,還會儲存⼀些包括app版本號,os系統型別等資訊,這些資訊第⼀次建立連線傳送,以後都維持到伺服器端快取,如果存在⼤量的斷開的連線,就會造成具柄和快取的浪費,因此⼼跳機制保證了在連線斷開後儘早的清理伺服器端連線使⽤的資源。

  • ⽀持使用者端斷線重連:⼀般移動裝置連線的都是運營商的內網,⽽IPv4地址總共約為43億個,因此為了節省IP地址資源,運營商通常會採⽤NAT⼿段,為聯⽹裝置分配內⽹IP,變成了內網IP:port-外網IP:port的對映,使得內網IP的⼿機能和外網聯通,但為了提⾼IP利⽤率,如果⼀個連線⼀段時間沒有資料收發,運營商就會把這個對映從NAT對映表清除掉,為了避免長連線被被運營商下掉,就需要長跳機制定期傳送⼀些資料對連線讓運營商以為這個連結還在用從而進行保活。

2、心跳實現方式

一般有三種:

  • TCP層⾯的keep-alive,傳送端在傳送完資料給接收方之後,接收方會啟動一個超時計時器,每當計時器超時就會傳送探測報⽂給傳送方,心跳包不攜帶資料,累計10次心跳包沒有迴應則認為連結失效斷開了。協定棧實現使⽤資源最少,但是心跳間隔設定不靈活,TCP的keep-Alive只代表連結層,不代表應⽤層可⽤,比如程式碼死鎖,同樣tcp連結可用,但是服務已經不可用了。
  • 應⽤層心跳:應⽤層⼼跳實際上就是客⼾端每隔⼀定時間間隔,向IM伺服器端傳送⼀個業務層的封包告知自⾝存活。相比TCP心跳,更靈活設定心跳間隔和更可⽤狀態的保活。
  • 智慧心跳:避免NAT超時,只能將心跳間隔設定為小於所有網路環境下NAT超時的最短時間。但是定期心跳,會有CPU和網路流量的浪費,因此提出智慧心跳,根據網路環境自動調整心跳間隔的時間,逐漸逼近NAT超時臨界點,優化心跳間隔。

3、總結

⼼跳作⽤:

  • 降低伺服器端連線維護⽆效連線的開銷
  • ⽀持客⼾端快速識別⽆效連線,快速重連
  • 連線保活,防⽌被運營商NAT超時端開。

問題:可否結合TCP的keep-Alive和應⽤層心跳使⽤?
tcp的keep-alive解決網路可⽤性,應⽤層keep-alive解決⽹絡和服務可⽤性,但是應⽤層無法區分網路還是服務問題,因此加上tcp心跳包可以進⼀步區分,tcp心跳探測到連結正常,而此時應用層通訊出現問題,則可能是服務出現問題。

二、心跳實現範例

1、註冊中心

微服務專案中為了讓客⼾端知道具體服務部署的地址,通常要使用服務註冊中心。

(1)、註冊中心解決問題

  • 提供服務地址的儲存
  • 儲存內容發⽣變化,變更內容推播到客⼾端

通過註冊中心,即使需要擴容,或者摘除節點,也不⽤重啟客⼾端伺服器。

  • 使用者端會與註冊中心建⽴連線,並且告訴註冊中心,它對哪⼀組服務感興趣;
  • 伺服器端向註冊中心註冊服務後,註冊中心會將最新的服務註冊資訊通知給客⼾端;
  • 客⼾端拿到伺服器端的地址之後就可以向伺服器端發起調⽤請求了。

(2)、服務狀態如何管理

主動探測:服務要開啟⼀個埠,然後由註冊中心每隔⼀段時間(比如30秒)探測這些埠是否可⽤,如果不可⽤就從服務列表摘除。
問題:

  • 服務節點都需要開放⼀個統⼀端⼝給註冊中心探測,端⼝可能被互相佔⽤。
  • 伺服器部署很多,探測對於註冊中⼼的代價也很高,服務不可⽤的時候還會有延遲。

心跳機制:服務節點註冊到服務中心後,會按照⼀定的時間間隔向註冊中心傳送心跳包,註冊中心收到後會更新最新續約時間,啟動⼀個定時器,如果⼀定時間還沒有收到心跳,就認為服務不可⽤。服務也需要檢測是否存活,那麼也可以考慮使⽤心跳機制來檢測

2、聊天室心跳應用和實現

在即時通訊系統中,比如聊天室,一般都是採用傳送方與伺服器建立一條TCP連線,接收方也會與伺服器建立一條TCP連線。
首先使用者有⼀個登陸的過程:

  • tcp使用者端與伺服器端通過三次握⼿建立tcp連線。
  • 基於該連線使用者端傳送登陸請求。
  • 伺服器端對登陸請求進⾏解析和判斷,如果合法,就將當前使用者uid和標識當前tcp連線的socket描述符(也就是fd)建⽴對映關係。
  • 這個對映關係⼀般是儲存在本地快取中。
  • 然後當伺服器端收到要傳送給這個使用者的訊息時,先從快取中根據uid查詢fd,如果找到,就基於fd將訊息推播出去。

具體的即使通訊系統應用場景可以瞭解:實時線上線上人數

(1)、心跳實現邏輯

因為要基於該TCP連線傳送訊息,所以要先保證該連線的可靠性,因此要使用如上中所說要通過心跳及時的探測到連結的是否仍有效。
邏輯如下:

  • 使用者與伺服器建立TCP連線
  • 使用者每隔幾秒向伺服器傳送心跳包
  • 伺服器端在收到心跳包之後,更新使用者的最後線上時間
  • 伺服器端同時有一個定時指令碼,定時指令碼每隔一段時間執行一次掃描所有的使用者的最後線上時間與現在時間對比,如果超過設定的超時時間,則重連或者清空相應伺服器端快取資源給出對應操作。

(2)、hash結構具體實現

實現上:可以用redis的hash結構,配合記錄所有的使用者的最後線上時間。

hash結構中字串是一個key對應一個value,value中通常只有一個對應key的資料,而hash中,把很多個資料(field:value)存到一個value中,Hash型別可以看成具有String Key和String Value的map容器新增和刪除操作都是O(1)(平均)的複雜度,每個hash可以儲存232-1 鍵值對(40多億),操作複雜度和儲存上限一般不會有問題。
hash的key為chat-room,表示這是聊天室key。
當用戶傳送心跳,設定hash欄位,field為user_id,value為當前系統時間戳

hset chat-room user_id time.Now().Unix()

伺服器端定時指令碼檢測邏輯如下

res = hgetall chat-room # 將hash所有欄位fields和value儲存在res字典中
for field, value := range res: # 遍歷所有欄位,也就是檢視所有使用者的上一次心跳時間
    if time.Now().Unix() - value > 60 * 3:  # 如果上一次心跳距離現在超過3分鐘,則認為連線斷開,連線資源。
        clearConnect()