億級流量架構演進實戰 - 架構演進構建 TCP 長連接網關 04
這不是一個講概念的專欄,而且我也不擅長講概念,每一篇文章都是一個故事,我希望你可以通過這些故事瞭解我當時在實際工作中遇到問題和背後的思考,架構設計是種經驗,我有幸參與到多個億級系統的架構設計中,有所收穫的同時也希望把這些收穫分享與大家。
承接上篇,網關是負責接口調用獲取請求數據的,HTTP 屬於單向操作,建客戶端與服務平臺的 TCP 雙向通道,保持客戶端與服務平臺的會話狀態,則可以提供更多、更靈活的技術實現和業務實現。在業務服務調用上通過 HTTP 網關,在平臺服務調用上則通過 TCP 網關,實現平臺與業務解耦,並且平臺採用 TCP 通道還可以增加對平臺的控制力,如下圖所示:
本文繼續講述構建 TCP 長連接網關的故事。
1。Session 管理
網關爲什麼需要 Session 管理?因爲 HTTP 網關是單向通信的短連接,是無狀態的;而 TCP 網關是雙向通信的長連接,是有狀態的。所謂有狀態,簡單的理解,用戶在 Web 網站登錄會後瀏覽器會基於 Session 緩存用戶的認證信息,以實現用戶有狀態下的服務訪問。HTTP 網關 API 的安全訪問目前是基於 OAuth2 技術,而 TCP 網關則可以通過長連接實現訪問鑑權後基於 Session 管理實現 API 有狀態下的服務訪問。而且 TCP 屬於 OSI 的傳輸層,所以建立 Session 管理機制構建會話層來提供應用層服務,可以極大的降低系統複雜度。
TCP 網關的 Session 是客戶端與服務端建立的一次會話鏈接,會話信息中保存着 SessionId、連接創建時間、上次訪問事件,以及 Connection 和 SessionListener 對象。因爲 TCP 網關採用 Netty 框架,所以 Session 中的 Connection 保存了 Netty 的 ChannelHandlerContext 上下文信息。所有連接的 Session 會話信息會保存在 SessionManager 內存管理器中。
TCP 網關通過在 Netty 的 ChannelPipe 中加載自定義 ChannelHandler,構建 Container 容器,將每個 TCP Connection 封裝到一個 Session 會話中,保存在 Container 容器中,由 Container 容器構建 Session Layer 提供 Logic 請求調用,如下圖所示:
每一次 Session 的會話請求(ChannelRead)都是通過 Proxy 代理機制調用 Service 層,數據請求完畢後通過寫入 ChannelHandlerConext 再傳送到 Channel 中。同樣,數據下行主動推送也是如此,通過 Session Manager 找到 Active 的 Session,輪詢寫入 Session 中的 ChannelHandlerContext,就可以實現廣播或點對點的數據推送邏輯。
數據上行
數據上行特指從客戶端發送數據到服務端,數據從 ChannelHander 的 channelRead 方法獲取數據。數據包括創建會話、發送心跳、數據請求等。這裏注意的是,channelRead 的數據包括客戶端主動請求服務端的數據,以及服務端下行通知客戶端的返回數據,所以在處理 object 數據時,通過數據標識區分是請求 - 應答,還是通知 - 回覆。
數據下行
數據下行通過 MQ 廣播機制到所有服務器,所有服務器收到消息後,獲取當前服務器所持有的所有 Session 會話,進行數據廣播下行通知。如果是點對點的數據推送下行,數據也是先廣播到所有服務器,每天服務器判斷推送的端是否是當前服務器持有的會話,如果判斷消息數據中的信息是在當前服務,則進行推送,否則拋棄。
2。心跳
心跳是用來檢測保持連接的客戶端是否還存活着,客戶端每間隔一段時間就會發送一次心跳包上傳到服務端,服務端收到心跳之後更新 Session 的最後訪問時間。在服務端長連接會話檢測通過輪詢 Session 集合判斷最後訪問時間是否過期,如果過期則關閉 Session 和 Connection,包括將其從內存中刪除,同時註銷 Channel 等。
上文已提到,TCP 網關長連接容器的 Handler 就是放在 Pipeline 的中,所以,每一個 Channel 對應一個 Connection,一個 Connection 就對應一個 Session,Session 由 Session Manager 管理,Session 與 Connection 是一一對應,Connection 保存着 ChannelHandlerContext(ChannelHanderContext 可以找到 Channel),Session 通過心跳機制來保持 Channel 的 Active 狀態。
當然,除了這種方法之外,還可以通過在服務端基於 Session Manager 發送心跳包檢測客戶端是否還活着,如果嘗試無響應,則關閉 Session 和 Connection。
除此之外,Netty 框架也提供了 IdleStateHandler 方法,IdleStateHandler 會檢測 channelRead 和 write 方法多久沒有被調用了,如果長時間沒有調用,就會調用 userEventTriggered 方法。
3。斷線重連
客戶端與服務端通過 TCP 長連接進行通信,但在中國複雜的網絡環境下,移動客戶端可能由於網絡抖動、弱網絡情況下,遭遇非正常網絡閃斷,如何處理斷開後的斷線重連?TCP 長連接斷開之後,如果重連?如果處理斷開後的連接再重連後,找到上一次連接的會話呢?
這個解決得益於 Netty 的 ChannelHandlerContext,它可以存儲一些自定義屬性到 Channel 的上下文中。在每次成功建立請求的 Channel 裏存入 SessionId,將它作爲一個鉤子,當網絡閃斷後,再次請求建立連接的 Channel,可以通過判斷是否存在 SessionId 的鉤子,進行處理。
客戶端每通過 TCP 與服務端進行一次建連,都會在服務容器裏創建一個 Session 會話,該會話保存 Connection 的句柄,對應 Netty 的一個 Channel 通道。建連成功後,通過定時的心跳保持 Channel 屬於 Active 活躍。但客戶端進入弱網絡環境下,客戶端可能已經掉線,但並未向服務端主動發送關閉 Channel 請求,而服務端仍認爲該 Channel 仍存活。直到在由服務端的會話存活檢測機制檢測到 Channel 已經 InActive,纔會由服務端銷燬該 Channel。
服務端的會話存活檢測是 5 分鐘一次,所以存在客戶端掉線後,在 5 分鐘內又重新建連,而這時服務端的建連邏輯,不是重新創建一個 Session,而是去尋找上一次的 Session,並更新標識存活。具體的實現是在每次建連的 Channel 裏存入 SessionId,當網絡閃斷後,判斷 Channel 是否存在 Session,之所以實現是得益於 Netty 的 ChannelHandlerContext,可以存儲一個自定義屬性到 Channel 的上下文中。
當然,TCP 網關一定是集羣,所以,斷線重連也是極有可能請求到不同的服務器上,而這種情況按照新 Connection 創建的 Session 處理,只有出現重連到同一服務器時,才需要考慮上述的處理邏輯。
4。總結
言而總之,本篇文章重點講述了 TCP 網關的心跳、Session 管理、斷線重連。下篇文章,我將介紹架構演進重構消息 PUSH 系統。如果你覺得有收穫,歡迎你把今天的內容分享給更多的朋友。
本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源:https://mp.weixin.qq.com/s/j5bepIbD735zepGdo5ZLTQ