
以下為演講實(shí)錄:
大家好!我叫吉奇,來(lái)自聲網(wǎng)。現在負責聲網(wǎng)RTM 實(shí)時(shí)信令云服務(wù)后臺及SDK技術(shù)架構設計。這次演講會(huì )按照RTM的系統架構上的分布或子系統的層級關(guān)系來(lái)展開(kāi)。
首先,RTM 是一個(gè)通用的消息系統,主要是為了解決實(shí)時(shí)場(chǎng)景下信令的低延遲和高并發(fā)問(wèn)題。我們聲網(wǎng)是業(yè)務(wù)遍布全球的平臺,因此在所有的后臺設計中,把分區作為一個(gè)比較重要的事情來(lái)看。目前 RTM 有幾個(gè)大區域,有美洲、亞洲、東南亞、中國大陸,還有歐洲、非洲幾個(gè)大區。區與區之間相對獨立,每個(gè)區會(huì )有跨區傳輸網(wǎng)絡(luò )。每個(gè)區之間由三個(gè)子系統組成,首先是消息核心(Message Core),還有事件中心(Event Center),最后是應用服務(wù)(Application Services)。我會(huì )分別講一下各個(gè)子系統內部的架構實(shí)現,即消息核心、事件中心、應用服務(wù)和跨區網(wǎng)絡(luò )。
消息核心(Message Core)
首先是消息核心,它是目前成熟度最高,也是最復雜的子系統。在該系統里面有幾個(gè)主要的組件,首先有接入服務(wù)器、點(diǎn)對點(diǎn)消息轉發(fā)服務(wù)、頻道消息的轉發(fā)服務(wù)、簡(jiǎn)單的狀態(tài)管理(包括用戶(hù)狀態(tài)和頻道狀態(tài)),還有頻道分布狀態(tài)服務(wù)器。
在消息核心,所有的服務(wù)都是分布式,沒(méi)有一個(gè)單點(diǎn)或者中心式的情況,因此可以保證高可用,并且性能方面可以支持高吞吐量和低延遲。Messaging Core 有一個(gè)特點(diǎn),具有非常大的擴展性,但是它的問(wèn)題是只支持基本核心的功能,剩下的都要放在其它子系統中。
分布式的信息核心有幾個(gè)優(yōu)勢特性:
- 完全排除單點(diǎn)故障
- 接近100%可用
- 端到端延遲 < 100ms
- 任何節點(diǎn)都可水平擴展
- 支持數百萬(wàn)人同頻道(無(wú)理論上限)
- 大型活動(dòng)中支持數百萬(wàn)QPS消息下發(fā)
- 核心功能超高響應
所謂核心功能,目前消息核心支持的功能是點(diǎn)對點(diǎn)消息、頻道消息,可以加入頻道、退出頻道。用戶(hù)也可以同時(shí)加入多個(gè)頻道,使用一些頻道管理的功能,比如獲取用戶(hù)屬性、頻道狀態(tài),能查詢(xún)頻道中有多少人,其他用戶(hù)是否在線(xiàn)等基本功能。

