YouTube買粉丝、facebook刷点赞、tiktok买粉丝点赞–instagram买粉丝
YouTube買粉丝、facebook刷点赞、tiktok买粉丝点赞–instagram买粉丝

01 kafka訂閱模式會阻塞嗎(如何實現支持數億用戶的長連消息系統)

来源: 发表时间:2024-05-16 06:38:18

如何實現支持數億用戶的長連消息系統

此文是根據周洋在【高可用架構群】中的分享內容整理而成,轉發請注明出處。

周洋,360手機助手技術經理及架構師,負責360長連接消息系統,360手機助手架構的開發與維護。

不知道咱們群名什么時候改為“Python高可用架構群”了,所以不得不說,很榮幸能在接下來的一個小時里在Python群里討論golang....

360消息系統介紹

360消息系統更確切的說是長連接push系統,目前服務于360內部多個產品,開發平臺數千款app,也支持部分聊天業務場景,單通道多app復用,支持上行數據,提供接入方不同粒度的上行數據和用戶狀態回調服務。

目前整個系統按不同業務分成9個功能完整的集群,部署在多個idc上(每個集群覆蓋不同的idc),實時在線數億量級。通常情況下,pc,手機,甚至是智能硬件上的360產品的push消息,基本上是從我們系統發出的。

關于push系統對比與性能指標的討論

很多同行比較關心go語言在實現push系統上的性能問題,單機性能究竟如何,能否和其他語言實現的類似系統做對比么?甚至問如果是創業,第三方云推送平臺,推薦哪個?

其實各大廠都有類似的push系統,市場上也有類似功能的云服務。包括我們公司早期也有erlang,nodejs實現的類似系統,也一度被公司要求做類似的對比測試。我感覺在討論對比數據的時候,很難保證大家環境和需求的統一,我只能說下我這里的體會,數據是有的,但這個數據前面估計會有很多定語~

第一個重要指標:單機的連接數指標

做過長連接的同行,應該有體會,如果在穩定連接情況下,連接數這個指標,在沒有網絡吞吐情況下對比,其實意義往往不大,維持連接消耗cpu資源很小,每條連接tcp協議棧會占約4k的內存開銷,系統參數調整后,我們單機測試數據,最高也是可以達到單實例300w長連接。但做更高的測試,我個人感覺意義不大。

因為實際網絡環境下,單實例300w長連接,從理論上算壓力就很大:實際弱網絡環境下,移動客戶端的斷線率很高,假設每秒有1000分之一的用戶斷線重連。300w長連接,每秒新建連接達到3w,這同時連入的3w用戶,要進行注冊,加載離線存儲等對內rpc調用,另外300w長連接的用戶心跳需要維持,假設心跳300s一次,心跳包每秒需要1w tps。單播和多播數據的轉發,廣播數據的轉發,本身也要響應內部的rpc調用,300w長連接情況下,gc帶來的壓力,內部接口的響應延遲能否穩定保障。這些集中在一個實例中,可用性是一個挑戰。所以線上單實例不會hold很高的長連接,實際情況也要根據接入客戶端網絡狀況來決定。

第二個重要指標:消息系統的內存使用量指標

這一點上,使用go語言情況下,由于協程的原因,會有一部分額外開銷。但是要做兩個推送系統的對比,也有些需要確定問題。比如系統從設計上是否需要全雙工(即讀寫是否需要同時進行)如果半雙工,理論上對一個用戶的連接只需要使用一個協程即可(這種情況下,對用戶的斷線檢測可能會有延時),如果是全雙工,那讀/寫各一個協程。兩種場景內存開銷是有區別的。

另外測試數據的大小往往決定我們對連接上設置的讀寫buffer是多大,是全局復用的,還是每個連接上獨享的,還是動態申請的。另外是否全雙工也決定buffer怎么開。不同的策略,可能在不同情況的測試中表現不一樣。

第三個重要指標:每秒消息下發量

