webman 必须通过 workerman/mqtt 扩展在独立 process 中集成 mqtt,因 php-mqtt/client 阻塞 i/o、不兼容事件循环、无自动重连且易致断连/内存泄漏/消息丢失。

Webman 本身不内置 MQTT 客户端能力,必须通过 workerman/mqtt 扩展显式集成;直接调用 php-mqtt/client 或原生 mosquitto-php 扩展在 Webman 中会因生命周期和连接管理问题导致断连、内存泄漏或消息丢失。
为什么不能直接用 php-mqtt/client 在 Webman 进程里跑 MQTT
Webman 是基于 Workerman 的常驻进程框架,但它的 HTTP/WS Worker 和自定义 Process 是隔离的。常见错误是把 php-mqtt/client 实例写在控制器里——每次 HTTP 请求都新建连接,既无法复用又会快速耗尽 Broker 连接数。更严重的是,该库默认使用阻塞 I/O,在 Webman 的事件循环中会卡死整个 Worker 进程。
-
php-mqtt/client要求手动调用loop()或loopOnce(),而 Webman 的主循环由 Workerman 管理,二者冲突 - 它不兼容 Workerman 的
onConnect/onMessage回调风格,需自行封装定时器轮询,极易漏消息 - 无自动重连策略,Broker 重启后连接永久失效,除非你额外写守护逻辑
workerman/mqtt 怎么在 Webman 里正确启动
必须放在独立的 Process 中运行,不能挂载到 HTTP Worker 下。这是唯一稳定方式。
- 在
app/process目录下新建MqttProcess.php,继承support\Process -
onStart中创建\Workerman\Mqtt\Client实例,并设置reconnect_period(建议 5–10 秒) - 订阅逻辑写在
onConnect回调内,避免连接未建立就调用subscribe() - 收到消息后,不要直接操作数据库或调用阻塞函数;应投递到 Redis 队列或通过
Worker::sendToWorker()转给其他 Worker 处理
示例关键片段:
$mqtt = new \Workerman\Mqtt\Client('mqtt://broker.example.com:1883', [
'client_id' => 'webman-' . uniqid(),
'username' => config('mqtt.username'),
'password' => config('mqtt.password'),
'reconnect_period' => 8,
]);
$mqtt->onConnect = function ($mqtt) {
$mqtt->subscribe('device/+/status');
};
$mqtt->onMessage = function ($topic, $payload) {
// 转发到队列,不在此处做 heavy work
redis()->lPush('mqtt_incoming', json_encode(compact('topic', 'payload')));
};
主题订阅动态更新时怎么避免重复订阅或漏订
Webman 的 Process 默认只启动一次,设备列表变化后不会自动刷新订阅。硬编码 subscribe() 在 onConnect 里只生效首次连接。
- 不要在
onConnect里查 DB 订阅——DB 查询可能失败或超时,导致连接成功但未订阅 - 推荐方案:用
Worker::timer->add()每 30 秒轮询一次设备表,对比当前已订阅的client_id列表,差量调用$mqtt->subscribe()或$mqtt->unsubscribe() - 注意
subscribe()是异步的,需监听onSubscribe回调确认成功,否则下次轮询还会再发 - 若设备量大(>1000),避免单次订阅过多 topic,可分批 + 延迟执行,防止 Broker 拒绝连接
消息处理性能瓶颈常出在哪几个环节
真实 IoT 场景下,每秒百条以上报文时,onMessage 回调里的代码就是最大瓶颈点。
- JSON 解析(
json_decode($payload, true))在大数据量时 CPU 占用飙升,可考虑用json_decode($payload, false)+ 对象访问替代数组 - 直接写 MySQL 会阻塞事件循环,必须改用异步驱动(如
think-swoole的协程 MySQL)或走队列 - 日志写入
Log::info()默认同步刷盘,高并发下 IO 拖垮性能;应设为异步模式或仅记录 ERROR 级别 - Redis
lPush虽快,但未启用连接池时每次新建连接,建议用redis()->connection()->getPool()复用连接
最易被忽略的一点:MQTT 的 QoS=1 消息在 Webman 进程意外退出(如 reload)时,未确认的 PUBACK 会丢失,导致设备重发。必须配合 Broker 的会话保持(clean_session=false)与本地消息去重(如用 Redis Set 缓存 msg_id)才能真正可靠。这层逻辑得自己补,workerman/mqtt 不提供。
php免费学习视频:立即使用
踏上前端学习之旅,开启通往精通之路!从前端基础到项目实战,循序渐进,一步一个脚印,迈向巅峰!











