尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

EMQX部署MQTT服务器实战:从Docker到生产环境

EMQX部署MQTT服务器实战:从Docker到生产环境 先说一句题外话你搜“使用emqx部署mtqq服务器”的时候八成是把MQTT这几个字母打反了。这种手滑我太熟悉了自己当年第一次查资料的时候也干过同样的事。但不管你是刚接触MQTT的小白还是已经在设备端写了好一阵子固件的工程师只要开始正经搞物联网数据链路迟早会撞上同一个问题消息服务端到底应该怎么搭我之前在好几个项目里折腾过EMQX有自己玩的内网环境也有真正接了几百台设备的半生产场景。从最早的4.x版本一路用到现在5.x踩过的坑、翻过的车、查过的源码和文档足够写一本小册子了。这篇博客就围绕“EMQX部署MQTT服务器”这件事从协议原理、环境准备、Docker部署、认证安全、客户端联调到生产排错一层一层给你捋清楚。不管你是打算在本地跑一个开发实例还是想部署一套能承载真实设备的服务端都能从中找到可以参考的东西。整个部署流程其实并不复杂真正容易出问题的往往是配置细节和认知偏差。比如很多人以为MQTT服务器启动起来就能直接接设备结果认证没开、端口没放通、心跳参数不匹配设备一上线就掉线。下面我按自己实际操作的习惯把一个完整流程拆给你看。1. 为什么选EMQXMQTT服务端选型背后的门道1.1 MQTT协议到底解决什么问题MQTT全称是Message Queuing Telemetry Transport翻译过来叫消息队列遥测传输。它和HTTP这类请求响应协议最大的区别在于它是一个基于发布订阅模型的轻量级消息协议。发布者把消息发到一个主题上订阅者只要订阅了这个主题就能收到消息两边完全解耦。打个比方HTTP就像你打电话必须两个人都在线才能说话MQTT更像小区里的布告栏有人往布告栏贴通知你什么时候路过都可以看甚至你可以只挑自己关心的那个板块看。这个模型对物联网场景太合适了因为设备端的网络往往不稳定、带宽也有限不可能要求每个设备都保持一个稳定的长连接去轮询服务器。MQTT协议本身在传输层就做了很多优化固定报文头最小只需要2个字节加上心跳保活机制让低功耗设备也能轻松扛住连接维护。说回MQTT协议的工作原理整个体系里最核心的角色叫Broker也就是消息代理服务器它负责接收所有客户端的连接、处理订阅关系、转发消息。你搜“MQTT服务器”也好搜“mqtt部署”也好本质上都是在找谁来做这个Broker。现在市面上的选择并不少Mosquitto轻量但功能简单EMQX功能完整但资源占用更大RabbitMQ甚至也能开MQTT插件凑合用。关键在于你的业务规模和管理需求到底在哪个量级。1.2 EMQX在众多Broker里凭什么更值得选我第一次接触EMQX的时候项目需求很朴素设备端上报数据后端服务实时接收并发连接数大概也就几百。当时有人推荐Mosquitto觉得它轻量、资源占用低但用下来发现一个问题——设备规模一旦上来想要可视化的监控、灵活的认证体系、复杂的规则处理Mosquitto就得自己去拼一堆周边工具。EMQX的优势恰恰在于它把这些能力集成在了同一个产品里。具体来看EMQX有几个特点对实际部署非常友好。第一是原生支持MQTT 3.1/3.1.1和5.0协议还兼容MQTT over WebSocket这意味着浏览器端用MQTT.js也能直接接入不用自己做协议转换。第二是内置了完整的认证鉴权体系用户名密码、JWT、HTTP认证、LDAP等各种方式都有生产环境不用裸奔。第三是自带一个图形化Dashboard连接数、消息速率、主题订阅情况一眼就能看清楚排查问题的时候省太多事了。当然选EMQX还有一个比较实在的理由它在国内的技术社区非常活跃中文文档也比较全。真的遇到问题搜一下“emqx使用教程”或者直接在官网扒文档基本都能找到答案。这一点在项目交付期特别重要因为卡在一个冷门Broker的配置上半天查不到解决办法的体验真的会让人崩溃。2. 部署前的环境规划与镜像准备2.1 先搞清楚你的并发和消息量级很多人一上来就急着执行docker run结果跑到一半发现资源配置完全不合理。我个人的经验是动手部署前先花十分钟想清楚两件事你预计会有多少设备同时在线每秒钟大概会产生多少条消息。这两个数字直接决定了你该用什么样的部署形态。如果只是开发环境或内部小规模使用并发不超过几千连接单机部署完全够了用Docker跑一个容器最省事。如果是生产环境设备规模上万甚至更多那就要考虑多节点集群、负载均衡和持久化方案了。EMQX单机的性能其实比很多人想象中强得多在普通云服务器上跑几万连接并非难事但消息代理这种服务最怕的是单点故障集群才是生产环境的常态。需要说明的是本文主要演示单机Docker部署因为这是复杂度最低、最适合快速上手的路径。单机跑通了理解了配置文件的组织方式和各端口的作用后面再上集群就是复制节点的事。2.2 系统环境与端口规划EMQX官方支持Linux、Windows、macOS甚至你可以在Windows上直接下载压缩包运行。不过我强烈建议服务端跑在Linux环境最好是用Docker部署。原因很简单Docker屏蔽了宿主系统的差异所有依赖、配置、运行环境都封装在镜像里换一台机器也能保证行为一致。部署之前先规划好端口。EMQX默认会用到的端口有下面几个我整理成一张表方便你对照端口协议/用途说明1883MQTT over TCP设备端最常用的接入端口8883MQTT over TLS/SSL加密接入端口生产环境建议开启8083MQTT over WebSocket浏览器端接入端口8084MQTT over WSSWebSocket加密版18083Dashboard图形化管理后台8081HTTP API通过REST API管理集群和客户端这里要特别提醒一点1883和18083是默认端口如果宿主机本身有别的服务占用了Docker映射时需要改成其他宿主机端口。比如宿主机18083已被占用可以映射成28083:18083访问Dashboard时用28083。千万不要图省事直接把EMQX的默认端口覆盖掉因为镜像内部的端口是写死的映射前先确认清楚。2.3 下载镜像的两种方式拉取EMQX镜像很直接docker pull emqx/emqx:5.6.0也可以直接不用手动pull写docker-compose.yml后执行docker compose up -dCompose会自动拉取。版本号我建议别用latest生产环境最忌讳隐性变更——今天拉到的latest和一个月后的latest可能就是两个东西。锁定具体版本比如5.6.0或5.7.2才方便后续统一升级。如果你的服务器在离线内网环境那就需要在一台能访问外网的机器上先pull镜像然后docker save导出tar包拷到目标机器上再用docker load导入。这个方法在工业现场很常用很多工厂机房不允许部署机器直接访问外网离线部署往往是唯一选项。3. 使用Docker Compose快速部署EMQX从单机到可持久化3.1 编辑docker-compose.yml直接跑docker run当然可以但我个人更推荐用Docker Compose尤其是当你开始考虑数据持久化时。Compose文件相当于把部署参数固化成了一个可版本管理的文本换机器、恢复环境都非常方便。我一般会在项目目录下建一个emqx目录然后在里面创建docker-compose.yml。一个最基础但已经考虑了持久化的Compose文件长这样version: 3.8 services: emqx: image: emqx/emqx:5.6.0 container_name: emqx restart: unless-stopped ports: - 1883:1883 - 8083:8083 - 8081:8081 - 18083:18083 environment: EMQX_NODE__COOKIE: mysupersecretcookie EMQX_DASHBOARD__DEFAULT_PASSWORD: admin volumes: - emqx_data:/opt/emqx/data - emqx_log:/opt/emqx/log volumes: emqx_data: driver: local emqx_log: driver: local这里面有几个细节值得展开说。EMQX_NODE__COOKIE是Erlang节点的通讯密钥后面如果你要扩展成多节点集群所有节点的cookie必须一致否则节点之间无法互相通信。初始部署就把它固定下来能省掉以后很多麻烦。EMQX_DASHBOARD__DEFAULT_PASSWORD则是把Dashboard的默认密码从public改掉避免一开局就暴露安全风险。restart: unless-stopped的意思是容器崩溃或机器重启后只要不是手动停掉的Docker都会自动把它拉起来。对于服务端程序来说这个配置几乎是必须的省得你半夜被设备离线告警叫醒。还要注意环境变量里双下划线的写法这是EMQX 5.x映射配置项的规则。比如EMQX_DASHBOARD__DEFAULT_PASSWORD就是配置文件中dashboard.default_password这一项的环境变量化写法。如果你需要改动其他配置比如监听buffer大小、日志级别都可以按这个规则去环境变量里找。3.2 初始化核心配置Compose文件准备好了之后启动服务docker compose up -d然后查看日志确认EMQX是否正常启动docker compose logs -f emqx看到类似EMQX 5.6.0 is running now的日志说明启动成功了。接着可以用curl简单探活curl http://localhost:18083/status如果返回{status:ok}那服务基本就没问题。此时打开浏览器访问http://服务器IP:18083用admin和你自己设置的密码登录Dashboard就能看到连接数、消息速率等实时指标。这里要注意Dashboard监听的是0.0.0.0地址意味着如果有公网或局域网访问权限的人也能通过18083端口访问你的管理后台。所以部署完成后第一件事要么把18083端口放到内网或防火墙白名单里要么至少把默认密码改掉。每次看到有人把EMQX裸奔在公网上还保持着admin/public密码我都替他捏一把汗。3.3 生产环境建议的持久化配置上面的Compose已经挂了emqx_data和emqx_log两个数据卷用来保存消息数据、配置和日志。这样即使容器删掉重建数据也不会丢。但如果你要跑真正的生产环境光有数据卷还不够还需要考虑这几个方向。第一个是备份策略。EMQX的数据目录里主要是内置数据库、会话状态、规则引擎数据和配置文件你要定期把数据卷的内容做快照或备份。最简单的做法是用docker run --rm -v emqx_data:/opt/emqx/data -v $(pwd):/backup alpine tar czf /backup/emqx_data.tar.gz -C /opt/emqx/data .把数据打包出来。第二个是日志处理。默认配置下日志是写到容器内的/opt/emqx/log通过数据卷持久化。如果项目规模大建议把日志接入ELK或者Loki这类集中日志系统方便检索历史日志。小黑板上的原始日志翻起来太痛苦了这个坑我踩过不止一次。第三个是集群扩展。单机节点处理能力不够时可以在Compose里再加一个服务或者用独立节点加入集群。需要保证的是节点名唯一、cookie一致并且提前规划好集群的拓扑。4. 认证、权限与TLS安全加固别做裸奔的Broker4.1 启用内置用户名密码认证EMQX默认是允许匿名访问的也就是说任何客户端只要知道你的IP和端口就能随意连接和订阅所有主题。这在局域网开发阶段很爽但生产环境就是灾难。我见过太多人把设备接入服务部署在公网服务器上连认证都没开结果被不明来源的客户端连上来疯狂收发消息白白消耗流量和CPU。最常用的认证方式是内置数据库认证也就是在EMQX里维护一套用户名和密码设备连接时校验。在EMQX 5.x里可以通过Dashboard来配置登录后台进入“访问控制”-“认证”选择“密码认证”后端选“内置数据库”然后添加用户。操作非常直观这里不再赘述点击路径。如果你喜欢用配置文件的方式管理可以在emqx.conf里对应位置加上认证数据或者在docker-compose中通过挂载文件把认证配置放进去。更推荐用Dashboard或HTTP API管理认证因为可以动态增删用户不需要重启服务。我顺手贴一个用HTTP API创建认证用户的示例注意替换你的管理员账号密码和实际请求地址curl -X POST http://localhost:18083/api/v5/authentication \ -u admin:你的密码 \ -H Content-Type: application/json \ -d { mechanism: password_based, backend: built_in_database, user_id_type: username, bootstrap: [ { user_id: device_001, password: 设备密码 } ] }API方式适合批量创建用户比如你有上千台设备总不能一台一台在页面上点。写个脚本循环调用接口效率高得多。当然不同小版本的API结构会有细微差异遇到404或者格式错误优先查对应版本的官方API文档。4.2 配置TLS单向认证光有用户名密码还不够因为密码在网络上传输时默认是明文只要有人抓包账号密码就暴露了。真要承载业务数据强烈建议开启TLS加密。用自签名证书做演示的话先生成证书openssl req -x509 -newkey rsa:2048 -nodes -keyout emqx.key -out emqx.crt -days 365 -subj /CNlocalhost然后把证书文件放到certs目录在docker-compose里挂载进去并开放8883端口ports: - 1883:1883 - 8883:8883 - 8083:8083 - 8081:8081 - 18083:18083 volumes: - ./certs:/opt/emqx/certs:ro - emqx_data:/opt/emqx/data - emqx_log:/opt/emqx/log environment: EMQX_LISTENERS__SSL__DEFAULT__BIND: 0.0.0.0:8883 EMQX_LISTENERS__SSL__DEFAULT__SSL__KEYFILE: /opt/emqx/certs/emqx.key EMQX_LISTENERS__SSL__DEFAULT__SSL__CERTFILE: /opt/emqx/certs/emqx.crt重启服务后客户端连接端口从1883改成8883启用TLS即可。要注意的是自签名证书客户端需要信任生产环境最好用正规CA签发的证书或者用企业内部CA并提前把根证书下发到设备端。4.3 WebSocket接入与跨域策略很多Web项目是通过MQTT over WebSocket接入EMQX的比如Vue3前端用MQTT.js走的往往是8083或8084端口。如果前端站点和后端Broker域名不是同一个浏览器跨域策略会拦下连接。常见做法是把WebSocket接入放到Nginx反向代理后面通过路径区分不同服务同时统一管理跨域头。我自己常用的Nginx配置片段类似这样location /mqtt { proxy_pass http://127.0.0.1:8083; proxy_http_version 1.1; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection upgrade; proxy_set_header Host $host; }这样Web端就可以连接ws://你的域名/mqtt而不是直接暴露EMQX端口。同时Nginx层面能做访问控制、流量限制比直接把8083暴露出去安全得多。之前在项目里遇到前端连不上MQTT排查了半天发现是Nginx没配WebSocket升级头Connection: upgrade没带上浏览器端一直握手失败。这个问题报错信息还不太直观很容易让人怀疑是EMQX配置问题。5. 客户端接入与消息链路验证用MQTTX和命令行把流程跑通5.1 用MQTTX做最简单的发布订阅服务端部署完成、认证也开了接下来就是验证消息链路。最省事的客户端是MQTTX跨平台桌面应用界面清爽非常适合联调。下载安装后新建连接填上Broker地址、端口、用户名密码点连接即可。连接成功后订阅一个测试主题比如test/topic。然后在另一个连接会话里往同一个主题发布一条消息比如hello from emqx。正常情况下订阅端应该立刻收到这条消息。这里要注意一点MQTT的订阅和发布动作由Broker转发和客户端在不在同一台机器没关系所以你可以分别从两个不同的设备连接测试验证网络链路是否通。MQTTX还支持模拟多个客户端连接方便你测试并发场景。但要注意免费版对单机并发客户端数量有限制真要大压力测试还是得用专业工具或者脚本。5.2 命令行压测与自动验证MQTTX适合人肉验证但要写自动化测试或者批量模拟设备命令行才是正路。EMQX镜像自带的emqx ctl命令可以查看客户端列表、订阅关系、主题统计等信息。进入容器执行docker exec -it emqx emqx ctl broker stats可以看到当前Broker的消息收发统计、连接数、订阅数等状态。如果你希望做发布订阅的自动化验证可以用Python的paho-mqtt库写一个小脚本。下面这个例子同时实现了订阅和发布适合作为链路自检工具import paho.mqtt.client as mqtt import time BROKER localhost PORT 1883 USERNAME device_001 PASSWORD 你的密码 def on_connect(client, userdata, flags, rc): print(connected, rc:, rc) client.subscribe(test/topic) def on_message(client, userdata, msg): print(received:, msg.topic, msg.payload.decode()) client mqtt.Client() client.username_pw_set(USERNAME, PASSWORD) client.on_connect on_connect client.on_message on_message client.connect(BROKER, PORT, 60) client.loop_start() client.publish(test/topic, hello from emqx) time.sleep(3) client.loop_stop()把脚本放在服务器上执行后如果能在控制台看到自己发布的hello from emqx被订阅回调打印出来说明整条链路从设备到Broker再到订阅方都是通的。这个自检脚本我每次部署完都会跑一遍比打开图形化客户端黑盒验证要放心得多。5.3 从设备端到服务端的联动排查实际项目里设备端往往是STM32、ESP32、4G模块这类资源受限的硬件它们可能使用MQTT over TCP也可能走TLS加密。比如很多工控项目会用4G模块直接连EMQX模块里配置好Broker地址、端口、设备ID和密码即可。另一个常见场景是Node-RED把OPC UA的工业数据转换成MQTT后上报EMQX作为汇聚节点后端再用规则引擎或者订阅方把数据落到数据库。在做设备联调时我习惯先在服务器上用MQTTX或Python脚本模拟一个“标准客户端”确认Broker侧没问题再去查设备侧的配置。很多所谓“设备连不上服务器”的问题最后定位下来都是设备端填的端口不对、用户名密码有空格或者设备固件里的TLS证书没有装全。先隔离变量永远是排查链路问题最快的路径。6. 常见问题排查与生产避坑记录6.1 连接不上或者频繁掉线现象可能原因排查方法客户端连接超时防火墙/安全组未放通1883端口在宿主机执行telnet 服务器IP 1883测试连通性连接被拒绝rc5认证失败检查用户名密码、是否启用了认证插件连接后立刻断开KeepAlive参数不匹配设备端心跳间隔设置过长或过短改为30~60秒偶尔掉线重连客户端和Broker网络链路不稳定查看Dashboard“连接日志”确认是否是NAT超时导致连接不上时先从网络层、认证层、配置层三个角度逐层排查。网络层用curl或telnet探测端口是否可达认证层看日志里有没有auth failure关键字配置层看监听器是否真的绑定在了期望的网卡和端口上。记住不要一上来就怀疑EMQX本身它自带日志非常详细认真看日志能解决80%的问题。6.2 订阅收不到消息的几种可能订阅收不到消息是新手最容易困惑的问题但原因往往很简单。最常见的是主题拼写不一致发布端发到device/001/data订阅端订阅的是device/001/#这是能收到的但如果订阅的是device/001那肯定收不到因为MQTT没有“父子主题自动匹配”的规则device/001和device/001/data是两个完全不同的主题。另一个容易忽略的是QoS等级。如果发布端QoS是0消息发出后Broker尽力转发网络抖动时消息可能会丢而且不会重试。对重要数据建议发布和订阅至少使用QoS 1同时订阅端注意清理会话和保留会话的影响。默认情况下客户端断开连接后离线期间的消息不会保存除非用到了MQTT 5.0的Session Expiry或者QoS 1 持久会话否则重新连上以后是收不到离线消息的。还有一个常见原因是客户端订阅时机晚于发布时机。脚本里如果直接写完client.publish就退出进程消息可能还没到达Broker进程就已经结束了。发布订阅操作之后一定要保留一点时间让事件循环跑起来这就是上面Python脚本里要time.sleep(3)的原因。6.3 调优心得与监控建议部署和验证都通过之后接下来就是持续稳定运行。对于EMQX而言有几点调优经验值得分享。第一容器不要过度限制内存。Erlang虚拟机对内存的管理策略和普通程序不太一样它倾向于占据更多内存作为缓存这是正常的。你要做的是设置合适的宿主内存上限比如--memory1g观察一段时间如果频繁出现内存超限重启再往上调整。第二生产环境建议开启规则引擎或者外部数据持久化把重要消息及时转存到数据库避免Broker内存被消息积压撑爆。EMQX的规则引擎可以很方便地把MQTT消息转存到MySQL、PostgreSQL、ClickHouse等存储这个功能非常实用。第三监控必不可少。至少把EMQX的Prometheus监控指标接入你的监控体系。EMQX在18083之外还提供了prometheus协议拉取指标的端口配合Grafana能做出很漂亮的监控面板。连接数、消息速率、订阅数、内存占用这几个指标做好了服务出问题时你能第一时间感知到而不是等设备大量告警才知道坏了。注意部署在云服务器上的时候不要只改EMQX配置云平台的安全组规则也要同步处理。我遇到过最典型的坑是EMQX配置完全正确1883端口也在监听但云服务器安全组没放行1883外部设备怎么都连不进来。这个问题在网络层排查时花了我一个下午。另外一个容易被忽略的细节是系统时间同步。MQTT的心跳机制依赖时间设备端和服务器端时间偏差过大会导致连接被误判为超时而断开。建议服务器上开启NTP时间同步设备端也要定期校时。很多排查到最后的“随机掉线”问题都跟系统时间漂移有关。最后再分享一个我自己的小习惯每次部署完EMQX第一件事不是急着接设备而是用命令行工具做一轮发布订阅自检再花两分钟检查Dashboard上是否显示认证生效、端口是否只开放了必要的几个。这套流程走完心里基本就有底了。后面哪怕真出了状况也能快速定位是设备侧的问题还是服务端的问题。EMQX本身是个很成熟的开源项目大部分问题都有迹可循细心一点总能找到根源。
返回列表