這一點上,也要看我們對消息到達的QoS級別(回復ack策略區別),另外看架構策略,每種策略有其更適用的場景,是純粹推?還是推拉結合?甚至是否開啟了消息日志?日志庫的實現機制、以及緩沖開多大?flush策略……這些都影響整個系統的吞吐量。

另外為了HA,增加了內部通信成本,為了避免一些小概率事件,提供閃斷補償策略,這些都要考慮進去。如果所有的都去掉,那就是比較基礎庫的性能了。

所以我只能給出大概數據,24核,64G的服務器上,在QoS為message at least,純粹推,消息體256B~1kB情況下,單個實例100w實際用戶(200w+)協程,峰值可以達到2~5w的QPS...內存可以穩定在25G左右,gc時間在200~800ms左右(還有優化空間)。

我們正常線上單實例用戶控制在80w以內,單機最多兩個實例。事實上,整個系統在推送的需求上,對高峰的輸出不是提速,往往是進行限速,以防push系統瞬時的高吞吐量,轉化成對接入方業務服務器的ddos攻擊所以對于性能上,我感覺大家可以放心使用,至少在我們這個量級上,經受過考驗,go1.5到來后,確實有之前投資又增值了的感覺。

消息系統架構介紹

下面是對消息系統的大概介紹,之前一些同學可能在gopher 買粉絲上可以看到分享,這里簡單講解下架構和各個組件功能,額外補充一些當時遺漏的信息:

架構圖如下,所有的service都 written by golang.

幾個大概重要組件介紹如下:

dispatcher service根據客戶端請求信息,將應網絡和區域的長連接服務器的,一組IP傳送給客戶端。客戶端根據返回的IP,建立長連接,連接Room service.

room Service,長連接網關,hold用戶連接,并將用戶注冊進register service,本身也做一些接入安全策略、白名單、IP限制等。

register service是我們全局session存儲組件,存儲和索引用戶的相關信息,以供獲取和查詢。

買粉絲ordinator service用來轉發用戶的上行數據,包括接入方訂閱的用戶狀態信息的回調,另外做需要協調各個組件的異步操作,比如kick用戶操作,需要從register拿出其他用戶做異步操作.

saver service是存儲訪問層,承擔了對redis和mysql的操作,另外也提供部分業務邏輯相關的內存緩存,比如廣播信息的加載可以在saver中進行緩存。另外一些策略,比如客戶端sdk由于被惡意或者意外修改,每次加載了消息,不回復ack,那服務端就不會刪除消息,消息就會被反復加載,形成死循環,可以通過在saver中做策略和判斷。(客戶端總是不可信的)。

center service提供給接入方的內部api服務器,比如單播或者廣播接口,狀態查詢接口等一系列api,包括運維和管理的api。

舉兩個常見例子,了解工作機制:比如發一條單播給一個用戶,center先請求Register獲取這個用戶之前注冊的連接通道標識、room實例地址,通過room service下發給長連接 Center Service比較重的工作如全網廣播,需要把所有的任務分解成一系列的子任務,分發給所有center,然后在所有的子任務里,分別獲取在線和離線的所有用戶,再批量推到Room Service。通常整個集群在那一瞬間壓力很大。

deployd/agent service用于部署管理各個進程,收集各組件的狀態和信息,zookeeper和keeper用于整個系統的配置文件管理和簡單調度

關于推送的服務端架構

常見的推送模型有長輪訓拉取,服務端直接推送(360消息系統目前主要是這種),推拉結合(推送只發通知,推送后根據通知去拉取消息).

拉取的方式不說了,現在并不常用了,早期很多是nginx+lua+redis,長輪訓,主要問題是開銷比較大,時效性也不好,能做的優化策略不多。

直接推送的系統,目前就是360消息系統這種,消息類型是消耗型的,并且對于同一個用戶并不允許重復消耗,如果需要多終端重復消耗,需要抽象成不同用戶。

