有关 EMQX 水平可扩展性的挑战与对策 - MQTT Broker 集群详解(三)
在这篇文章中,我们将介绍 MQTT Broker 集群在可扩展性方面的一些改进。 我们将主要关注 EMQX 内部使用的数据库引擎,以及它在 EMQX 5.0 版本中是如何改进的。
在开始本文之前,我们需要了解 EMQX 集群中是数据是如何复制的:EMQX broker 将主题和客户端的运行时信息存储在 Mnesia 数据库中,有助于跨集群复制数据。
Mnesia 简介
Mnesia 是一个开源数据库管理系统,由爱立信公司开发作为开放电信平台( Open Telecom Platform )的一部分,最初是用来处理 ISP 级电信交换机中的配置和运行时数据。EMQX 4.3 之前的版本使用其来存储各种运行时数据,例如主题、路由、ACL 规则、告警等等。
MySQL、Postgres、MongoDB 等数据库以及 Redis 和 memcached 等内存存储大家应该都非常熟悉,对 Mnesia 则可能不甚了解。但它确实有其独特的优势,可将上述产品的许多功能集成到一个简洁的应用程序中。
Mnesia 有一个相当学术的定义:一个嵌入式、分布式、事务型的 noSQL(非关系型)数据库。听起来有些复杂,我们接下来将逐一为大家解释。
嵌入式
MySQL 和 Postgres 等最广泛使用的数据库普遍采用客户端—服务器模式:数据库在单独的进程中运行(通常在专用服务器上),业务应用程序通过网络或 UNIX 域套接字发送请求并等待答复,通过这种方式来与数据库交互。这种模式在很多方面都很方便,因为它允许将业务逻辑与存储分开并单独管理。 但同时也有一些缺点:与远程进程交互不可避免地会增加每个请求的延迟。
相反,嵌入式数据库与业务应用程序则在相同的进程中运行。sqlite 是一个典型的嵌入式数据库的例子。Mnesia 也属于这一类:它与其他 EMQX 应用程序在同一进程中运行。 从 Mnesia 表中读取数据可以像读取局部变量一样快,因此我们可以在热点中读取数据库数据而不会影响性能。
分布式
我们之前提到过 Mnesia 是一个分布式数据库,这意味着数据表被网络复制到不同的物理位置。对于分布式数据库,如果节点之间不共享任何物理资源(如 RAM 或磁盘),而是在应用程序级别进行协调,这种类型称为无共享架构 (SN)。 这种类型通常是首选,因为它不需要任何专门的硬件,并且可以水平扩展。
Mnesia 应用程序与 EMQX 一起运行,有助于通过 Erlang 分发协议跨集群中的所有节点复制表更新。 这意味着业务应用程序可以在本地读取更新的数据。它还有助于提升容错性能:只要集群中有一个节点处于活动状态,数据就是安全的。EMQX 依靠此功能实现跨集群复制路由信息。
事务型
Mnesia 支持 ACID 事务,这是嵌入式数据库的一个非常独特的功能。这意味着可以将多个读取和更新操作组合在一起。一个 Mnesia 事务具有原子性(必须完整或无任何效力)、一致性(尽管保证比 Postgres 更宽松)、隔离性(不影响其他事务)和持久性。所有这些保证都在整个集群中保留。
在数据一致性关键场景中,EMQX 采用 Mnesia 事务。
NoSQL
传统的关系型数据库使用一种称为 SQL 的特殊查询语言与数据库进行交互,这种数据库通常使用 ORM(对象关系映射) 来加快开发速度。 另一方面,Mnesia 没有专门的查询语言:它使用 Erlang(或 Elixir)作为查询语言,因此不需要 ORM。 它直接使用 Erlang 术语进行查询操作,与业务逻辑的集成非常顺畅。
架构
在 Mnesia 集群中,所有节点都是平等的。 每个节点都可以存储任何表的副本、启动事务并访问这些表。 Mnesia 集群使用全网状拓扑:每个节点都与集群中的所有其他节点对话。 每个事务都被复制到集群中的所有节点,如下图所示:
Mnesia 集群
针对 CAP 原则(在一致性、可用性、分区容错性三个要素中选择两个),Mnesia 默认为 AP(可用性、分区容错性)。
挑战
综上所述,Mnesia 数据库有一系列独特的功能,并都在 EMQX 中得到了使用。现在,我们要谈谈它的缺点以及我们改进它的原因。
尽管 Mnesia 与硬件无关,但它最初的开发考虑了特定的集群架构:一组服务器,通过快速、低延迟的局域网实现互连。
在理想条件下,网状拓扑结构可以减少事务复制延迟:节点之间的所有通信都可以并行完成,无需任何中介。 然而,它限制了集群的水平可扩展性,因为节点之间的链接数量和节点数量之间是平方关系。随着节点数量的增加,保持所有节点完全同步的成本越来越高,事务的性能也会下降。
节点的同等性质和传统的集群范式叠加后,使得更换单个节点变得容易,但是可以同时加入集群的节点数量受到限制。
于是我们就面临这样一个局面:集群部署在地理冗余的云环境中,一切都是动态的和暂时的,节点在自动扩展组中运行,我们希望它们一直在波动状态。
为了应对这些挑战,我们对 Mnesia 进行了扩展,称之为 Mria。
对策:Mria 的引入
Mria 是 Mnesia 的开源扩展版本,它为 Mnesia 带来了最终一致性。
Mria 从全网状拓扑架构转变为网状+星型拓扑架构。 每个节点承担两个角色之一:核心(core)或复制者(replicant)。
核心(core)节点的行为很像常规的 Mnesia 节点:它们以全网状连接,每个节点都可以发起写事务、持有锁等。核心节点很大程度上都是静态和持久的。
另一方面,复制(replicant)节点不参与事务。它们连接到某一个核心节点,并被动地从中复制事务。这意味着不允许复制节点自行执行任何写操作。相反,它们要求核心节点代表它们更新数据。同时,它们拥有数据的完整本地副本,因此读取访问速度也同样快。
Mria 集群
可以将 Mria 看作是客户端-服务器和嵌入式数据库的组合:通过服务器写入,但在本地读取。
这种集群拓扑架构解决了两个问题:
- 水平可扩展性
- 支持集群自动扩展
由于复制节点不参与写入,因此当向集群添加更多复制节点时,事务延迟不会受到影响,从而允许创建更大的 EMQX 集群。
此外,复制节点被设计为暂时的。 添加或删除它们不会改变数据冗余,因此可以将它们放在自动扩展组中,从而实现更好的 DevOps 实践。
在下一篇文章中,我们将更详细地讨论如何配置 EMQX 来充分 Mria 的优势。