# 分布式集群介绍

EMQX 原生支持分布式集群架构,在保证高可用性、容错性以及可扩展性的同时,能够处理大量客户端和消息。

相比与之前版本,EMQX 5.0 集群采用了新的 Mria 集群架构,单节点能支持 500 万 MQTT 设备连接,集群可扩展至 1 亿并发 MQTT 连接。

本章节将向您介绍 EMQX 分布式集群特性以及工作方式,以帮助您更好的使用 EMQX 集群。

# 工作原理

集群中的每个节点都运行一个 EMQX 实例,每个节点与集群中的其他节点通信,共享客户端连接、订阅、发布消息等信息。这允许集群在节点之间自动分配负载并在节点出现故障时提供高可用性。

EMQX 集群在不同节点上维护数据的多个副本来提供数据冗余,即使一个节点发生故障,数据在集群中的其他节点上仍然可用。

在集群架构下,您可以随着的增长向集群添加新节点,从而提供可扩展性。这使您可以处理越来越多的客户端和消息,而不必担心单个代理的限制。

EMQX 由 Erlang/OTP (opens new window) 驱动,Erlang/OTP 最初是爱立信为电信设备开发和设计的编程语言平台。正式介绍 EMQX 集群之前,我们首先来了解下什么是 Erlang/OTP。

# Erlang/OTP 与 Erlang 节点(Node)

电信设备(如路由器、接入网关)通常是通过背板连接主控板卡与多块业务板卡构成的分布式系统。

Erlang/OTP 平台的分布式程序是由分布互联的 Erlang 运行时系统组成,每个 Erlang 运行时系统被称为一个节点(Node),节点通过 TCP 相互连接,形成一个网络,节点之间采用消息传递的方式通信。

Erlang 节点由唯一的节点名称来识别,该名称由用 @ 分隔的两部分组成:

<name>@<ip-address-or-FQDN>
1

节点之间通过节点名通信。接下来我们将演示如何在 Erlang REPL 中创建节点并组建简单集群。

  1. 在本地启动四个 shell 终端,然后使用 -name 参数,并设置相同的 cookie 来分别启动四个 Erlang 节点。
erl -name node1@127.0.0.1 -setcookie my_nodes
erl -name node2@127.0.0.1 -setcookie my_nodes
erl -name node3@127.0.0.1 -setcookie my_nodes
erl -name node4@127.0.0.1 -setcookie my_nodes
1
2
3
4
  1. 进入 node1@127.0.0.1 的控制台,查看当前节点的名称和连接的节点,其中 node(). 用于查看当前节点名,nodes(). 用于查看与当前节点建立连接的其他节点。
(node1@127.0.0.1) 4> node().
'node1@127.0.0.1'

(node1@127.0.0.1) 4> nodes().
[]
1
2
3
4
5
  1. 告知 node1 发起与其他节点的连接。
(node1@127.0.0.1) 1> net_kernel:connect_node('node2@127.0.0.1').
true
(node1@127.0.0.1) 2> net_kernel:connect_node('node3@127.0.0.1').
true
(node1@127.0.0.1) 3> net_kernel:connect_node('node4@127.0.0.1').
true
1
2
3
4
5
6
  1. 重新运行步骤 2 中的命令查看连接到 node1 的所有节点。
(node1@127.0.0.1) 4> nodes().
['node2@127.0.0.1','node3@127.0.0.1','node4@127.0.0.1']
1
2

我们可以看到,node2node3node4 已经与 node1 建立了分布式连接,这四个节点组成一个集群。

新的节点加入集群后,它将与集群中的每个节点都建立一个 TCP 连接,这四个节点之间的连接关系如下图所示:

image

# EMQX 分布式集群设计

EMQX 分布式集群的基本功能是转发和发布消息到订阅者,如下图所示。

image

为了实现这一目标,EMQX 在 嵌入式数据库 Mria 中维护着与之相关的几个数据表:

  • 订阅表
  • 路由表
  • 主题树

# 订阅表:主题-订阅者

EMQX 会维护一个订阅表,用于存储主题->订阅者之间的映射关系,从而确保能将传入消息正确路由到对应的客户端。该数据只存在于订阅者所在的 EMQX 节点上,类似的结构如下:

node1:

    topic1 -> client1, client2
    topic2 -> client3

node2:

    topic1 -> client4
1
2
3
4
5
6
7
8

# 路由表:Topic-Node

