3. EMQX集群发现与组建机制

EMQX 集群的组建分为两个阶段:节点发现与集群建立。节点发现解决的是“新节点如何知道集群里还有哪些节点”的问题;集群建立解决的是“知道彼此之后,如何形成一致的 Erlang 分布式集群并同步路由与订阅关系”的问题。EMQX 支持多种发现方式,包括手动发现(manual)、静态节点列表(static)、DNS 发现(dns)、etcd 发现、K8s 发现等。不同方式适用于不同的部署环境,选择错误会导致节点无法加入集群、集群分裂或节点重复加入等问题。

一、集群发现的基本原理

EMQX 基于 Erlang/OTP 的分布式能力构建集群。每个 EMQX 节点启动时会生成一个节点名,格式为 name@host,例如 emqx@10.0.0.11。节点之间通过 Erlang 分布式协议(默认端口 4370,或使用 TLS 时另配端口)建立连接。集群发现机制的核心任务,就是让每个节点在启动时获得一份“候选节点列表”,然后尝试与列表中的节点建立连接,最终形成一个全互联的集群。

EMQX 的发现策略由 cluster.discovery_strategy 配置项决定,可选值包括 manual、static、dns、etcd、k8s 等。无论采用哪种策略,最终都会把发现的节点交给集群管理模块,由它完成节点连接、路由表同步和订阅关系同步。

EMQX 集群发现与组建流程 发现策略层(Discovery Strategy) manual 手动加入 emqx_ctl cluster join static 静态节点列表 cluster.static.seeds dns DNS 发现 A / SRV 记录 etcd etcd 发现 键值注册 k8s Kubernetes 发现 API Server 查询 候选节点列表(Candidate Node List) emqx@10.0.0.11 | emqx@10.0.0.12 | emqx@10.0.0.13 集群管理模块(Cluster Manager) 节点连接建立 路由表同步 订阅关系同步 集群状态一致 集群组建完成 所有节点全互联,路由与订阅信息一致 失败场景 端口不通 / 节点名冲突 失败场景 发现列表为空 / 网络分区

二、手动发现(manual)

手动发现是最基础的方式。集群启动时不会自动寻找其他节点,需要运维人员通过命令行或 API 手动将节点加入集群。典型操作如下:

# 在节点 emqx@10.0.0.12 上执行,将其加入 emqx@10.0.0.11 所在的集群
emqx_ctl cluster join emqx@10.0.0.11

# 查看集群状态
emqx_ctl cluster status

手动发现适合节点数量少、部署频率低、需要精确控制集群成员关系的场景,例如测试环境或小规模生产环境。它的缺点是每次扩容都需要人工介入,节点重启后不会自动重新加入集群,需要配合自动化脚本使用。

三、静态节点列表(static)

静态节点列表方式通过配置文件预先定义集群中所有节点的地址。每个节点启动时读取这份列表,并尝试与列表中的其他节点建立连接。配置示例如下:

cluster {
  discovery_strategy = static
  static {
    seeds = ["emqx@10.0.0.11", "emqx@10.0.0.12", "emqx@10.0.0.13"]
  }
}

静态列表的优点是配置简单、不依赖外部服务,节点重启后能自动重新加入集群。缺点是节点列表固定,扩容时需要修改所有节点的配置并重启,不适合节点数量频繁变化的场景。它适用于节点数量固定、IP 地址稳定的中小规模集群。

四、DNS 发现(dns)

DNS 发现方式利用 DNS 记录动态获取节点列表。EMQX 支持两种 DNS 记录类型:A 记录和 SRV 记录。使用 A 记录时,所有节点共享同一个域名,DNS 返回多个 IP 地址;使用 SRV 记录时,可以指定每个节点的端口和优先级。配置示例如下:

cluster {
  discovery_strategy = dns
  dns {
    name = "emqx.cluster.local"
    record_type = a
  }
}

DNS 发现的优势在于节点列表由 DNS 动态维护,扩容时只需更新 DNS 记录,无需修改 EMQX 配置。它适合节点数量较多、IP 地址可能变化的场景,例如云环境或容器化部署。需要注意的是,DNS 解析结果有缓存时间(TTL),节点发现存在一定延迟;同时要确保 DNS 服务本身的高可用,避免 DNS 故障导致集群无法组建。

五、etcd 发现

etcd 发现方式利用 etcd 作为服务注册中心。每个 EMQX 节点启动时将自己的节点信息写入 etcd 的指定路径,同时监听该路径下的其他节点信息,从而实现动态发现。配置示例如下:

cluster {
  discovery_strategy = etcd
  etcd {
    server = "http://10.0.0.100:2379"
    prefix = "emqx/cluster"
    node_ttl = 60s
  }
}

etcd 发现适合已经使用 etcd 作为基础设施的云原生环境。它的优点是节点信息动态更新、支持 TTL 自动清理失效节点、集群规模可弹性伸缩。缺点是需要额外维护 etcd 集群,增加了运维复杂度。

六、Kubernetes 发现(k8s)

在 Kubernetes 环境中,EMQX 通常以 StatefulSet 或 Deployment 方式部署。K8s 发现方式通过调用 Kubernetes API Server 查询指定命名空间下符合标签选择器的 Pod 列表,从而获取集群节点信息。配置示例如下:

cluster {
  discovery_strategy = k8s
  k8s {
    apiserver = "https://kubernetes.default.svc:443"
    service_name = "emqx-headless"
    namespace = "emqx"
    address_type = ip
    app_name = "emqx"
  }
}

K8s 发现是 EMQX 在 Kubernetes 上部署时的推荐方式。它直接利用 K8s 的服务发现能力,无需额外组件,节点扩缩容时自动感知。使用时需要为 EMQX Pod 配置相应的 ServiceAccount 和 RBAC 权限,允许其读取 Pod 列表。配合 Headless Service,每个 Pod 可以获得稳定的 DNS 名称,便于节点间通信。

七、发现方式对比与选型建议

发现方式 依赖组件 动态扩容 适用场景
manual 无 需人工介入 测试环境、小规模固定集群
static 无 需改配置重启 节点数量固定、IP 稳定的集群
dns DNS 服务 支持,有 TTL 延迟 云环境、容器化部署
etcd etcd 集群 支持,实时性高 已有 etcd 基础设施的云原生环境
k8s K8s API Server 支持,自动感知 Kubernetes 环境
选型时优先考虑部署环境:Kubernetes 环境首选 k8s 发现;已有 etcd 的云原生环境可选 etcd;传统虚拟机或物理机环境,节点数量少且固定时用 static,节点数量多或 IP 不固定时用 dns;仅用于测试或临时验证时用 manual。

八、集群组建的关键检查点

无论采用哪种发现方式,集群组建成功后都应检查以下内容:

  • 节点连通性:确认所有节点之间的 Erlang 分布式端口(默认 4370)互通,防火墙和安全组规则已放行。
  • 节点名唯一性:每个节点的 node.name 必须唯一,否则会出现节点名冲突导致加入失败。
  • Cookie 一致性:所有节点的 node.cookie 必须相同,否则 Erlang 节点之间无法建立信任关系。
  • 集群状态:执行 emqx_ctl cluster status,确认所有节点均处于 running 状态,且节点列表完整。
  • 路由与订阅同步:在任意节点发布消息,确认其他节点上的订阅者能正常收到,验证路由表已同步。
如果发现节点无法加入集群,优先检查 Erlang Cookie 是否一致、节点名是否冲突、分布式端口是否被防火墙拦截。DNS 和 etcd 方式还需检查解析结果或注册路径是否正确。