旧安卓手机跑物联网后端:4000 台设备每秒 1.95 万条

这套物联网遥测系统跑在一台淘汰的旧安卓手机上(8 核 ARM,5.7GB 内存,Termux 当 Linux 服务器):设备通过 MQTT 上报温度、湿度、电量,Go 后端订阅解析后写入 SQLite,网页面板实时显示速率曲线、在线设备和电量状态。最新一轮压测用 4000 台模拟设备持续上报,平均每秒处理 1.95 万条,60 秒窗口峰值 2.03 万条;同一套程序原样搬到 4 核 x86 云服务器,峰值 7.1 万条/秒,200 万条零丢失。

下面这张面板是最新一轮压测的实况:4000 台设备每台发 500 条,1 分 44 秒收到 197 万条,平均每秒 1.95 万条,数据库写入 87MB,速率曲线全程无衰减。压测端共发出 200 万条,面板收到 197 万条,差额来自投递模式:这轮用 QoS 0(发出即算完,不逐条等对方确认),设备量超过链路处理能力时,中间的消息中转站会丢掉一部分过载消息。要求一条都不能少的业务改用 QoS 1(每条等确认,速度慢一些)。文中提到的零丢失都做过条数核对。

压测实况:4000 台设备持续上报

架构设计

收消息的环节只把数据拷进缓冲队列,不做解析;解析和统计由 4 个 worker 并行处理;落库由单个 writer 攒够 1000 条后打包成一个事务写入。两级缓冲队列各 10 万容量,吸收突发流量,也给退出时留出排空时间。面板所需的总消息数、每秒速率、在线设备全部从内存读取,不查数据库,看面板不影响数据接收。

架构图

性能定位

稳态下用 Go 自带的 pprof 抓 CPU 采样,导出火焰图(横条宽度是该函数占到的采样比例,纵轴是调用栈深度),采样结果直接交给 AI 解读,几秒就能出结论。4,964 个采样点的分布是落库路径 33.4%、MQTT 消息接收 15.7%、运行时调度 13.1%、JSON 解析 12.4%,没有一处能靠换掉单个组件解决。同一时刻的系统监控:后端程序占用 2.4 个 CPU 核、内存 58MB,消息中转程序占用 0.5 个核。

稳态 CPU 采样火焰图

压测中的系统资源占用

服务规模

以这台手机实测的每秒 1.95 万条为基准(换成 QoS 1 稳态 1.57 万条算更保守,结论不变),一天约能处理 16.8 亿条。按常见上报频率折算:

上报频率 每台每天 满负荷理论设备数 10 万台时的平均负载
15 分钟一次 96 次 1,755 万台 111 条/秒(占能力 0.6%)
30 分钟一次 48 次 3,510 万台 56 条/秒(0.3%)
1 小时一次 24 次 7,020 万台 28 条/秒(0.2%)

10 万台设备的真实负载只吃掉不到 1% 的吞吐能力,30 万台也只占 0.9%(每秒 167 条)。吞吐不是瓶颈,真正的约束是连接数和存储。

连接数。常态在线的长连接模式下,每个连接占 30 到 50KB 内存,加上消息中转程序是单线程,这台手机的合理规模是 1 到 3 万台同时在线;要撑 10 万长连接就得换成多核服务器或者拆多个网关。但电池供电的设备(水表、烟感这类,休眠、唤醒、上报、立刻断开)本来不适合长连接,改走短连接后这个约束直接消失,10 万台可行。代价有两个:一是重连速率,10 万台按 15 分钟上报约 111 次/秒重连,实测建连能力约 1000 次/秒,余量 9 倍;二是必须让设备错峰唤醒(随机延迟 0 到 60 秒),否则整点会打成重连风暴。

存储。单条约 120 字节,30 万台设备每天 1.7GB、每月 52GB。压测中观察到单张表写到 200MB 以内速度不衰减,涨到 800MB 时掉到四分之一,200MB 约等于 175 万条,也就是 1,200 台设备一个月的量。按这条边界推算分表粒度:千台级别按月分表就够,10 万台约 4 小时一张表,30 万台按小时一张表(每月 720 张,每张约 72MB)。这部分是按实测边界外推的设计值,没有跑过 30 万台的真机验证。

测试环境

旧手机 4 核 x86 云服务器(GitHub Codespaces)
CPU 8 核 ARM64,root 锁定最高频率 4 核 x86-64
内存 5.7GB + 2.6GB 内存压缩空间 16GB
系统 Android 10 + Termux Ubuntu 24.04
程序 同一套 Go 源码,各自编译 同左
协议与数据 MQTT,单条约 120 字节(温度、湿度、电量、时间戳) 同左
压测工具 自写 Go 程序,用协程模拟设备,手机本地回环压测 同左

并发建连这一项按有据可查的数字写:压测日志完整留档的是 1 万台设备全部建连成功、平均连接耗时 191.5ms(QoS 1,每台 3 条);更早一轮 3 万台的实验只在架构文档里有结论、没有留下日志,所以没有写进正文。