在此,以點(diǎn)對點(diǎn)消息為例,和大家分享一下擴展性是怎么樣做的。首先 SDK 登錄系統的時(shí)候,會(huì )通過(guò) DNS 來(lái)訪(fǎng)問(wèn)我們的 AP 服務(wù),AP 知道附近的邊緣節點(diǎn) R 的地址,會(huì )根據當前的客戶(hù)端的地理分布,包括邊緣節點(diǎn)的負載情況來(lái)給 SDK 回一組地址。SDK 在拿到地址之后,可以登錄連接邊緣節點(diǎn),然后發(fā)消息。這些消息到達邊緣節點(diǎn)后會(huì )投遞給本區的點(diǎn)對點(diǎn)消息轉發(fā)節點(diǎn) F。F 知道本區內所有用戶(hù)登錄在哪個(gè)邊緣節點(diǎn),這是由本區所有邊緣節點(diǎn) R 上報給轉發(fā)節點(diǎn) F 的。圖中的 U 是用戶(hù)在線(xiàn)狀態(tài)服務(wù)器,那么一個(gè)用戶(hù)給另外的用戶(hù)發(fā)消息,有三種情況,第一種情況,對端在線(xiàn)并且在同一個(gè)區里面,F 可以直接投遞;第二種情況對端在線(xiàn)但在別的區里面;第三種情況對端不在線(xiàn)。在后兩種情況中,消息轉發(fā)服務(wù)器 F 不知道該用戶(hù)的信息,也不知道在哪個(gè)節點(diǎn)上。這時(shí)候就可以通過(guò) U 來(lái)獲取這些用戶(hù)狀態(tài),因為 U 知道全網(wǎng)跨區情況下的用戶(hù)生命周期,也知道這個(gè)用戶(hù)是否在線(xiàn),F 去問(wèn) U 是否在線(xiàn),如果在線(xiàn)在哪個(gè)區里面,可以通過(guò)跨區投入到別的用戶(hù)。
這里的可擴展體現在哪里呢?首先,所有的節點(diǎn)都是可以水平擴展的,隨著(zhù)業(yè)務(wù)量增長(cháng),可以增加部署。邊緣節點(diǎn)是可以隨意增加的,而核心節點(diǎn) F 和 U 不能做任意的水平擴展,因為他們保留了一定的狀態(tài),我們用了一個(gè)一致性哈希的分片方法,所以把所有用戶(hù)的賬號哈希之后產(chǎn)生一個(gè) 32 位的隨機數,想象把這些數放到一個(gè)環(huán)上,每個(gè)服務(wù)器各自產(chǎn)生一組隨機數,在環(huán)上均勻分布。這樣所有的消息會(huì )被映射到比自己的哈希值小的那一個(gè)服務(wù)器上面。所有的節點(diǎn)的 partition 都是可以動(dòng)態(tài)地增加和減少的。假如說(shuō)有一個(gè)核心服務(wù)器故障或者下架了,那么它可以重新分布到別的服務(wù)器上,實(shí)際上我們地消息核心中除了邊緣節點(diǎn)R之外還有十幾種核心節點(diǎn),它們都是做了分片的。這就是所謂的可擴展性。

