问题背景:断网时数据去哪儿了
边缘网关通常部署在工厂车间、加油站、变电站这类网络质量不稳定的一线现场。上游是云端 MQTT 或 HTTPS 接口,下游挂着几十到几百个 PLC、电表和传感器。只要出口带宽抖动或者专线中断,采集到的数据就面临两个选择:丢掉,或者原地等死。很多团队早期用内存队列顶一下,进程一重启数据全没;后来改成每条数据同步写数据库再上报,结果磁盘 IO 直接成为瓶颈,单机吞吐从每秒八千条掉到不足两千条。
更麻烦的是续传语义。云端按时间戳做业务计算,如果网关在断网期间只是简单堆文件,恢复后无脑重发,就会带来重复上报和乱序问题。运维侧还要面对“到底积压了多少、什么时候能追平”的可见性缺失。所以这个模块要解决的不是“存下来”这么简单,而是三件事:断网期间可靠落盘、恢复后有序可控地回放、积压状态可观测。下面按选型、落地、效果三个部分展开,都是我们在实际项目中跑过的方案。
方案选型:磁盘队列与内存队列的取舍
缓存层我们对比了三种路线。第一种是纯内存环形队列加定时批量落盘,优点是写入延迟低,单条写入在几十微秒级别,缺点是断电会丢最后一批数据,且队列满后只能丢弃最老数据,适合允许少量丢失的遥测场景。第二种是直接上嵌入式数据库如 SQLite 或 RocksDB,写入可靠、支持按时间范围查询,但每次事务提交都有 fsync 开销,在低端 ARM 网关上周转量受限,而且数据库文件损坏后的修复成本高。第三种是我们最终采用的“分段日志文件 + 内存索引”:数据按固定大小切分成段文件顺序追加写,索引只记录每段的起止时间戳和偏移量,内存占用恒定。
关键取舍在写入确认点。同步 fsync 每条数据能保证不丢,但实测单机写入从一万二千条每秒降到一千八百条每秒,代价太大。我们改成批量提交:累积 200 条或 200 毫秒触发一次 fsync,最坏情况丢失一个批次的窗口。对于绝大多数工业采集场景,这个窗口可以接受,换来的是写入吞吐回到九千条每秒以上。另一个取舍是段文件大小,设成 64MB 时单段回放耗时约 3 秒,设成 8MB 则文件数量过多、目录遍历变慢,最终定在 32MB,兼顾回放粒度和文件管理开销。
落地步骤:从落盘到回放的四个环节
第一步是写入路径。采集线程只负责把消息序列化成定长头部加变长负载,追加到当前活跃段文件的末尾,然后更新内存中的写偏移。序列化头部包含消息 ID、采集时间戳和设备标识,为后续去重和排序提供依据。写入完成后投递一个轻量信号给刷盘线程,由它按批次策略执行 fsync。这里要注意段文件切换必须加锁且保证原子性,切换瞬间不能有半条消息落在两个文件里。
第二步是积压管理与淘汰策略。我们给缓存设了容量上限,按磁盘配额计算,默认占用不超过网关可用空间的 30%。当积压超过阈值时,优先丢弃最老的段文件而不是最新数据,因为业务侧更关心近期状态。淘汰动作记录到本地审计日志,运维可以通过管理接口查到“某时间段因容量淘汰了多少条”,避免数据无声消失。
第三步是续传调度。网络恢复后不立刻全速回放,而是采用令牌桶限速,初始速率设为正常上报速率的 50%,观察云端返回的成功率,连续成功 100 批后逐步提升到 100%。回放按段文件顺序进行,同一段内按写入偏移严格有序,保证云端看到的时间戳单调不减。每条消息带唯一 ID,云端做幂等去重,网关侧则在收到确认后才推进回放位点,位点持久化到独立的小文件中,重启后从位点继续。
第四步是可观测性。网关暴露三个核心指标:当前积压条数、最老未上报数据的时间戳、回放速率与预估追平时间。这三个指标接入现有监控后,运维不需要登录设备就能判断链路健康度。实测中,一个积压 40 万条的网关在带宽恢复后约 11 分钟完成追平,期间云端业务无重复计算。
效果与成本:数字说话
上线这套方案后,三个可量化结果比较有说服力。其一,单台网关的写入吞吐稳定在九千二百条每秒,P99 写入延迟 6 毫秒,相比早期同步写数据库的方案吞吐提升约 4 倍,磁盘 IO 等待时间下降 72%。其二,断网 30 分钟产生的约 18 万条积压数据,恢复后全部有序送达,重复率控制在 0.3% 以内,靠的是消息 ID 幂等加位点确认。其三,运维排查断网问题的时间从平均 40 分钟降到 8 分钟,因为积压指标直接给出了追平预估。
成本侧主要是磁盘占用和少量内存。按每条消息平均 400 字节、保留 24 小时计算,单网关额外占用约 3GB 磁盘,内存索引常驻在 20MB 以内。相比引入独立消息中间件带来的部署和运维开销,这个成本对边缘设备是可接受的。需要提醒的是,段文件数量要定期做合并清理,否则小文件过多会拖慢目录扫描,我们在实现里加了后台压缩线程,把已回放完毕的段文件直接删除,活跃段不超过两个,整体磁盘增长曲线是平稳的。