千萬級高性能長連接 Go 服務架構實踐

作者 | glstr

導讀 

introduction

移動互聯網時代,長連接服務成爲了提升應用實時性和互動性的基礎服務。本文主要介紹了百度系內基於 golang 實現的統一長連接服務。主要從統一長連接功能實現和性能優化等角度,描述了統一長連接服務在設計、開發和維護過程中面臨的問題和挑戰,重點介紹瞭解決相關問題和挑戰的解決方案和實踐經驗。

01

摘要

移動互聯網時代,用戶對服務的實時性、互動性有了更高的要求,因此能夠極大提升服務實時性、互動性的長連接服務,成爲了移動互聯網應用的剛需。長連接,顧名思義,是應用存活期間和服務端一直保持的網絡數據通道,能夠支持全雙工上下行數據傳輸。其和請求響應模式的短連接服務最大的差異,在於它可以提供服務端主動給用戶實時推送數據的能力。

不過,長連接作爲基礎服務,要做到低延時、高併發、高穩定性,對服務的開發和維護有較高的要求,如果每個業務都維護自身的長連接服務,一方面有較大的重複開發和維護成本,另一方面長連接服務功能迭代、服務穩定性、專業性很難跟上業務訴求。

因此,統一長連接項目通過打造完整的端到服務端的長連接服務系統,給業務提供一套安全、高併發、低延遲、易接入、低成本的長連接服務能力。

02

統一長連接服務

統一長連接服務主要目的是給業務提供一套安全、高併發、低延遲、易接入、低成本的長連接服務系統。主要願景包括:

  1. 滿足百度體系內 APP 主要場景如直播、消息、PUSH、雲控等業務對長連接能力的訴求,提供安全構建、維護長連接和數據上下行能力;

  2. 保障服務的高併發、高穩定性、低延遲,保障長連接服務的專業性和先進性;

  3. 支持長連接多業務長連接複用,減少 APP 建立和維護長連接的成本和壓力;

  4. 支持業務快速接入長連接,提供給業務簡單清晰的接入流程和對外接口。

03

問題和挑戰

爲了構建能夠滿足業務訴求的長連接服務,統一長連接服務在設計、開發和維護過程中,我們面臨着一些問題和挑戰需要去解決,其主要包括以下兩個方面。

3.1 功能實現

統一長連接服務與接入業務的邊界關係是長連接業務架構設計的首要問題。與業務專用的長連接服務不同,統一長連接服務要實現的目標是多業務方共用一條長連接。因此在設計時既要考慮到不同業務方、不同業務場景對長連接服務的訴求,同時,也要明確長連接服務的服務邊界,避免過多介入業務邏輯,限制後續長連接服務的迭代和發展。

通常,業務對長連接服務的主要訴求包括三個方面:

  1. 連接建立、維護、管理;

  2. 上行請求轉發;

  3. 下行數據推送。

數據上下行過程中,需要能夠支持不同業務數據協議上的差異;此外,根據不同的業務類型,對下行數據推送模式、推送量級有着不同的要求。以長連接服務常見的業務方:消息、直播、PUSH 爲例。

1. 消息場景:主要是私信和有人數限制 (500-1000) 的羣聊,推送通知的模式主要是單播和批量單播,推送頻率和併發度,依賴於私信和羣裏消息的發送頻率。

2. 直播消息場景:直播消息是一個組播場景,組播成員數和直播間在線人數相關,峯值百萬甚至千萬,推送消息頻率高。

3.PUSH 場景:PUSH 場景是對一個固定人羣下發消息,推送模式是批量單播,推送頻率相對而言比較低。

綜上,統一長連接服務,要實現的服務能力如下:

  1. 連接建立、維護、管理;

  2. 上、下行數據轉發,區分不同業務、兼容不同業務消息協議;

  3. 下行推送上,支持單播、批量單播、廣播。

3.2 性能優化