高可用怎么樣做呢?首先如上圖所示介紹一下頻道消息簡(jiǎn)單的流程。假定邊緣服務(wù)器收到用戶(hù)的頻道消息,會(huì )把該消息投遞給 F,F 是點(diǎn)對點(diǎn)消息的轉發(fā)服務(wù)器,它看到是頻道消息的話(huà)會(huì )自動(dòng)拋給 D,D 專(zhuān)門(mén)負責頻道消息分發(fā),D 采用是級聯(lián)的模式,每一個(gè)區都有一組總的頻道消息分發(fā)服務(wù)器,在每個(gè)數據中心會(huì )有一組機房級別的代理。區域級根服務(wù)器發(fā)消息到機房級別的代理服務(wù)器,機房級服務(wù)器往該機房所有的邊緣節點(diǎn) R 轉發(fā),這樣可以保證在超大頻道下面的性能。現在有一個(gè)問(wèn)題,之前我說(shuō)了 U 是保存用戶(hù)的生命周期的,而頻道的生命周期與用戶(hù)不一樣,頻道不是一個(gè)特定的個(gè)體。比如說(shuō)用戶(hù)要么在中國或美國,不可能同時(shí)在中國和美國,但頻道可以。尤其當頻道比較大的時(shí)候,分布會(huì )非常廣,很有可能是跨區頻道,甚至在中國、美國、歐洲都有用戶(hù)處于同一頻道。那么你該怎樣獲取某頻道的用戶(hù)分布呢?我們用頻道分布服務(wù)器 O 來(lái)處理。所有的 R 都會(huì )在本地頻道創(chuàng )建、銷(xiāo)毀的時(shí)候,把該事件通知給 O。O 把頻道分布的信息告訴頻道消息轉發(fā)服務(wù) D,D 會(huì )從中獲得兩個(gè)信息,第一個(gè)信息是對于某頻道來(lái)說(shuō),在本區內該頻道的用戶(hù)分布在哪幾個(gè)邊緣服務(wù)器上,第二個(gè)信息是可以知道該頻道是否跨區,如果跨區的話(huà),又是哪幾個(gè)區域。D 通過(guò)第一個(gè)信息可以判斷在本區投遞給哪些用戶(hù),通過(guò)第二信息可以知道需要通過(guò)跨區傳輸網(wǎng)絡(luò )投遞給哪些別的區域的 D,讓它們在別的區域來(lái)負責下發(fā)。
在這里高可用主要體現在 O 是對等部署的。我們每一條消息或者每一次狀態(tài)改變或者每一個(gè)查詢(xún)請求都會(huì )有一個(gè)全局唯一的 ID,這個(gè) ID 由兩部分組成,第一部分保證其唯一性,第二部分保證在某一個(gè) session 之內前后的請求有一個(gè)單調遞增的大小關(guān)系。這樣的話(huà),從多臺對等部署的 O 同步給 D 的頻道分布信息,就相當于要保證一個(gè)單一來(lái)源但多路徑的信息同步的一致性問(wèn)題,我們是可以通過(guò)這個(gè) ID 來(lái)做到版本控制和除重從而保證一致的。當然對等部署只是其中一個(gè)手段,還有很多別的模式用到不同的服務(wù)上面,比如事件中心的高可用就是由雙數據中心主備切換來(lái)保證的。但消息核心中的服務(wù)一般都是采用的比較激進(jìn)的對等部署的方式,這樣的好處是任何一個(gè)服務(wù)器掛了都不會(huì )有切換的事件,保證服務(wù) 100% 可用。
事件中心(Event Center)
Messaging Core 下面是 Event Center。就像我在開(kāi)頭說(shuō)到的,Messaging Core 有一個(gè)限制,它是靠多重冗余和相對激進(jìn)的策略來(lái)保證低延遲和高可靠的系統,因此很多擴展的功能沒(méi)有辦法做,所以會(huì )通過(guò) Event Center 來(lái)支持這些擴展功能。
舉個(gè)例子,比如用戶(hù)屬性是在消息核心中完成的,而頻道屬性在消息核心中就做不了。因為頻道屬性和用戶(hù)屬性不一樣的地方在于,對于某一個(gè)用戶(hù),他的用戶(hù)屬性只有他自己能夠編輯,他是該屬性的主人,由該用戶(hù)的客戶(hù)端來(lái)保證屬性的一致性。所以就算在服務(wù)端有多重冗余的情況下,該屬性也可以達到最終一致。但頻道屬性不同。頻道里可能同時(shí)有多個(gè)人在同時(shí)編輯頻道屬性,也可能同時(shí)有多個(gè)人在讀該屬性,怎樣達到一致性?這里就需要對頻道消息的編輯操作有一個(gè)統一的來(lái)源。但這個(gè)來(lái)源又不能是單點(diǎn),否則很容易出故障也很容易成為瓶頸。
因此我們決定將所有的事件,包括狀態(tài)改變、消息的投遞都統一寫(xiě)到 Event Center 里面。Event Center 分為兩個(gè)部分,Event Storage 和 Event Queue。我們的實(shí)現原則是傳輸與狀態(tài)隔離,數據與索引隔離。傳輸是 Messaging Core 和跨區傳輸網(wǎng)絡(luò )來(lái)負責,狀態(tài)是存在 Event Center,而 Application Services 是消費的狀態(tài),這樣可以做到傳輸與狀態(tài)的隔離。
那什么叫數據與索引隔離呢?對于所有的事件來(lái)說(shuō)我們都會(huì )把它的 meta data,或者叫事件的 header 放到 Event Queue 里,這樣消費者去消費事件隊列的話(huà)就會(huì )很快,而事件的內容本身則放在 Event Storage。我之前說(shuō)過(guò)對于 RTM 的所有消息、事件、查詢(xún)都有一個(gè)ID,這樣的話(huà)就能建立一個(gè)事件 Header - 事件ID - 事件Body 之間的映射。消費者可以通過(guò) Event Queue 建立對事件 Header 的索引,通過(guò)這個(gè)索引來(lái)做各種業(yè)務(wù)邏輯,然后再通過(guò) ID 來(lái)找到對應的事件 Body。比如對于歷史消息的條件查詢(xún)就是這么做的。在這種模式下我們可以做到比如查詢(xún)當前在線(xiàn)的所有用戶(hù)里屬性屬性滿(mǎn)足 "gender:female","age:24" 的用戶(hù)。
應用服務(wù)(Application Services)
Application Services 是一個(gè)微服務(wù)的架構,在 Event Center 的支持下可以支持很多的業(yè)務(wù)邏輯。還包括實(shí)時(shí)的監控、計費、問(wèn)題調查、分析等。它的好處是易于開(kāi)發(fā),我們通過(guò) Event Center 把傳輸和事件解耦了,讓我們可以更容易地實(shí)現更多的功能。目前已經(jīng)落地的功能包括頻道屬性和歷史消息,還有很多其他的功能在開(kāi)發(fā)中。
下面講一下跨區傳輸網(wǎng)絡(luò ),它負責所有區域到區域之間的通信。我們有去中心化地實(shí)時(shí)路由計算策略,會(huì )根據延遲和負載來(lái)動(dòng)態(tài)挑選跨區路由。實(shí)際上你發(fā)現在很多場(chǎng)景下面,跨境傳輸是最難的問(wèn)題,尤其是在教育場(chǎng)景下。例如,老師在東南亞某個(gè)地方,學(xué)生在國內,他們之間建立連接、收發(fā)一些消息的過(guò)程中,穩定性和到達率會(huì )遇到很多問(wèn)題。聲網(wǎng)全球有 200 多個(gè)數據中心,我們通過(guò)智能路由來(lái)進(jìn)行實(shí)時(shí)傳輸,比如中國到菲律賓,當前網(wǎng)絡(luò )不好的時(shí)候,我們可能會(huì )通過(guò)新加坡進(jìn)行中轉,如果新加坡到菲律賓好但是到國內不好,我們會(huì )也許會(huì )通過(guò)國內某個(gè)機房先中轉到新加坡。RTM SDK 今年上線(xiàn)后,從運營(yíng)數據來(lái)看高峰期的跨洋平均 RTT 是 250ms,該數據已經(jīng)比較接近實(shí)際網(wǎng)絡(luò )傳輸延遲。

