從百萬到億級:EMQX 5.0 新架構的利與弊
1.Mnesia:Erlang語言中的分布式數據庫
在EMQX 5.x版本之前,集群數據存儲采用的是Erlang/OTP自帶的實時分布式數據庫管理系統——Mnesia。Mnesia是用Erlang語言實現的,并且與Erlang緊密耦合,這也造就了它的獨特之處,它幾乎將Erlang變成了一種數據庫編程語言。Mnesia可以說是專為用Erlang編寫的工業級電信應用程序而設計的,并提供了支持高容錯電信級系統所必需的常用功能。它試圖解決典型電信系統所需的所有數據管理問題,并具有許多傳統DBMS通常不具備的功能。其提供的特性主要包括:
- 快速實時的鍵值查找;
- 復雜的非實時性查詢;
- 分布式數據支持;
- 高容錯性;
- 復雜的數據對象。
Mnesia通常支持兩種數據訪問模式:本地模式和遠程模式。本地模式采用的是全連接、點對點的復制模式,即節點中的數據表會復制到集群所有節點中;而在遠程模式中,當要訪問的表沒有本地副本時,會通過RPC調用讀取遠程具有數據表副本的節點。本地模式訪問的缺點是集群擴展性差,且存在腦裂的風險,但優點也顯而易見,因為集群中每個節點都擁有集群全量的數據,故而可以通過本地查詢來提高檢索效率。
相對于遠程模式的網絡操作而言,本地讀取數據的延遲要比遠程模式的網絡延遲小幾個數量級。另外,這種實現方式也能提高集群的分布式容錯能力,只要保證集群中仍有存活的節點,集群數據就是全量的、安全的。所以在早期的EMQX實現中,默認使用的就是本地模式。尤其是在消息分發時,通過本地查詢Mnesia數據庫中的路由表數據快速定位到消息要投遞的節點,可以實現個位數毫秒的高效、低延時的消息分發操作。
2.Mnesia的弊端:復制帶來的開銷
如前文所述,由于Mnesia集群使用全網狀的連接架構,集群中每個節點都會與其它所有的節點建立連接,每個節點產生的事務也都被會復制到集群中的所有節點上。這就導致集群的整體可擴展性差:首先,集群中每增加一個節點,集群數據同步的開銷也會隨之增大,且由于網絡問題導致的集群腦裂的風險也會增加。其次,集群中每個節點都要能夠承載全量的集群數據,相對于Mnesia這種經常將數據存放在內存中的應用場景來說,服務器資源的投入也會跟著集群規模的擴展而增加,對機器配置和性能的要求也會越來越高。集群節點間的數據復制成本和服務器資源投入這兩個問題一直是限制集群擴展性的核心問題。
Mnesia 網狀拓撲架構
3.Mria:從全網狀到單復制
為了解決Mnesia全網狀復制帶來的問題,EMQX 5.x版本中引入了新的數據層解決方案實現——Mria。Mria對Mnesia進行了封裝,其核心訴求是在實現數據的本地讀寫的基礎上,盡可能地減少集群節點復制的開銷。
Mria將原有的全網狀復制的Mnesia節點擴展成兩種不同的角色節點——核心節點(Core)和復制節點(Replicant)。核心節點與傳統的Mnesia節點行為類似,仍舊采用全網狀的復制模式,所有核心節點之間的事務仍會復制到其它核心節點上。復制節點則不直接參與Mnesia事務處理,而是連接到集群中某個核心節點上,被動地復制來自核心節點的數據更新。為此,核心節點還同時擁有另外一項重要的工作,即處理連接到自身的所有復制節點的數據處理。
由于復制節點不再參與集群中事務的同步工作,只有少數的核心節點會實時地同步事務,而復制節點只是復制對應核心節點的數據,所以這種實現模式在復制節點可以擁有集群全量數據以實現高效的本地數據檢索的前提下,同時能夠減少整個集群的事務同步開銷。借助于復制節點的特性,當更多的設備需要接入到集群中時,只需要相應地擴展復制節點的數量,讓這些節點承載設備連接,而又不會直接增加核心節點寫操作的延時,從而達到擴展集群規模的效果。
Mria 單復制拓撲架構
但是Mria這種架構實現也不是銀彈,雖然它可以解決全網狀復制帶來的數據同步問題,但是依然無法很好地處理所有節點都要承載集群全量數據的問題。
另外需要特別注意的是,為了提高Mria架構的復制效率,EMQX官方在Erlang/OTP實現的基礎上引入了一個叫做post-commit鉤子的實現。如果要應用新的Mria架構,需要使用有此補丁的Erlang/OTP庫,否則集群會自動降級到Mnesia的實現模式。遺憾的是,到目前為止,該新特性并未合并到Erlang/OTP官方倉庫中,需要研發人員自己構建帶有此補丁的依賴庫。
PR: mnesia: Add post-commit hook #5926
4.AMQ 2.0:基于角色的路由分發
AMQ是中國移動智慧家庭運營中心自研的基于開源EMQX實現的物聯網連接中間件。為了增加集群的擴展能力,我們在2.0版本中引入了Mria開源實現的新特性,用于解決集群節點復制的開銷問題。同時,為了解決所有節點需要承載集群全量數據的問題,我們設計了新的集群數據復制實現——連接分發引擎:一種基于節點角色進行訂閱/復制的路由分發機制。
路由數據是物聯網連接集群中的核心數據,它存儲設備訂閱主題與集群節點的映射關系,在消息發布時根據消息主題信息查找所有匹配的節點,用于集群內節點間的消息派發。在EMQX的實現中,路由數據存在于集群中的所有節點上。客戶端的主題訂閱數據,則只保存在連接所在的節點上,用于節點內部派發消息到客戶端。當客戶端連接到集群某個節點訂閱某個新的主題時,就會生成一條路由數據,該數據最終會同步到集群所有節點上,每個節點都可以通過本地查詢找到任意主題對應的訂閱節點列表。當客戶端發布消息時,連接所在節點會根據消息主題檢索路由數據得到所有訂閱節點的信息,然后將消息派發到這些節點上。
Mria實現的一個問題就在于,集群中很多節點復制了本身就不需要的路由數據。設想這樣一種場景:一個智能門鎖和一個智能臺燈分別連接到集群中的NodeA和NodeB節點上,并且分別訂閱了主題TopicA和TopicB。由于EMQX實現的特性,這兩個節點都會存儲一條包含TopicA和TopicB的路由數據。但由于門鎖和臺燈之間不會直接互相發布消息,對這兩個節點來說,他們都存儲了一條永遠也不會用到的路由數據。同理,當集群中接入的設備越來越多時,每個節點上都會存在大量無用的路由數據記錄。這不僅會增加服務器資源的投入,還會導致查詢性能的降低,另外在新節點加入集群時,還會導致數據復制時間的增加,降低節點的接入效率。
在AMQ 2.0實現的路由分發機制中,每個節點都有一個數據復制角色:DB(Database),SVC(Service)或者CONN(Connection)。其中只有少數的DB角色節點才擁有全量的集群數據,在集群中承擔“數據中心”的角色。DB節點負責根據可配置的訂閱策略將路由數據分發給對應的SVC或CONN節點。另外,SVC和CONN節點并不會簡單地復制DB節點的所有路由數據,而是根據可配置的角色策略選擇性地復制自己所需要的數據。這樣,這些節點存儲的數據就是有限的,并不會隨著集群數據量的增加而增加,并且仍舊采取本地查詢的方式檢索數據,不會影響消息派發時數據檢索的效率。此外,由于不需要同步集群全量數據,每個SVC和CONN節點都可以做到快速接入、快速完成存量數據的復制。
AMQ基于角色的路由分發拓撲架構
5.總結
無論是EMQX 4.x的Mnesia實現,還是EMQX 5.x的Mria實現,亦或是AMQ 2.0的路由分發實現,目的都是一樣的:在確保數據讀寫效率的前提下,盡可能地擴展集群的規模。