# EMQX 集群

EMQX 集群是指多个 EMQX 节点协同工作，组成一个统一系统。各节点之间自动共享客户端会话、主题订阅和路由信息，实现消息的无缝传递与系统的水平扩展。

相比单节点模式需要独立处理所有连接与数据，集群架构可在节点间分担负载，在节点故障时自动重路由流量，确保服务持续可用。该架构非常适合构建大规模、关键业务的 MQTT 消息平台与物联网系统。

## 本章导读

本章节将全面介绍 EMQX 集群的核心概念与实践应用，包括：

- [集群部署的优势](#为什么使用-emqx-集群)
- [EMQX 集群工作原理](#emqx-集群工作原理)
- [Mria 与 RLOG 架构](./mria-introduction.md)
- [如何通过手动或自动方式创建集群](./create-cluster.md)
- [如何保障节点通信安全](./security.md)
- [如何配置负载均衡](./lb.md)
- [如何进行集群负载重平衡与节点疏散](./rebalancing.md)
- [如何进行系统调优和性能测试](../../performance/overview.md)

无论您是构建高可用 MQTT 平台，还是准备部署生产级集群，本章节都将为您提供清晰的指导。

## 为什么使用 EMQX 集群

EMQX 集群适用于对可靠性、可扩展性与高性能有严格要求的大规模或关键业务系统。其主要优势包括：

- **可扩展性**：通过增加节点横向扩展系统，支持不断增长的 MQTT 客户端与消息量。
- **高可用性**：分布式架构避免单点故障，某些节点宕机时系统依然可用。
- **负载均衡**：集群中的节点可分担消息处理与连接负载，避免资源瓶颈。
- **集中管理**：支持统一的管理界面和 API，简化运维工作。
- **数据一致性与安全性**：会话和路由状态在集群节点间自动同步，确保数据一致并保障通信安全。

## EMQX 集群工作原理

一个 EMQX 集群由多个节点组成，每个节点都运行一个 EMQX 实例，这些节点协同工作以路由消息、管理 MQTT 会话，并确保高可用性与可扩展性。每个节点都会与其他节点通信，分享客户端订阅和路由信息，从而确保消息能够准确传递给所有相关订阅者，无论这些订阅者连接在哪个节点上。

这种分布式架构使 EMQX 能够支持关键任务型消息系统，实现极低的停机时间与灵活的横向扩展。

### 集群架构演进

#### EMQX 5.0 之前：基于 Mnesia 的集群架构

早期版本的 EMQX 使用 Erlang/OTP 内置的 Mnesia 数据库，并采用全互联（Full-Mesh）拓扑结构。每个节点通过 Erlang 分布式协议（默认端口为 4370）与集群中所有其他节点建立直接的 TCP 连接，构成一个高度耦合的系统。

<img src="./assets/mnesia-cluster.png" alt="mnesia-cluster" style="zoom: 33%;" />

然而，这种架构存在以下限制：

- 节点数增加时，同步开销显著增加
- 集群规模超过 5 个节点时，容易出现不稳定
- 扩展性有限，通常只能通过垂直扩展（提升硬件性能）来应对

> EMQX 4.3 在基准测试中可支持 1000 万并发连接，但需要进行广泛的性能调优并依赖高性能硬件。详情参见[性能测试报告](https://www.emqx.com/en/resources/emqx-v-4-3-0-ten-million-connections-performance-test-report)。

#### EMQX 5.0 及以后：Mria + RLOG 架构

从 5.0 版本起，EMQX 引入了全新的 [Mria 集群架构](./mria-introduction.md)，支持更大规模、更稳定的集群运行。

核心改进包括：

- **核心节点与副本节点角色分离**：核心节点负责写操作和完整数据复制，副本节点则只读，负责客户端会话处理。
- **复制日志（RLOG）机制**：实现从核心节点到副本节点的异步、高吞吐数据复制。
- **可扩展性提升**：单个集群可支持高达 1 亿个 MQTT 连接。

<img src="./assets/EMQX_cluster.png" alt="EMQX_cluster" style="zoom:30%;" />

::: tip 注意

集群功能在试用期内可用，试用结束后需购买商业 License，否则该功能将被禁用。

:::

为了支持这一架构，EMQX 基于 Erlang/OTP 平台，并借助一套内部数据结构来完成消息路由与投递。接下来的[支撑 EMQX 集群的 Erlang/OTP 架构](#支撑-emqx-集群的-erlang/otp-架构)和[集群数据结构](#cluster-data-structures)小节将详细介绍这些运行机制。

### 支撑 EMQX 集群的 Erlang/OTP 架构

EMQX 基于 [Erlang/OTP](https://www.erlang.org/) 平台开发，这是一个最初为构建分布式电信系统而设计的运行时系统与框架。在 Erlang 中，每个运行实例被称为一个**节点（Node）**，其名称格式为 `<name>@<host>`，例如：`emqx1@192.168.0.10`。

Erlang 节点通过 TCP 连接并使用轻量级的消息传递进行通信。这种机制构成了 EMQX 集群通信的基础。所有集群节点必须使用相同的 Cookie 以完成认证。一旦连接建立并认证通过，该节点即可自动加入集群。在 EMQX 5.x 中，节点的角色（核心或副本）会决定它在数据复制与路由中的职责。

### 集群数据结构

为了在分布式集群中高效路由消息，EMQX 使用三种关键内部数据结构：订阅表、路由表和主题树。这些结构协同工作，确保消息即使在跨节点传输的情况下也能被正确投递给订阅者。

#### 订阅表（分区存储）

每个 EMQX 节点维护一个本地订阅表，用于记录直接连接到该节点的客户端订阅信息。数据是分区存储的，因为每个节点仅保存自身客户端的订阅信息，从而减少了集群的同步负担并提升扩展性。

当消息被路由到某个节点时，该节点会查找本地订阅表以确定哪些客户端应接收该消息。

示例结构：

```
node1:
    topic1 -> client1, client2
    topic2 -> client3

node2:
    topic1 -> client4
```

该示例说明了同一个主题（如 `topic1`）可在多个节点上拥有订阅者，而每个节点独立管理自身的映射关系。

#### 路由表（由核心节点复制）

路由表记录了哪些主题在哪些节点上被订阅。在 EMQX 5.x 中，该表由核心节点维护并复制到所有节点。副本节点仅保留只读副本，这些数据通过 RLOG 同步机制传输。

当客户端在副本节点上发起订阅时，该事件会被上报给核心节点，由其更新全局路由表，并同步到其他节点。

示例结构：

```
topic1 -> node1, node2
topic2 -> node3
topic3 -> node2, node4
```

#### 主题树（由核心节点复制）

主题树是一种分层结构，用于匹配发布的主题与订阅模式（包括 [MQTT 通配符](../../messaging/mqtt-wildcard-subscription.md) `+` 和 `#`）。它帮助 EMQX 高效处理复杂的主题过滤逻辑。

与路由表类似，主题树由核心节点构建并同步至所有副本节点。当客户端（如 `client1`）订阅 `t/+/x` 时，该订阅模式将被添加至主题树，并在全集群中同步更新。

主题订阅关系示例：

| 客户端  | 节点  | 订阅主题     |
| ------- | ----- | ------------ |
| client1 | node1 | t/+/x, t/+/y |
| client2 | node2 | t/#          |
| client3 | node3 | t/+/x, t/a   |

在这些订阅建立后，EMQX 将构建如下的主题树与路由表：

<img src="./assets/cluster_2.png" alt="image" style="zoom:67%;" />

#### 消息分发流程

当一个 MQTT 客户端发布消息时，它所在的节点（无论是核心还是副本）使用主题树匹配消息主题与所有订阅模式，同时查找路由表，并根据消息主题将消息转发到对应的节点（可能是多个节点）。然后，接收到消息的节点会查找本地订阅表，并将消息发送至对应的订阅者。

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

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

如需进一步了解 EMQX 集群设计，请参见 [EMQX 集群设计](../../design/clustering.md)。

## 集群特性概览

EMQX 基于其自研的 [Ekka](https://github.com/emqx/ekka) 库扩展了 Erlang 原生的分布式机制，提供了一整套先进的集群能力。通过 Ekka 提供的抽象层，EMQX 实现了自动节点发现、动态集群组建、网络分区处理、节点清理等关键特性。

### 节点发现与自动集群

EMQX 支持多种节点发现机制，可在不同的部署环境中实现自动集群：

| 策略 | 描述                      |
|------|---------------------------|
| 手动 | 通过手动命令创建一个集群  |
| 静态 | 基于静态节点列表的自动集群 |
| DNS | 基于 DNS A 和 SRV 记录的自动集群 |
| etcd | 通过 etcd 的自动集群     |
| k8s  | 基于 Kubernetes 服务的自动集群 |

详细说明请参阅：[创建与管理集群](./create-cluster.md)。

### 网络分区自动修复

EMQX 提供网络分区自动修复功能，在发生网络分区时可自动恢复集群，无需人工干预，适用于对高可用性有严格要求的关键业务场景。

该功能由 `cluster.autoheal` 参数控制，默认启用：

```bash
cluster.autoheal = true
```

启用后，EMQX 会持续监测集群内节点之间的连接状态。当检测到网络分区时，会隔离受影响的节点，并继续使用剩余节点对外提供服务。一旦网络恢复，系统会自动将被隔离的节点重新加入集群。

### 集群节点自动清理

EMQX 支持自动清理离线节点的功能，可在配置的时间间隔后自动将断开连接的节点移出集群，从而避免长期离线节点影响集群性能。

该功能默认启用，由 `cluster.autoclean` 参数控制，默认时间间隔为 24 小时：

```bash
cluster.autoclean = 24h
```

### 跨节点会话保持

在 EMQX 集群模式下，MQTT 客户端的持久会话支持跨节点保持与迁移，确保连接切换时会话状态不丢失。

启用方法如下：

- **MQTT 3.x 客户端**：设置 `clean_start = false`
- **MQTT 5.0 客户端**：设置 `clean_start = false` 且 `session_expiry_interval > 0`

启用后，当客户端断开连接时，EMQX 会保留其与 Client ID 关联的会话信息。客户端重新连接后，EMQX 会恢复之前的会话，投递断开期间积压的消息，并保留原有订阅。

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

这一机制确保客户端在连接节点变化时，仍能收到离线期间缓存的消息，并保持订阅状态不变。

### 网络要求

为确保集群性能，EMQX 要求集群节点之间的网络延迟低于 10 毫秒；若延迟超过 100 毫秒，集群可能无法正常运行。

建议将核心节点部署在同一私有网络中。在 Mria+RLOG 架构下，也推荐将副本节点部署在同一私有网络内。

## 下一步：创建 EMQX 集群

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

- [部署架构与集群要求](./mria-introduction.md)
- [创建集群](./create-cluster.md)
- [集群安全](./security.md)
