thingsboard如何维护设备的状态的

本文以thingsboard-3.1.1为例说明

正文

        thingsboard在内存里面是记录了每个设备(包括网关)的在线状态的,在数据attribute_kv表中active字段对应的就是设备在线状态的值。

        thingsboard的对mqtt消息的处理是由MqttTransportHandler来完成的,底层通信基于netty实现,熟悉netty的开发者对ChannelInboundHandlerAdapter一定特别熟悉,咱们直接看下MqttTransportHandler是如何重载channelRead方法的,如下所示:

@Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) {
        log.trace("[{}] Processing msg: {}", sessionId, msg);
        try {
            if (msg instanceof MqttMessage) {
                processMqttMsg(ctx, (MqttMessage) msg);
            } else {
                ctx.close();
            }
        } finally {
            ReferenceCountUtil.safeRelease(msg);
        }
    }
private void processMqttMsg(ChannelHandlerContext ctx, MqttMessage msg) {
        address = (InetSocketAddress) ctx.channel().remoteAddress();
        if (msg.fixedHeader() == null) {
            log.info("[{}:{}] Invalid message received", address.getHostName(), address.getPort());
            processDisconnect(ctx);
            return;
        }
        deviceSessionCtx.setChannel(ctx);
        switch (msg.fixedHeader().messageType()) {
            case CONNECT:
                processConnect(ctx, (MqttConnectMessage) msg);
                break;
            case PUBLISH:
                processPublish(ctx, (MqttPublishMessage) msg);
                break;
            case SUBSCRIBE:
                processSubscribe(ctx, (MqttSubscribeMessage) msg);
                break;
            case UNSUBSCRIBE:
                processUnsubscribe(ctx, (MqttUnsubscribeMessage) msg);
                break;
            case PINGREQ:
                if (checkConnected(ctx, msg)) {
                    ctx.writeAndFlush(new MqttMessage(new MqttFixedHeader(PINGRESP, false, AT_MOST_ONCE, false, 0)));
                    transportService.reportActivity(sessionInfo);
                }
                break;
            case DISCONNECT:
                if (checkConnected(ctx, msg)) {
                    processDisconnect(ctx);
                }
                break;
            default:
                break;
        }
    }

        从上面的方法可以看到thingsboard是如何处理mqtt消息的,针对connect、publish、dusbsrcribe等消息类型进行了处理,processConnect与processDisconnect方法是处理设备连接/断开连接的,在processConnect方法中创建了设备的在线信息到内存中,processDisconnect则相反。

        processConnect是建立连接,但是要维护设备的实时连接状态,只处理连接消息肯定是不够的,thingsboard还会处理publish(属性更新以及遥测值上传)等消息也会更新设备的活动状态,具体可以参考TransportService.reportActivity方法。

// 属性更新以及遥测值上传
private void processPublish(ChannelHandlerContext ctx, MqttPublishMessage mqttMsg) {
        if (!checkConnected(ctx, mqttMsg)) {
            return;
        }
        String topicName = mqttMsg.variableHeader().topicName();
        int msgId = mqttMsg.variableHeader().packetId();
        log.trace("[{}][{}] Processing publish msg [{}][{}]!", sessionId, deviceSessionCtx.getDeviceId(), topicName, msgId);

        if (topicName.startsWith(MqttTopics.BASE_GATEWAY_API_TOPIC)) {
            if (gatewaySessionHandler != null) {
                handleGatewayPublishMsg(topicName, msgId, mqttMsg);
                transportService.reportActivity(sessionInfo);
            }
        } else {
            processDevicePublish(ctx, mqttMsg, topicName, msgId);
        }
    }
@Override
    public void reportActivity(TransportProtos.SessionInfoProto sessionInfo) {
        reportActivityInternal(sessionInfo);
    }

    private SessionMetaData reportActivityInternal(TransportProtos.SessionInfoProto sessionInfo) {
        UUID sessionId = toSessionId(sessionInfo);
        SessionMetaData sessionMetaData = sessions.get(sessionId);
        if (sessionMetaData != null) {
            sessionMetaData.updateLastActivityTime();
        }
        return sessionMetaData;
    }

        可以看到每次设备(通过设备自身或者通过网关上传数据)都会更新设备的最后活跃时间字段。看到这里一直没有看到除了设备主动关闭连接的情况下thingsboard是如何清理过期连接的,接下来是本场的主角:DefaultTransportService.checkInactivityAndReportActivity方法:

    private void checkInactivityAndReportActivity() {
        long expTime = System.currentTimeMillis() - sessionInactivityTimeout;
        sessions.forEach((uuid, sessionMD) -> {
            long lastActivityTime = sessionMD.getLastActivityTime();
            TransportProtos.SessionInfoProto sessionInfo = sessionMD.getSessionInfo();
            if (sessionInfo.getGwSessionIdMSB() > 0 &&
                    sessionInfo.getGwSessionIdLSB() > 0) {
                SessionMetaData gwMetaData = sessions.get(new UUID(sessionInfo.getGwSessionIdMSB(), sessionInfo.getGwSessionIdLSB()));
                if (gwMetaData != null) {
                    lastActivityTime = Math.max(gwMetaData.getLastActivityTime(), lastActivityTime);
                }
            }
            
            if (lastActivityTime < expTime) {
                // 长时间没有对话,session过期
                if (log.isDebugEnabled()) {
                    log.debug("[{}] Session has expired due to last activity time: {}", toSessionId(sessionInfo), lastActivityTime);
                }
                process(sessionInfo, getSessionEventMsg(TransportProtos.SessionEvent.CLOSED), null);
                sessions.remove(uuid);
                sessionMD.getListener().onRemoteSessionCloseCommand(TransportProtos.SessionCloseNotificationProto.getDefaultInstance());
            } else {
                if (lastActivityTime > sessionMD.getLastReportedActivityTime()) {
                    final long lastActivityTimeFinal = lastActivityTime;
                    process(sessionInfo, TransportProtos.SubscriptionInfoProto.newBuilder()
                            .setAttributeSubscription(sessionMD.isSubscribedToAttributes())
                            .setRpcSubscription(sessionMD.isSubscribedToRPC())
                            .setLastActivityTime(lastActivityTime).build(), new TransportServiceCallback<Void>() {
                        @Override
                        public void onSuccess(Void msg) {
                            sessionMD.setLastReportedActivityTime(lastActivityTimeFinal);
                        }

                        @Override
                        public void onError(Throwable e) {
                            log.warn("[{}] Failed to report last activity time", uuid, e);
                        }
                    });
                }
            }
        });
    }

        checkInactivityAndReportActivity这个方法是DefaultTransportService创建时启动的一个定时检测任务,

private final ConcurrentMap<UUID, SessionMetaData> sessions = new ConcurrentHashMap<>();

        sessions是以设备id为主键的ConcurrentMap对象,这个方法就会定期去扫描sessions里的session数据,长时间与thingsboard未进行会话就会关闭与设备的会话连接,并清除内存保存的会话数据。

### 关于 ThingsBoard 设备状态偶尔掉线的解决方案 ThingsBoard 是一种强大的物联网平台,用于数据收集、处理和可视化。然而,在实际应用中可能会遇到设备状态偶尔掉线的情况。以下是可能的原因分析以及对应的解决方案: #### 可能原因及对应措施 1. **网络连接不稳定** 如果设备与服务器之间的网络连接存在波动,则可能导致设备状态显示为离线。可以通过优化网络环境来改善这一情况。例如,使用更稳定的通信协议(如 MQTT 而不是 HTTP),或者增加心跳包发送频率以保持连接活跃[^2]。 2. **心跳超时设置不当** 默认情况下,ThingsBoard设备会定期向服务器发送心跳消息以表明其在线状态。如果该时间间隔过长而实际断连恢复所需的时间较短,就可能出现短暂性的“假下线”。调整 `tb-transport` 配置文件中的相关参数可以缓解此现象,比如缩短最大允许无响应周期 (maxInactiveIntervalInSeconds)[^3]。 3. **资源耗尽** 当设备端内存泄漏或其他硬件限制导致无法正常运行客户端程序时也会引发此类问题。因此建议监控并记录日志以便及时发现异常行为;另外还可以考虑升级固件版本至最新稳定版从而修复已知漏洞或性能瓶颈[^4]。 4. **服务端负载过高** 若 ThingsBoard 平台本身承受着巨大压力,则有可能因为未能及时处理来自各个节点的数据流而导致误判某些装置处于不活动状态之中。此时应评估当前架构能否满足需求,并视具体情况采取扩容策略——无论是垂直扩展(提升单一实例规格)还是水平分片(引入更多副本参与工作)[^5]。 5. **认证机制失效** 设备重新启动后如果没有正确刷新令牌或许可证也可能被标记成离线模式。确保每次开机都执行完整的身份验证流程至关重要。对于长期部署场景来说, 定期轮换密钥也是一种良好实践方式[^6]。 ```bash # 示例命令:更新事物板上的特定实体属性值 curl -X POST https://your-thingsboard-server/api/v1/$ACCESS_TOKEN/telemetry \ -H 'Content-Type: application/json' \ -d '{"temperature": 70}' ``` 上述脚本展示了如何通过 REST API 向 ThingsBoard 提交遥测数据的一个简单例子。请注意替换 `$ACCESS_TOKEN` 和 URL 地址部分为你自己的配置信息前缀。 --- ####
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值