咱們在5月正式發佈了實時消息(RTM)SDK。在5 月 27 日舉行的 Qcon 廣州站上,聲網 Agora 資深技術架構師吉奇 以《高併發場景下分佈式實時信令系統的架構實踐》做爲話題,分享了 RTM SDK 背後的架構設計經驗。算法
如下爲演講實錄:服務器
你們好!我叫吉奇,來自聲網。如今負責聲網RTM 實時信令雲服務後臺及SDK技術架構設計。此次演講會按照RTM的系統架構上的分佈或子系統的層級關係來展開。網絡
首先,RTM 是一個通用的消息系統,主要是爲了解決實時場景下信令的低延遲和高併發問題。咱們聲網是業務遍及全球的平臺,所以在全部的後臺設計中,把分區做爲一個比較重要的事情來看。目前 RTM 有幾個大區域,有美洲、亞洲、東南亞、中國大陸,還有歐洲、非洲幾個大區。區與區之間相對獨立,每一個區會有跨區傳輸網絡。每一個區之間由三個子系統組成,首先是消息核心(Message Core),還有事件中心(Event Center),最後是應用服務(Application Services)。我會分別講一下各個子系統內部的架構實現,即消息核心、事件中心、應用服務和跨區網絡。session
首先是消息核心,它是目前成熟度最高,也是最複雜的子系統。在該系統裏面有幾個主要的組件,首先有接入服務器、點對點消息轉發服務、頻道消息的轉發服務、簡單的狀態管理(包括用戶狀態和頻道狀態),還有頻道分佈狀態服務器。架構
在消息核心,全部的服務都是分佈式,沒有一個單點或者中心式的狀況,所以能夠保證高可用,而且性能方面能夠支持高吞吐量和低延遲。Messaging Core 有一個特色,具備很是大的擴展性,可是它的問題是隻支持基本核心的功能,剩下的都要放在其它子系統中。併發
分佈式的信息核心有幾個優點特性:負載均衡
徹底排除單點故障分佈式
接近100%可用微服務
端到端延遲 < 100ms高併發
任何節點均可水平擴展
支持數百萬人同頻道(無理論上限)
大型活動中支持數百萬QPS消息下發
核心功能超高響應
所謂核心功能,目前消息核心支持的功能是點對點消息、頻道消息,能夠加入頻道、退出頻道。用戶也能夠同時加入多個頻道,使用一些頻道管理的功能,好比獲取用戶屬性、頻道狀態,能查詢頻道中有多少人,其餘用戶是否在線等基本功能。
在此,以點對點消息爲例,和你們分享一下擴展性是怎麼樣作的。首先 SDK 登陸系統的時候,會經過 DNS 來訪問咱們的 AP 服務,AP 知道附近的邊緣節點 R 的地址,會根據當前的客戶端的地理分佈,包括邊緣節點的負載狀況來給 SDK 回一組地址。SDK 在拿到地址以後,能夠登陸鏈接邊緣節點,而後發消息。這些消息到達邊緣節點後會投遞給本區的點對點消息轉發節點 F。F 知道本區內全部用戶登陸在哪一個邊緣節點,這是由本區全部邊緣節點 R 上報給轉發節點 F 的。圖中的 U 是用戶在線狀態服務器,那麼一個用戶給另外的用戶發消息,有三種狀況,第一種狀況,對端在線而且在同一個區裏面,F 能夠直接投遞;第二種狀況對端在線但在別的區裏面;第三種狀況對端不在線。在後兩種狀況中,消息轉發服務器 F 不知道該用戶的信息,也不知道在哪一個節點上。這時候就能夠經過 U 來獲取這些用戶狀態,由於 U 知道全網跨區狀況下的用戶生命週期,也知道這個用戶是否在線,F 去問 U 是否在線,若是在線在哪一個區裏面,能夠經過跨區投入到別的用戶。
這裏的可擴展體如今哪裏呢?首先,全部的節點都是能夠水平擴展的,隨着業務量增加,能夠增長部署。邊緣節點是能夠隨意增長的,而核心節點 F 和 U 不能作任意的水平擴展,由於他們保留了必定的狀態,咱們用了一個一致性哈希的分片方法,因此把全部用戶的帳號哈希以後產生一個 32 位的隨機數,想象把這些數放到一個環上,每一個服務器各自產生一組隨機數,在環上均勻分佈。這樣全部的消息會被映射到比本身的哈希值小的那一個服務器上面。全部的節點的 partition 都是能夠動態地增長和減小的。假如說有一個核心服務器故障或者下架了,那麼它能夠從新分佈到別的服務器上,實際上咱們地消息核心中除了邊緣節點R以外還有十幾種核心節點,它們都是作了分片的。這就是所謂的可擴展性。
高可用怎麼樣作呢?首先如上圖所示介紹一下頻道消息簡單的流程。假定邊緣服務器收到用戶的頻道消息,會把該消息投遞給 F,F 是點對點消息的轉發服務器,它看到是頻道消息的話會自動拋給 D,D 專門負責頻道消息分發,D 採用是級聯的模式,每個區都有一組總的頻道消息分發服務器,在每一個數據中心會有一組機房級別的代理。區域級根服務器發消息到機房級別的代理服務器,機房級服務器往該機房全部的邊緣節點 R 轉發,這樣能夠保證在超大頻道下面的性能。如今有一個問題,以前我說了 U 是保存用戶的生命週期的,而頻道的生命週期與用戶不同,頻道不是一個特定的個體。好比說用戶要麼在中國或美國,不可能同時在中國和美國,但頻道能夠。尤爲當頻道比較大的時候,分佈會很是廣,頗有多是跨區頻道,甚至在中國、美國、歐洲都有用戶處於同一頻道。那麼你該怎樣獲取某頻道的用戶分佈呢?咱們用頻道分佈服務器 O 來處理。全部的 R 都會在本地頻道建立、銷燬的時候,把該事件通知給 O。O 把頻道分佈的信息告訴頻道消息轉發服務 D,D 會從中得到兩個信息,第一個信息是對於某頻道來講,在本區內該頻道的用戶分佈在哪幾個邊緣服務器上,第二個信息是能夠知道該頻道是否跨區,若是跨區的話,又是哪幾個區域。D 經過第一個信息能夠判斷在本區投遞給哪些用戶,經過第二信息能夠知道須要經過跨區傳輸網絡投遞給哪些別的區域的 D,讓它們在別的區域來負責下發。
在這裏高可用主要體如今 O 是對等部署的。咱們每一條消息或者每一次狀態改變或者每個查詢請求都會有一個全局惟一的 ID,這個 ID 由兩部分組成,第一部分保證其惟一性,第二部分保證在某一個 session 以內先後的請求有一個單調遞增的大小關係。這樣的話,從多臺對等部署的 O 同步給 D 的頻道分佈信息,就至關於要保證一個單一來源但多路徑的信息同步的一致性問題,咱們是能夠經過這個 ID 來作到版本控制和除重從而保證一致的。固然對等部署只是其中一個手段,還有不少別的模式用到不一樣的服務上面,好比事件中心的高可用就是由雙數據中心主備切換來保證的。但消息核心中的服務通常都是採用的比較激進的對等部署的方式,這樣的好處是任何一個服務器掛了都不會有切換的事件,保證服務 100% 可用。
Messaging Core 下面是 Event Center。就像我在開頭說到的,Messaging Core 有一個限制,它是靠多重冗餘和相對激進的策略來保證低延遲和高可靠的系統,所以不少擴展的功能沒有辦法作,因此會經過 Event Center 來支持這些擴展功能。
舉個例子,好比用戶屬性是在消息核心中完成的,而頻道屬性在消息核心中就作不了。由於頻道屬性和用戶屬性不同的地方在於,對於某一個用戶,他的用戶屬性只有他本身可以編輯,他是該屬性的主人,由該用戶的客戶端來保證屬性的一致性。因此就算在服務端有多重冗餘的狀況下,該屬性也能夠達到最終一致。但頻道屬性不一樣。頻道里可能同時有多我的在同時編輯頻道屬性,也可能同時有多我的在讀該屬性,怎樣達到一致性?這裏就須要對頻道消息的編輯操做有一個統一的來源。但這個來源又不能是單點,不然很容易出故障也很容易成爲瓶頸。
所以咱們決定將全部的事件,包括狀態改變、消息的投遞都統一寫到 Event Center 裏面。Event Center 分爲兩個部分,Event Storage 和 Event Queue。咱們的實現原則是傳輸與狀態隔離,數據與索引隔離。傳輸是 Messaging Core 和跨區傳輸網絡來負責,狀態是存在 Event Center,而 Application Services 是消費的狀態,這樣能夠作到傳輸與狀態的隔離。
那什麼叫數據與索引隔離呢?對於全部的事件來講咱們都會把它的 meta data,或者叫事件的 header 放到 Event Queue 裏,這樣消費者去消費事件隊列的話就會很快,而事件的內容自己則放在 Event Storage。我以前說過對於 RTM 的全部消息、事件、查詢都有一個ID,這樣的話就能創建一個事件 Header - 事件ID - 事件Body 之間的映射。消費者能夠經過 Event Queue 創建對事件 Header 的索引,經過這個索引來作各類業務邏輯,而後再經過 ID 來找到對應的事件 Body。好比對於歷史消息的條件查詢就是這麼作的。在這種模式下咱們能夠作到好比查詢當前在線的全部用戶裏屬性屬性知足 "gender:female","age:24" 的用戶。
Application Services 是一個微服務的架構,在 Event Center 的支持下能夠支持不少的業務邏輯。還包括實時的監控、計費、問題調查、分析等。它的好處是易於開發,咱們經過 Event Center 把傳輸和事件解耦了,讓咱們能夠更容易地實現更多的功能。目前已經落地的功能包括頻道屬性和歷史消息,還有不少其餘的功能在開發中。
下面講一下跨區傳輸網絡,它負責全部區域到區域之間的通訊。咱們有去中心化地實時路由計算策略,會根據延遲和負載來動態挑選跨區路由。實際上你發如今不少場景下面,跨境傳輸是最難的問題,尤爲是在教育場景下。例如,老師在東南亞某個地方,學生在國內,他們之間創建鏈接、收發一些消息的過程當中,穩定性和到達率會遇到不少問題。聲網全球有 200 多個數據中心,咱們經過智能路由來進行實時傳輸,好比中國到菲律賓,當前網絡很差的時候,咱們可能會經過新加坡進行中轉,若是新加坡到菲律賓好可是到國內很差,咱們會也許會經過國內某個機房先中轉到新加坡。RTM SDK 今年上線後,從運營數據來看高峯期的跨洋平均 RTT 是 250ms,該數據已經比較接近實際網絡傳輸延遲。
如上圖所示是簡化版的跨區傳輸網絡,這個算法有點相似於 BGP 算法。自治域與自治域之間全鏈接,每一個節點都有本身的路由表,每一個節點會按期廣播本身的路由表到別的節點。好比 A 知道到本身到 B、C、D 的延遲是多少,一輪廣播以後 B、C、D 就會知道本身若是經過 A,到其餘節點的延遲會有多少。各節點會選擇延時較短的路線傳輸。固然,實際策略確定不會這麼簡單,由於若是全部節點都採用相同策略,流量可能會聚集到某一些節點上去,在流量高峯期時會對這些節點形成衝擊。所以咱們有一套很複雜的策略來進行負載均衡。