文档

运行和配置代理服务器所需的一切都在这里。控制台内置的帮助与这些章节一一对应,并已翻译成七种语言。

快速上手

安装之后,代理服务器在三个端口上监听::1883 提供 TCP 上的 MQTT,:8883 提供 TLS 上的 MQTT,:8567 提供 Web 控制台以及 /mqtt 路径下 WebSocket 上的 MQTT。

打开 http://localhost:8567 并以管理员身份登录。默认账号是 admin / admin;首次登录后请立即在「个人设置」里更改密码。

控制台的管理员不是 MQTT 账号。设备要用「用户」页面里创建的独立账号连接。

创建第一个用户:名称、至少八位的密码,以及一条 ACL 规则。规则 $u/# 会让设备对自己的分支拥有完全访问权——控制台里就有一个「自己的分支」按钮专门用于此。

mosquitto_pub -h localhost -p 1883 -u sensor-42 -P s3nsorPass! \
  -t 'sensor-42/temp' -m '21.5'

mosquitto_sub -h localhost -p 1883 -u sensor-42 -P s3nsorPass! \
  -t 'sensor-42/#' -v

在 Linux 上安装

推荐走 APT 仓库:更新会随系统更新一起到来。

sudo wget -qO /usr/share/keyrings/elx-repo.gpg https://repo.um-d.ru/elx-repo.gpg
echo "deb [signed-by=/usr/share/keyrings/elx-repo.gpg] https://repo.um-d.ru stable main" | sudo tee /etc/apt/sources.list.d/elx-repo.list
sudo apt-get update
sudo apt-get install elxmqttbroker

软件包会把可执行文件装到 /opt/elxmqttbroker,创建一个不能登录的系统用户 elxmqtt,把数据和 config.json(权限 0600)放在旁边,并把 elxmqttbroker 服务设为开机启动。

systemctl status elxmqttbroker      # 状态
sudo systemctl restart elxmqttbroker # 重启
journalctl -u elxmqttbroker -f      # 日志

该单元用 ProtectSystem=strict 加了约束:只有代理服务器自己的目录可写。默认情况下控制台只在 127.0.0.1:8567 监听并经由 nginx 发布,而 MQTT 的 1883 和 8883 直接对外——那是另一套协议,不需要反向代理。

nginx 的配置必须转发 UpgradeConnection 两个头,并把 proxy_read_timeout 设得宽裕些:/ws/live/mqtt 都是长时间保持的 WebSocket 连接。

在 Windows 上安装

通常使用安装程序 elxmqttbroker-setup.exe:它把程序放进 Program Files,把数据放进 C:\ProgramData\ELX MQTT Broker,可选地注册 ELXMQTTBroker 服务随系统启动,并在防火墙中放行 1883、8883 和 8567 端口。界面为俄语和英语,需要管理员权限。

设置放在 ProgramData 而不是程序旁边:代理服务器会从控制台改写 config.json,而服务不应该往 Program Files 里写东西。卸载时该文件夹会保留——里面存着用户、他们的权限和消息。

便携方式是单个 elxmqttbroker.exe:放进单独的文件夹运行,旁边就会出现 config.jsondata 目录和一张自签名 TLS 证书。

elxmqttbroker.exe                    # 用默认设置运行
elxmqttbroker.exe -web :9000         # 让控制台监听别的端口
elxmqttbroker.exe -version           # 版本与构建信息

便携版也可以手工当作服务来管理,用的是与安装程序相同的命令:

elxmqttbroker.exe -service install -config "C:\ProgramData\ELX MQTT Broker\config.json" -data "C:\ProgramData\ELX MQTT Broker\data"
elxmqttbroker.exe -service start
elxmqttbroker.exe -service stop
elxmqttbroker.exe -service uninstall

配置

config.json 是代理服务器唯一读取的文件。它在首次启动时生成,只保存启动前必须知道的内容:地址、TLS 和管理员账号。

{
  "dataDir": "data",
  "auth": { "username": "admin", "password": "admin" },
  "mqtt": { "tcp": ":1883", "tls": ":8883" },
  "web": { "addr": ":8567", "tlsAddr": "" },
  "tls": { "cert": "", "key": "", "selfSigned": true,
            "hosts": ["localhost", "127.0.0.1", "::1"] },
  "logLevel": "info",
  "logFormat": "text",
  "persistence": true
}

明文的 password 会在首次启动时变成 passwordHash 并从文件中抹去。其余一切——协议上限、SMTP、桥接、规则、API 令牌、MQTT 用户——都在控制台里配置并保存在 data/users.json 中;这些都不需要重启。

命令行选项的优先级高于配置文件:

选项作用
-configconfig.json 的路径
-data数据目录
-mqttTCP 上 MQTT 的监听地址
-mqtt-tlsTLS 上 MQTT 的监听地址
-webWeb 控制台的监听地址
-log日志级别
-version打印版本并退出
config.json 里有管理员密码的哈希、SMTP 密码以及外部认证来源的凭据。该文件的权限应设为 0600。