如上圖所示是簡(jiǎn)化版的跨區傳輸網(wǎng)絡(luò ),這個(gè)算法有點(diǎn)類(lèi)似于 BGP 算法。自治域與自治域之間全連接,每個(gè)節點(diǎn)都有自己的路由表,每個(gè)節點(diǎn)會(huì )定期廣播自己的路由表到別的節點(diǎn)。比如 A 知道到自己到 B、C、D 的延遲是多少,一輪廣播之后 B、C、D 就會(huì )知道自己如果通過(guò) A,到其他節點(diǎn)的延遲會(huì )有多少。各節點(diǎn)會(huì )選擇延時(shí)較短的路線(xiàn)傳輸。當然,實(shí)際策略肯定不會(huì )這么簡(jiǎn)單,因為如果所有節點(diǎn)都采用相同策略,流量可能會(huì )匯集到某一些節點(diǎn)上去,在流量高峰期時(shí)會(huì )對這些節點(diǎn)造成沖擊。因此我們有一套很復雜的策略來(lái)進(jìn)行負載均衡。
目前我們的 RTM SDK 已經(jīng)發(fā)布了Beta 版,大家可以訪(fǎng)問(wèn)聲網(wǎng)開(kāi)發(fā)者中心或點(diǎn)擊:https://docs.agora.io/cn/Real-time-Messaging/RTM_product?platform=All%20Platforms下載試用。歡迎大家給我們提出更多的建議和需求。