推的好處是實時性好,開銷小,直接將消息下發給客戶端,不需要客戶端走從接入層到存儲層主動拉取.

但純推送模型,有個很大問題,由于系統是異步的,他的時序性無法精確保證。這對于push需求來說是夠用的,但如果復用推送系統做im類型通信,可能并不合適。

對于嚴格要求時序性,消息可以重復消耗的系統,目前也都是走推拉結合的模型,就是只使用我們的推送系統發通知,并附帶id等給客戶端做拉取的判斷策略,客戶端根據推送的key,主動從業務服務器拉取消息。并且當主從同步延遲的時候,跟進推送的key做延遲拉取策略。同時也可以通過消息本身的QoS,做純粹的推送策略,比如一些“正在打字的”低優先級消息,不需要主動拉取了,通過推送直接消耗掉。

哪些因素決定推送系統的效果?

首先是sdk的完善程度,sdk策略和細節完善度,往往決定了弱網絡環境下最終推送質量.

SDK選路策略,最基本的一些策略如下:有些開源服務可能會針對用戶hash一個該接入區域的固定ip,實際上在國內環境下不可行,最好分配器(dispatcher)是返回散列的一組,而且端口也要參開,必要時候,客戶端告知是retry多組都連不上,返回不同idc的服務器。因為我們會經常檢測到一些case,同一地區的不同用戶,可能對同一idc內的不同ip連通性都不一樣,也出現過同一ip不同端口連通性不同,所以用戶的選路策略一定要靈活,策略要足夠完善.另外在選路過程中,客戶端要對不同網絡情況下的長連接ip做緩存,當網絡環境切換時候(wifi、2G、3G),重新請求分配器,緩存不同網絡環境的長連接ip。

客戶端對于數據心跳和讀寫超時設置,完善斷線檢測重連機制

針對不同網絡環境,或者客戶端本身消息的活躍程度,心跳要自適應的進行調整并與服務端協商,來保證鏈路的連通性。并且在弱網絡環境下,除了網絡切換(wifi切3G)或者讀寫出錯情況,什么時候重新建立鏈路也是一個問題。客戶端發出的ping包,不同網絡下,多久沒有得到響應,認為網絡出現問題,重新建立鏈路需要有個權衡。另外對于不同網絡環境下,讀取不同的消息長度,也要有不同的容忍時間,不能一刀切。好的心跳和讀寫超時設置,可以讓客戶端最快的檢測到網絡問題,重新建立鏈路,同時在網絡抖動情況下也能完成大數據傳輸。

結合服務端做策略

另外系統可能結合服務端做一些特殊的策略,比如我們在選路時候,我們會將同一個用戶盡量映射到同一個room service實例上。斷線時,客戶端盡量對上次連接成功的地址進行重試。主要是方便服務端做閃斷情況下策略,會暫存用戶閃斷時實例上的信息,重新連入的 時候,做單實例內的遷移,減少延時與加載開銷.

客戶端保活策略

很多創業公司愿意重新搭建一套push系統,確實不難實現,其實在協議完備情況下(最簡單就是客戶端不回ack不清數據),服務端會保證消息是不丟的。但問題是為什么在消息有效期內,到達率上不去?往往因為自己app的push service存活能力不高。選用云平臺或者大廠的,往往sdk會做一些保活策略,比如和其他app共生,互相喚醒,這也是云平臺的push service更有保障原因。我相信很多云平臺旗下的sdk,多個使用同樣sdk的app,為了實現服務存活,是可以互相喚醒和保證活躍的。另外現在push sdk本身是單連接,多app復用的,這為sdk實現,增加了新的挑戰。

綜上,對我來說,選擇推送平臺,優先會考慮客戶端sdk的完善程度。對于服務端,選擇條件稍微簡單,要求部署接入點(IDC)越要多,配合精細的選路策略,效果越有保證,至于想知道哪些云服務有多

相关栏目: