You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在MQTT发布者离线时通知订阅者?JS心跳检测异常问题求解

解决方案

你原有方案失效的核心原因是JS主线程的单线程事件循环机制:setTimeout、setInterval都属于宏任务,只有当前调用栈清空、微任务队列执行完之后才会触发回调,当主线程有大量同步任务、重渲染、密集计算逻辑时,定时器回调会被推迟,计时精度完全无法保证。


方案1:优先使用MQTT原生遗嘱消息(LWT)

这是成本最低、可靠性最高的方案,完全避开前端心跳计时的问题:

  • MQTT协议原生支持**遗嘱消息(Last Will and Testament)**特性,你只需要要求对应主题的发布者在连接MQTT broker时,提前配置好遗嘱主题、离线时要推送的遗嘱内容
  • 当发布者异常离线(未正常发送DISCONNECT包就断开连接)时,broker会自动把预配置的遗嘱消息推送给所有订阅了该遗嘱主题的订阅端,你直接订阅对应遗嘱主题即可收到离线通知,无需自行实现心跳逻辑

方案2:用Web Worker独立运行心跳逻辑

如果必须自己实现心跳检测,把整个心跳逻辑放到独立的Web Worker线程运行,完全不受主线程调用栈阻塞的影响:

  • Worker线程和主线程隔离,内部的定时器不会被主线程的重渲染、同步计算等任务阻塞,计时精度有保障
  • 示例代码:
    主线程代码:
    // 初始化心跳Worker
    const heartBeatWorker = new Worker('./heartbeat.worker.js')
    // 收到MQTT的pong消息时通知Worker
    client.on('message', (topic, payload) => {
      if (topic === 'ping/response') {
        heartBeatWorker.postMessage({ type: 'PONG_RECEIVED' })
      }
    })
    // 监听Worker发来的离线通知
    heartBeatWorker.onmessage = (e) => {
      if (e.data.type === 'PUBLISHER_OFFLINE') {
        // 执行离线通知逻辑
        console.log('发布者已离线')
      }
    }
    
    heartbeat.worker.js 代码:
    const PING_INTERVAL = 2000
    const TIMEOUT_THRESHOLD = 8000
    let lastPongTime = Date.now()
    
    // 每2秒发ping并检查超时
    setInterval(() => {
      self.postMessage({ type: 'SEND_PING' })
      if (Date.now() - lastPongTime > TIMEOUT_THRESHOLD) {
        self.postMessage({ type: 'PUBLISHER_OFFLINE' })
      }
    }, PING_INTERVAL)
    
    // 收到主线程的pong通知更新时间
    self.onmessage = (e) => {
      if (e.data.type === 'PONG_RECEIVED') {
        lastPongTime = Date.now()
      }
    }
    

方案3:主线程使用时间戳比对修正计时误差

如果暂时无法引入Web Worker,可以放弃依赖setTimeout的回调执行时间,改用时间戳差值做判定,即使定时器回调被延迟,判定逻辑依然准确:

  • 不需要为每次ping单独设置timeout,只需要在每次收到pong时记录当前时间戳lastPongTime
  • 不管setInterval的回调延迟多久触发,每次执行时直接计算当前时间和lastPongTime的差值,超过8秒直接判定离线
  • 示例代码:
    let lastPongTime = Date.now()
    const TIMEOUT_THRESHOLD = 8000
    
    // 收到pong时更新时间
    client.on('message', (topic, payload) => {
      if (topic === 'ping/response') {
        lastPongTime = Date.now()
      }
    })
    
    // 每2秒检查一次
    setInterval(() => {
      // 发ping
      client.publish('ping/request', 'ping')
      // 直接用时间差判定,不受定时器延迟影响
      if (Date.now() - lastPongTime > TIMEOUT_THRESHOLD) {
        console.log('发布者已离线')
      }
    }, 2000)
    

内容的提问来源于stack exchange,提问作者Meghraj Deshmukh

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.10.04 07:30:01