路由表记录了 主题->节点 之间的映射,它存储每个节点上客户端订阅的主题列表,并用于将消息路由到对应的节点。该数据会在同一集群中的所有节点复制一份,类似结构如下:

topic1 -> node1, node2
topic2 -> node3
topic3 -> node2, node4
1
2
3

# 主题树:主题匹配通配符

主题树是一种分层数据结构,它存储有关主题层次结构的信息,并用于消息与订阅客户端的匹配。

主题树会在同一集群中的所有节点复制一份,下面是一个 主题-订阅关系 的例子:

ClientNodeSubscribed topic
client1node1t/+/x, t/+/y
client2node2t/#
client3node3t/+/x, t/a

当所有的订阅完成后,EMQX 会维护以下主题树路由表

image

# 消息分发流程

当一个 MQTT 客户端发布消息时,它所在的节点会查找路由表,并根据消息主题将消息转发到对应的节点(可能是多个节点)。

然后,接收到消息的节点会查找本地订阅表,并将消息发送至对应的订阅者。

例如,当客户端 1 发布一条消息到主题 t/a 时,消息在节点之间的路由和分发如下:

  1. 客户端 1节点 1 发布一条主题为 t/a 的消息;
  2. 节点 1 查询主题树,了解到 t/a 与现有主题 t/at/# 相匹配。
  3. 节点 1 查询路由表,并得知:
    1. 节点 2 上有客户端订阅了 t/# 主题;
    2. 节点 3 上有客户端订阅了 t/a 主题; 因此节点 1 会将消息同时转发给节点 2节点 3
  4. 节点 2 收到转发的 t/a 消息后,通过查询本地订阅表,将消息分发给订阅了 t/# 的客户端。
  5. 节点 3 收到转发的 t/a 消息后,通过查询本地订阅表,将消息分发给订阅了 t/1 的客户端。
  6. 消息发布完成。

# 数据分区

EMQX 的订阅表在集群中分区存放,而主题树和路由表是集群复制的。

# EMQX 群集节点发现和自动群集

在分布式 Erlang 的基础上,EMQX 增加了一个抽象层: Ekka (opens new window)

Ekka 是一个为 Erlang/OTP 应用开发的集群管理库,支持 Erlang 节点自动发现、自组群集,以及网络分区自动修复和自动清理。

EMQX 支持多种节点发现策略。

策略描述
手动通过手动命令创建一个集群
静态静态节点列表的自动群集
组播采用 UDP 组播模式的自动群集
dnsDNS A 记录的自动群集
etcd通过 etcd 的自动群集
k8sKubernetes 服务的自动群集

# EMQX 集群协议设置

Erlang 集群中的每个节点都可以通过 TCP 或 TLS 连接, 可以在 etc/emqx.conf 中配置:

配置名称类型默认值说明
cluster.proto_dist枚举inet_tcp具有可选值的分布式协议如下:
-inet_tcp:使用 TCP IPv4
-inet6_tcp:使用 TCP IPv6
-inet_tls:使用 TLS
node.ssl_dist_optfile文件路径etc/ssl_dist.confcluster.proto_dist 选择为 inet_tls 时,需要配置 etc/ssl_dist.conf 文件,并指定 TLS 证书。

# 网络分区自动修复

EMQX 支持网络分区自愈,可以在 etc/emqx.conf 中进行配置。

cluster.autoheal = on
1

网络分区自愈过程如下:

  1. 节点在收到 Mnesia 的 "数据库不一致" 事件 3 秒后执行网络分区确认。
  2. 节点确认网络分区发生后,它将消息报告给 Leader 节点(集群中最早开始的节点)。
  3. 在 Leader 节点延迟一段时间后,当所有节点都在线时,它创建一个 SplitView。
  4. Leader 节点选择多数分区中的自愈协调者节点。
  5. 协调者节点重新启动少数派分区的节点以恢复集群

# 群集节点的自动清理

EMQX 支持集群自动清理,可以在 etc/emqx.conf 中进行配置。

cluster.autoclean = 5m
1

启用该功能后,断开连接的节点会在配置的时间间隔后自动从集群中移除

# 跨节点会话

EMQX 集群模式下,MQTT 连接的持久会话(Session) 可以跨节点专业。

例如负载均衡的两台集群节点: node1 与 node2,同一 MQTT 客户端先连接 node1,node1 节点会创建持久会话;客户端断线重连到 node2 时,MQTT 的连接在 node2 节点,持久会话仍在 node1 节点。

# 拓展阅读

您可继续阅读以下章节了解如何组建 EMQX 集群。