IoT 项目踩坑实录:从粘包乱码到稳定承载 200+ 设备
这是一个物联网项目。
整体链路如下:
传感器 → TCP Server(Netty) → TCP → MQTT Bridge → EMQX
后端消费者基于 EMQX 再落库(InfluxDB / MySQL / Redis / AI 模型)
为了验证系统抗压能力,我使用 Python 编写 TCP 客户端,模拟多传感器长连接 + 周期性上报,结果在压测过程中,连续踩中了多个典型但非常隐蔽的坑。
下面是对踩坑过程的完整复盘。
问题现象
1. 设备数量异常增长
压测一段时间后,数据库中出现了大量“新设备”,明明只模拟了 10 个设备,实际却创建了 200+ 条 device 记录,而且 device_sn 出现诡异值,如:
WT6B0000000\u0001
WT6B0000000\u0002
WT6B0000000\u00032. MQTT 发布失败
EMQX 日志持续报错:
Invalid UTF-8 char: [1]3. 解析数据明显异常
同一设备,第一次数据完全正常,后续数据角度、电压、阈值全部失真,解析类型从 0x0100 变成了 0x3B01,
但 TCP Server 没有报异常。
开始以为是数据库业务逻辑问题
最开始怀疑的是:
- MyBatis 的
select + insert并发问题 - 设备注册逻辑设计不当
确实,这段代码在并发下是有设计缺陷的:
IotDevice device = selectBySn(sn);
if (device == null) {
insert(device);
}
update(device);但将这部分代码先注释掉,再次运行后发现,问题依旧存在。
数据库问题只是放大器,真正的根本原因在 TCP 层。
根本原因:TCP 粘包 + 帧错位
1. TCP 的粘包问题
TCP 没有消息边界,只保证字节流有序。
这意味着:一次 send() ≠ 一次 read();多个设备并发时,字节流极易错位。
2. 错位带来的连锁反应
一旦 42 字节协议帧发生错位:
- 设备 ID 的最后一个字节被解析成
0x01 / 0x02 / 0x03 - Java 用
byte → char转 String - 生成了随机字符甚至直接生成了非法 UTF-8 字符
- MQTT publish 失败 or 数据库误判为新设备
所以数据库里看到的并不是多设备,而是:同一设备的 ID 被 TCP 粘包打碎了。
为什么已经用 FixedLengthFrameDecoder 了还不行?
我一开始的 Netty pipeline 是这样的:
pipeline.addLast(new FixedLengthFrameDecoder(42));
pipeline.addLast(new RailwayProtocolDecoder());
pipeline.addLast(new RailwayTcpHandler());但问题仍然存在。
问题在模拟脚本
Python 模拟脚本中,设备 ID 是二进制自增,而不是 ASCII:
hex_list[11] = f"{device_no:02X}" #这会直接产生:
0x01 0x02 0x03 ...而协议定义中的设备 ID 实际是:
"WT6B00000001" → ASCII '0' '1'最终修复方案
1.协议层彻底对齐
- 固定 42 字节
- 所有设备 ID 严格 ASCII
- 禁止任何非可见字符进入 device_sn
frame[11] = f"{0x30 + device_no:02X}" # '1' → 0x312.Netty 做帧切割
pipeline.addLast(new FixedLengthFrameDecoder(42));3.MQTT 发布必须明确 UTF-8
byte[] payload = mapper.writeValueAsBytes(data);
MqttMessage msg = new MqttMessage(payload);不要隐式 getBytes()。
4.数据库层兜底
- device_sn 加唯一索引
- 使用
INSERT ... ON DUPLICATE KEY UPDATE - 不再用
select + insert
在超高并发设备注册场景下,即使是这条SQL也可能成为瓶颈。后面我将引入缓存(Redis)做一层SN的快速判重。
最终效果
在修复后进行压力测试:
- 200+ TCP 长连接稳定
- 固定设备数量,无新增“幽灵设备”
- MQTT 发布成功率 100%
- 数据解析长期稳定
- EMQX 消费链路正常
本文系作者 @xiin 原创发布在To Future$站点。未经许可,禁止转载。
暂无评论数据