統一長連接服務因爲要給百度體系 APP 提供長連接能力的服務,要做到高併發、高可用、高穩定性。具體到長連接服務本身,其主要包括以下幾個方面。

3.2.1 建聯 qps、延時、成功率、連接維持

長連接在 app 打開同時需要完成建立、並在 app 存活期間保持連接存活。因此,長連接服務要支持萬級別的建聯 qps 和千萬級別在在線連接維持,並支持橫向擴容。此外,連接建立作爲長連接服務的基礎,建聯的成功率和延時重中之重。

3.2.2 上行請求 qps、延時、成功率

連接建立完成後,需要將業務請求轉發給業務側,這個依賴於用戶規模和請求頻率,一般至少要支持到幾十乃至百萬級別,並可以支持橫向擴容。

3.2.3 下行請求 qps,延時、成功率

下行請求根據業務場景的不同,分爲批量單播和組播,且不同業務對應請求 qps 要求不一樣,一般批量單播需要支持到百萬級 ups,組播要支持到千萬級 ups,且支持橫向擴容。

04

整體介紹

下面簡單介紹下,爲了完成上述目標,統一長連接服務所做的一些方案設計和實踐經驗。

4.1 整體介紹

4.1.1 整體架構

統一長連接服務整體架構如圖所示,整個服務包括統一長連接 SDK、控制層、接入層、路由層四個部分組成。統一長連接 SDK 歸屬於客戶端,控制層、接入層、路由層歸屬於服務端。每個組成部分在整體系統中的扮演的角色和功能如下。

1. 統一長連接 SDK:統一長連接 SDK 歸屬於客戶端,負責連通業務 SDK 和長連接服務端,其主要職責包括:

2. 控制層:長連接建聯之前的一個前置服務,主要用來驗證接入設備的合法性和決定設備的接入策略,其主要職責包括:

3. 接入層:接入層作爲統一長連接核心服務,承擔了連接介入,連接維護、請求轉發、下行推送等主要功能,是長連接核心邏輯和主要壓力的承擔者,其主要職責包括:

4. 路由層:負責構建設備標識和連接標識的映射關係,在業務指定設備標識進行推送的時候,提供設備標識查詢連接標識的能力。

4.1.2 核心流程

長連接生命週期內主要有四個核心流程:建立連接、維持連接、上行請求、下行推送。

1. 建立連接:由長連接 SDK 發起,先通過控制層獲取該設備的合法標識 token 和接入配置 (接入點、接入協議),然後和接入層開始建聯長連接,成功則長連接建立完成;

2. 維持連接:主要是通過長連接 SDK 定時發起心跳來保證長連接活躍;

3. 上行請求:上行請求由業務 SDK 發起,長連接 SDK 封裝後發送給接入層,接入層根據請求來源發送給指定的業務 Server;

4. 下行推送:下行推送由業務 Server 發起,經由路由層根據設備標識確定連接標識,然後將請求轉發到對應的接入層,寫入到設備指定連接上,經由長連接 SDK 轉發給業務 SDK。

4.2 功能實現

4.2.1 連接狀態

長連接由於連接生命週期較長,在週期內連接可能會因爲各種網絡情況、數據傳輸異常導致連接發生狀態變化,同時也爲了防止惡意設備模擬正常的客戶端對長連接服務進行攻擊,需要有一套機制能夠讓服務端驗證長連接狀態是合法有效的,同時對於處於異常狀態的連接,能夠觸發其重連並快速恢復。

統一長連接通過引入狀態機的方式構建了該機制。即明確定義長連接在生命週期內可能存在的各種狀態、每種狀態可以觸發的操作,以及狀態間相關轉移的場景等。比如連接在可以發送數據前,需要通過登錄驗證連接合法有效,認證通過後的連接才能視爲有效連接並支持上下行數據傳輸;登錄後的連接如果發生異常情況,比如數據格式異常、網絡狀態異常,會觸發連接失效觸發端上重新建聯登錄等等。引入這種的機制好處是:簡化長連接狀態流轉的開發邏輯,長連接生命週期裏面每種狀態以及狀態間轉移關係,觸發的操作都是明確定義的,避免了線上因爲各種未知原因導致連接處於不可知狀態,導致長連接異常甚至無法恢復。