用户与权限

这里有两套体系,切勿混淆。管理员是在 config.json 中定义的唯一账号,只管理控制台:没有角色划分,没有注册,进去之后就拥有全部权限。MQTT 设备是另一份账号列表:1 到 64 个字符的名称、密码、启用开关以及主题权限。

一条 ACL 规则由主题过滤器、访问方式和判定三部分组成:

字段取值
过滤器普通的 MQTT 过滤器:sensors/+/tempdev/#$u/#
访问方式readwritereadwrite
判定允许或拒绝

规则自上而下检查,命中第一条即生效,并且默认一律拒绝:没有明确的允许就没有访问权。顺序也是配置的一部分,因此规则可以用鼠标拖动,也可以用方向键移动。

过滤器中的 $u 会展开为发起连接的客户端名称。仅 $u/# 一条规则就能把每台设备锁在自己的分支里,免去按名字逐条编写。

密码以 PBKDF2-HMAC-SHA256 存储,迭代 21 万次并加随机盐。会话令牌和 API 令牌同样只保存哈希。

MQTT 认证

凭据来源在「设置 → 认证」里选择,同一时间只有一个生效。切换在运行中完成:先用「测试」按钮验证新来源,只有它有响应,代理服务器才会切换,并断开客户端让它们重新认证。如果该来源不可达,则什么都不会改变。

来源适用场合
internal账号在控制台里管理——默认选项
mysql / mariadb设备已经存在于你系统的数据库里
sqlite同上,只不过数据库是一个文件
csv简单的名单;文件变化时会重新载入
jwt设备用签名令牌代替密码
http每次连接都由你的服务来裁决

MySQL 和 MariaDB 的表结构沿用 EMQX:

CREATE TABLE mqtt_user (username VARCHAR(128) PRIMARY KEY, password VARCHAR(255));
CREATE TABLE mqtt_acl  (username VARCHAR(128), action VARCHAR(16),
                        permission VARCHAR(16), topic VARCHAR(255));
-- action: publish | subscribe | pubsub;  permission: allow | deny
INSERT INTO mqtt_user VALUES ('dev', SHA2('devpass',256));
INSERT INTO mqtt_acl  VALUES ('dev','pubsub','allow','dev/#');
"mqttAuth": {
  "backend": "mariadb",
  "sql": {
    "dsn": "mqtt:mqttpass@tcp(127.0.0.1:3306)/mqtt",
    "passwordQuery": "SELECT password FROM mqtt_user WHERE username = ${username} LIMIT 1",
    "aclQuery": "SELECT action, permission, topic FROM mqtt_acl WHERE username = ${username}",
    "passwordHash": "sha256"
  }
}

${username}${clientid} 是作为查询参数绑定的,而不是字符串拼接,因此不可能发生注入。passwordHash 支持 plainsha256sha512bcryptpbkdf2

JWT 放在密码或用户名字段里。支持 HS256/384/512 和 RS256/384/512;会校验签名、expnbf,也可以选择校验 issaud 以及你自定义的任何声明。权限取自 acl 声明,两种 EMQX 格式都可以,而 is_superuser 会完全免去检查。

签名算法只从配置中读取,绝不取自令牌头部:用 "alg": "none" 伪造的令牌通不过。

HTTP 来源会收到 {username, password, clientid, peerhost},并以 {"result": "allow", "acl": […]} 回应。空的 200 视为允许;deny、401、403、超时以及任何错误都视为拒绝——一个连不上的授权服务不该变成一扇敞开的门。响应会缓存 cacheSeconds 秒。

规则:重写、自动订阅、限流

主题重写会在运行中替换主题名称。一条规则由用于低成本初筛的 MQTT 过滤器、一个正则表达式,以及带 $1…$9 分组的目标模板组成:

何时过滤器正则表达式新主题
始终raw/#^raw/(.+)$cooked/$1

顺序与 EMQX 一致:先重写,后检查 ACL——权限是针对消息实际流经的主题来校验的。UNSUBSCRIBESUBSCRIBE 走同一套重写,否则客户端将永远无法取消订阅。

自动订阅会在连接建立后立即创建订阅,客户端无需发送 SUBSCRIBE。一条 %c/#%u/# 的规则,就能让每个客户端拥有自己的分支,完全不必手工配置。

主题限流适用于客户端整体表现正常,只是某个特定主题来得太频繁的情况:

字段作用
主题模式普通的 MQTT 过滤器:sensors/+/temp
动作稀释到指定频率,或整条丢弃
每秒消息数目标频率;允许小数(0.2 表示每五秒一条)
统计维度按客户端与主题,或按主题整体
稀释会保留最新的值:来得太早的消息会被暂留,下一条把它替换掉,到下一个节拍时发出的是最新鲜的那条。发布方始终会收到正常的确认——否则它会认为投递失败并把一切重发一遍。