4.2.2 多業務支持

統一長連接一個主要願景是支持多業務複用一條長連接,即同一條連接上,能夠兼容不同業務的數據協議,且在上下行業務數據傳輸時候能夠區分不同業務的請求轉發給指定業務。

這個主要是通過長連接私有數據協議來支持,長連接數據協議是長連接 SDK 和長連接接入層交互使用的數據協議,採用的二進制私有協議,協議主要分爲三部分:

1. 協議頭:包括協議標識、協議版本等;

2. 公參:設備標識、應用標識、業務標識、請求元數據等;

3. 業務數據:業務自定義數據,用來兼容不同業務的數據協議。

通過解析協議公參裏面的業務標識,長連接 SDK 和長連接接入層能夠確認業務數據對應的業務方,並根據業務標識將請求做對應轉發。業務的請求數據放在業務數據部分,協議有業務側指定,長連接服務只做轉發,不介入業務具體細節。

4.2.3 上行請求轉發

接入層根據業務標識確認業務數據來源後,會通過 RPC 請求將業務數據轉發給業務 server,然後將業務 Server 的返回寫回給端上。上行請求轉發除了會轉發業務上行請求數據外,也會講長連接公參數據一併帶給業務 Server。除此外, 如果業務有訴求,在連接狀態發生變化,比如連接斷開等,長連接接入層也可以將該信號實時通知業務 Server,以便業務 Server 根據狀態信號變化做進一步操作。

4.2.4 單播 & 多播推送

下行推送,根據業務需要,主要分爲兩類,單播推送和組播推送,以下對比了單播推送和組播的差異。

單播推送:服務端主動推送時候,比如一個業務要給某個設備推消息,由於接入層是多實例部署的,首先需要知道這個設備與哪個長連接實例相連,其次需要知道這個設備與這個實例內哪條長連接相關聯,那麼這個實例地址和對應的長連接一期就構成了這個設備當時的連接信息,給某個用戶推消息,本質上就是通過用戶設備找到這個設備對應的連接信息的過程,也就是設備 ID -> 連接信息(實例 ip + 連接 ID)映射關係的過程,路由層負責構建和維護設備和連接信息的一個模塊。

這個對業務的主要成本是確定需要推送用戶的設備 ID:

(1)對於一些業務本身的業務場景是設備維度的,那就可以直接通過接口進行推送;

(2)對於一些業務本身的業務場景是用戶維度的,一個用戶可以有多個設備,那麼業務側需要做一個用戶 -> 設備的映射關係,給用戶推消息,需要做用戶 -> 設備,然後設備 -> 連接信息的兩層轉換。

組播推送:此外,由於下行推送的時候,在某些場景下,會存在需要給大批量用戶推送相同消息的場景,比如直播 (聊天室)。路由層會維護一個連接組信息,連接組 ID-> 組內連接信息的映射。連接組的創建、連接加入和退出連接組,主要由對應的業務場景來控制,路由層只提供對應的接口能力,在連接組建立好後,向對應的連接組推消息,長連接服務會自動將消息拆分發給組內每一條連接。

使用連接組,業務需要做的事情:

(1)連接組創建;

(2)客戶端主動加入和退出連接組;

(3)根據連接 ID,推送消息。

4.3 性能優化

4.3.1 多協議支持

長連接底層強依賴 tcp&tls、quic、websocket 等通訊協議,一方面,不同的場景,可能會使用到不同的通訊協議,比如 NA 端一般傾向於 tcp 和 tls,小程序和 web 端傾向於 websocket,一方面,現有協議的迭代和新協議的出現,也會給長連接的性能和通道質量帶來優化。因此,爲了適配不同場景下的通訊協議,同時也爲了能夠快速探索通訊協議迭代對長連接服務質量的提升。統一長連接做了對不同通訊協議的兼容。

即爲了支持多種通訊協議,接入層對連接概念做了劃分,將連接分爲了兩層,connection 和 session。 

connection 層: 和具體的通訊層協議交互,封裝不同通訊協議的接口和邏輯差異,比如 tls、websocket、quic 等等。同時給上層 session 層提供統一的數據接口,包括連接建立、數據讀取、寫入、連接信息獲取等。新協議的接入,只需要在 connection 做相應適配,不影響 session 層的長連接業務邏輯。

session 層:長連接業務級別連接概念,維護長連接業務連接狀態,維護連接狀態機,支持請求轉發、下行推送等業務邏輯,本身依賴 connection 層提供的數據接口和實際的通訊協議進行交互。但是不感知具體的通訊協議差異。

同時,端上在建聯時,控制層會根據客戶端當前的情況,比如端類型 (NA、小程序、Web)、當前的網絡類型 (4G、5G)、設備質量情況等,下發不同的建聯協議和對應的接入點,端上根據下發的建議協議和接入點,結合端上自身情況,選用合適的接入協議和接入點進行建聯。

這樣設計的主要優勢在於:

  1. 長連接業務邏輯和通訊協議做了隔離,通訊協議的更新和新增,不影響長連接狀態機、請求轉發、下行推送等業務邏輯,簡化了兼容多通訊協議的實現難度,實現一套架構支持多通訊協議接入。

  2. 客戶端可以根據實際情況,採用不同的通訊協議接入,通訊協議帶來的通道質量優勢能夠很好的體現在長連接服務質量上。

4.3.2 請求轉發組 & 下行任務組

連接建立完成後,接入層主要面臨着三個壓力來源:連接維持、請求轉發、下行推送。按照單個實例需要支持百萬連接來考慮,假定單個連接心跳爲 1 分鐘 1 次,則連接維持需要支持 1.6w qps 心跳請求;請求轉發依賴於業務請求頻率,通常百萬在線至少需要支持 1-2w qps 上行請求;下行推送,根據推送場景的不同,通常組播場景下需要支持 5-10w ups 下行。綜上,單個實例如果要支持百萬連接,通常需要支持 3-4w qps 上行,5-10w 的 ups 下行。

統一長連接層接入層服務是使用 golang 來實現的,按照 golang 常用的網絡模型,一條連接會有對應兩個 goroutine,一個用來讀數據和處理數據,一個用來寫數據。這個模型存在兩個問題:

  1. 統一長連接是多業務複用一個連接,連接上會存在同時有多個請求上行,一個 goroutine 讀和處理數據,如果一個請求處理比較慢,會導致後續其他請求處理排隊的情況;

  2. 每個連接至少需要 2 個 goroutine, 如果單實例需要支持一百萬連接,則單個實例常態下會有 200 萬 goroutine, 而且,長連接服務,連接的建立和釋放非常頻繁,這個會導致 goroutine 的建聯和釋放也非常頻繁,進而給服務的 gc 帶來巨大的壓力。

統一長連接通過引入請求轉發組和下行任務組來解決上述兩個問題。

1、請求轉發組

接入層在實例啓動的時候,會根據支持的業務上行 qps,初始化對應的請求轉發組,連接在讀取完請求數據後,會根據請求屬於那個業務將請求轉給對應的請求轉發組,由請求轉發組內的 goroutine 完成後續請求;不同業務的請求轉發組是不同的 goroutine 池,避免業務間相互影響。連接本身的讀 goroutine 只是負責讀取數據,轉發請求給請求轉發組,避免請求處理存在排隊的情況。

2、下行任務組