洪泛控制在「设置 → 代理服务器」里配置,按连接生效:每秒消息数、消息突发量、每秒字节数、字节突发量。0 表示不限。超出限制不会断开连接:代理服务器会在下一次读取套接字前停顿,TCP 关闭窗口,发送方自己就慢下来了。

延迟发布

$delayed/{秒数}/{主题} 发布的消息会被代理服务器暂存,到期后再交给订阅者。格式与 EMQX 兼容。

mosquitto_pub -t '$delayed/60/sensors/alarm' -m '一分钟后'

间隔从 1 秒到 4 294 967(约 49 天)。确认会立即返回:代理服务器担保的是收到,而不是存在订阅者。分辨率为 1 秒。

队列可在「规则 → 延迟消息」中查看:主题、载荷、倒计时,并可逐条或一次性全部取消。消息可跨重启保留,代理服务器停机期间到期的消息会在启动后立刻发出。

代理之间的桥接

代理服务器可以作为客户端连接到另一台 MQTT 代理服务器,并双向转发主题。配置位于「设置 → 桥接」:远端地址(端口可省略——默认 1883,TLS 下为 8883)、账号、TLS、协议版本和规则。

规则字段含义
过滤器转发哪些主题
方向outinboth
QoS跨桥接的投递质量
前缀在各自一侧添加什么前缀

具备回环保护、自动重连、版本 5 下的 No Local(没有回声也没有重复),控制台里还有状态和计数。桥接工作在代理服务器层面,而不是作为一个单独的客户端。

桥接支持 QoS 0 和 1;QoS 2 会降级为 1,QoS 1 的投递采取尽力而为的方式。

REST API

控制台用会话 Cookie 调用自己的 API;外部系统则使用「设置 → API」里的 Bearer 令牌。令牌只显示一次,保存的只有它的哈希。令牌也可以只读发放——那样就只有 GETHEADOPTIONS 能通过。

curl -H "Authorization: Bearer TOKEN" http://localhost:8567/api/stats

典型场景是:你的系统开通一台设备,同时给它授予访问代理服务器的权限。

# 创建
curl -X POST http://localhost:8567/api/users \
  -H "Authorization: Bearer TOKEN" -H "Content-Type: application/json" \
  -d '{"username":"sensor-42","password":"s3nsorPass!","enabled":true,
       "acl":[{"filter":"$u/#","access":"readwrite","allow":true}]}'

# 修改密码或权限(只改所发送的字段)
curl -X PATCH http://localhost:8567/api/users/sensor-42 \
  -H "Authorization: Bearer TOKEN" -H "Content-Type: application/json" \
  -d '{"password":"newPass12345","enabled":false}'

# 删除
curl -X DELETE http://localhost:8567/api/users/sensor-42 \
  -H "Authorization: Bearer TOKEN"

密码永远不会被返回。错误统一以 {"error":{"code":"…","message":"…"}} 的形式给出:duplicatereservedinvalid_inputnot_foundread_onlyunauthorized

改动立即生效:修改密码、调整权限或执行删除时,代理服务器会自行切断该用户当前的连接,让新规则马上起作用,而不必等到下次重连。

这套 API 管理的是内置用户列表。如果选择了外部来源,账号就在那边,必须在来源本身中创建。

控制台的整套 API 都可用:统计与历史、客户端、订阅、保留消息、发布、事件、用户、设置、桥接、规则、延迟消息、导入与导出。

存储与重启

persistence: true 时,状态保存在 data/broker.db 中——WAL 模式的 SQLite,纯 Go 实现,不用 CGO。重启后仍然保留的是:

  • 保留消息——一有变化就立即写入,而不是按定时器。超过消息过期时间的会在启动时清理。
  • 持久会话——订阅、QoS 1/2 的离线队列以及未完成的握手。每十秒以及关闭时保存一次。清理会话不保存:按规范,它们随连接一起消失。
  • 延迟消息——代理服务器停机期间到期的,会在启动后立刻发出。

启动时日志里会出现类似 state restored retained=128 sessions=7 subscriptions=34 delayed=2 的一行。同在 data/ 目录下的还有 users.json(账号、规则和控制台设置,写入是原子的)以及自签名证书。

已知限制

  • 不支持集群:这款代理服务器按单进程设计。
  • 桥接支持 QoS 0 和 1;QoS 2 会降级为 1。
  • 协议中虽有 SCRAM 之类的增强认证,但内置来源并未提供,会返回 0x8C Bad authentication method
  • 频率限制按连接而非按用户生效:一个客户端开十条连接,就会得到十份额度。
  • 机器健康状况只在 Linux 和 Windows 上计算;在 macOS 和 BSD 上,该卡片会老实显示「无数据」。
代理服务器在负载下的表现另有一份报告:压力测试(最多 30,000 条连接,时延低至 73 µs,投递可达每秒 135 万条消息)。