下行數據寫入的時候,會有一個公共的任務組,每個連接在任務組中會有一個固定的處理協程,有數據下行的時候會講寫入數據的任務發到下行任務組。這個主要目的是,由於單個實例需要維護大量的在線長連接,每個長連接通常需要兩個協程,一個讀協程、一個寫協程,如果單實例支持 50w 連接,也就會單實例存在百萬協程,這個對服務 gc 和資源都會造成一定的壓力。同時,一個實例維持的所有連接通常不會同時都下行寫消息,因而,可以通過維護一個下行任務組,任務組裏面維護動態數量的協程數,每個連接綁定任務組裏面一個協程,有下行任務時將任務發送到任務組裏面,有任務組裏面的協程負責將任務下行,減少實例服務壓力。

4.3.3 服務部署

統一長連接服務部署上,主要做了以下幾點:

  1. 長連接在國內三大運營商的華東、華北、華南地域均部署了接入點;部分業務需要支持海外業務,增加了香港機房的獨立接入點入口;

  2. 根據業務量級和重要性,分大小集羣部署,每個集羣對應不同的域名,不同業務通過控制層下發域名分流到對應集羣。主要目的針對於重點業務,提供獨立部署的能力;對於次級業務,提供混部服務,降低成本和提高資源利用率;

  3. 針對接入層每個實例,將實例的配置和能夠支持的連接數,控制在 10w-20w 量級,避免單個實例支持連接數過多,實例發生服務抖動時,對整個集羣服務產生較大影響;單個實例支持的連接數有限制,減少實例內常態維護的 goroutine 數,減輕 gc 壓力。

4.4 業務接入

業務接入統一長連接通常涉及以下幾個步驟:

  1. 評估需要接入的能力:評估需要接入的長連接能力,比如下行推送中,是要接批量單播還是組播;是否要支持上行請求等,根據接入能力不同,需要對接不同的接口;

  2. 評估用戶量級:用戶量級涉及到資源評估以及是否需要單獨進行集羣部署等等;

  3. 端上接入長連接 SDK:客戶端需要接入長連接 SDK,接入上下行收發消息接口;

  4. 服務端接入上下行接口:服務端需要根據接入能力,適配不同的服務端接口;

  5. 申請資源服務上線。

05

總結與規劃

5.1 總結

目前統一長連接已支持了千萬級長連接併發在線,支持過百萬級 ups 批量單播和組播消息下行,擁有實時橫向擴容能力。服務第一次完成上線至今,長連接服務穩定,經歷過多次重大活動高併發推送的考驗,沒有出現過任何影響其他業務服務質量的 case。總的來說,統一長連接項目從立項、開發到最後上線運維,服務質量整體上是符合預期的。從統一長連接項目整個項目流程,主要總結有以下三點經驗:

1. 需求:分析需求時候,要明確需求和業務的邊界,也就是明確什麼應該長連接的能力,什麼是業務邏輯,堅持長連接服務不深入介入業務邏輯這個原則,保證長連接服務與業務的邏輯解耦,確保長連接服務結構的穩定。

2. 設計:需求明確的情況下,技術的方案的選擇以簡單滿足要求爲優先,長連接服務本身邏輯並不複雜,其主要在於服務的穩定性和高性能,巧妙而複雜的方案在長連接的場景下,並不一定能夠適用;

3. 運維:追求單實例性能的同時,也要在性能和運維之間做取捨,單實例性能再強,能夠支持的連接數再多,通常也比不上分拆多個小實例,帶來的穩定性和資源利用率的提升大。

5.2 規劃

統一長連接服務經歷數次迭代後,目前基本功能已經趨於穩定,進一步對長連接服務進行改善和優化,主要集中在以下幾個方向:

1. 精細化:進一步完善長連接全鏈路網絡質量數據統計和分析能力的建設。

2. 智能化:端上建聯、接入點接入、心跳頻率等能夠根據實際環境進行自動調整;

**3. 場景拓展:**探索長連接支持更多的業務場景。

本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源https://mp.weixin.qq.com/s/E8rO4uBkFpcLAb_LsBOphw