云计算百科
云计算领域专业知识百科平台

一条 TCP,两种采样:用 etherAdapter 把现场设备数据汇进 EtherDB

一、先说说现场的五个真实困扰

做过设备数据接入的人都遇到过这些:

  • 设备协议五花八门。 每来一种设备就要写一段解析代码,改一次编译一次,
    现场还不能停。
  • 一台设备要两种数据。 平时 1Hz 采 20 个测点;一旦触发事件,采样率要拉高、
    测点数可能变成 8 个甚至另一组。传统做法是开两条连接、跑两个程序,
    设备和网关两侧都要改。
  • 帧长靠猜。 协议里没有标准头,帧长字段到底是"整帧长度"还是"payload 长度",
    只能在运行期试探,试错了就错位、丢帧。
  • 攒批带来的延迟。 1Hz 的状态量本来一个点就要立刻进库,却被凑成 4000 行才写,
    监控画面迟迟不出数。
  • 换算把原始值弄丢了。 一乘 factor,存进库的就不是设备真实上报的码值,
    事后想做标定、追溯、二次换算都没有依据。
  • etherAdapter 就是为这五件事写的:一个进程、一套配置、一条 TCP 连接,
    把设备数据原样汇入 EtherDB。


    二、一条连接,两种采样:靠 frameType 区分

    同一台设备用一条 TCP 连接上报两类数据:

    • 状态帧(status):周期采样,一般 1Hz,测点固定;
    • 事件帧(event):突发采样,频率更高、测点数可能更少或不同。

    两者在6 字节自定义头的第一个字节里用帧类型区分:

    [frameType 1B][flag 1B][frameLen 2B][sequenceId 2B][payload …][flag 1B?]

    • frameType —— 这台设备上的"哪套测点"。对适配器来说,它就是另一台逻辑设备:
      有自己的字段表、自己的解析规则、自己的 EtherDB 表。
    • flag —— 事件流的定界字节。它只对事件帧有意义:在同一条 TCP 数据流里,
      适配器靠它认出"接下来是另一套解析规则"。状态帧不带定界符,这个字节被忽略。
    • frameLen —— 本帧 payload 字节数。一次可以携带多条记录
      (frameLen / 单条记录长度),多个采样点共用一个头,省带宽。
    • sequenceId —— 预留字段,当前不校验、不解析,将来扩展用。

    配置上,同一 (ip, 端口) 写多行设备表即可:

    device_idip源端口data_proto_idframe_type
    dev_T100_001 127.0.0.1 10002 100077 0xC(状态)
    dev_T100_001_E 127.0.0.1 10002 100078 0xE(事件)

    第一行是状态设备,其余是事件设备。设备侧只需要在头里改一个字节,
    数据就分别落进 dev_T100_001 和 dev_T100_001_E 两张表。

    这一步的价值:设备固件不用为"加一路事件"改造连接管理,
    网关侧也不用新开监听端口。


    三、状态数据不攒批:来了就提交

    大多数状态量是 1Hz,根本不值得等。etherAdapter 的策略是:

    • 状态帧:每收到一个数据块立即刷写,不攒批、不等定时器 —— 画面出数最快;
    • 事件帧:按批写入 —— 突发事件频率高、点数多,批量写入才是吞吐最优。

    两者在同一条管道里共存,配置里只需要把设备行写对。


    四、只存原始值:factor 先放一边

    时序库的价值在于"如实地留下现场",所以适配器不做物理量换算:

    • data_proto_table.factor 当前不参与计算,启动时若发现配置了非 1 的系数,
      只打一条告警提醒;
    • 整型字段就按原始码值存(有符号/无符号类型自动选合适的列宽),
      FLOAT32/FLOAT64 按原值存;
    • 想换算?查询时再乘,或者交给上层 —— 原始值永远还在。

    五、从设备到 EtherDB 的路径

    设备 ──TCP──> muduo 网络层 ──无锁队列(moodycamel)──> 单线程解析
    │
    RowBatch ──┬──────────────┘
    ▼
    写入队列 ──> 列绑定预编译 INSERT ──> EtherDB
    └─> 发布队列 ──> ZMQ PUB(预留)

    几个刻意的设计:

    • 解析单线程:所有协议解析集中在一个线程里,配置驱动、没有锁竞争,
      也避开了多线程解析的时序问题;网络线程只负责把字节搬进队列。
    • 列绑定批量写入:按设备预编译 INSERT,每列一块连续内存,
      一次绑定多行 → 一次执行。慢速 HTTP 数据走普通 SQL,不占预绑定资源。
    • 配置驱动:三张 SQLite 表(设备 / 数据头 / 字段)决定一切,
      加设备就是加一行,改协议就是改一行。

    EtherDB 侧的性能已经有公开实测(见 EtherDB/docs/EtherDB-performance.md):
    同机批量写入约 214 万行/秒、8 进程聚合约 618 万行/秒、
    COUNT(*) 0.34 ms、千万行分页 0.82 ms,服务端单文件 1.07 MB。
    适配器只负责"把数据正确地喂进去",不改变这条写入路径。


    六、本机跑通的一条完整链路

    用仓库自带的 fixtures,从设备模拟到落库实测:

    csv2sqlite.exe config\\etherAdapter.db tests\\fixtures –force
    etherdb_dserver.exe -p 7040
    etherAdapter.exe

    :: 状态 + 事件在一条 TCP 连接上交替上报
    device_sim.exe -n 60 -i 5 -t C,E -l 73,62 -f 0,01 -e 0,03 -v 100

    结果(query_check.exe 直接查库):

    表内容说明
    dev_T100_001 item1 = 100,102,104… 状态帧,单字段,来了就提交
    dev_T100_001_E item1 = 101,103,105…,含 item2 事件帧,两个测点,另一张表
    dev_MB_401 reg1=2000+k, reg2=2100+k MODBUS 3s 轮询,原始寄存器值
    dev_HTTP_001 temp=12.5, humidity=60 HTTP POST JSON,直拼 SQL

    运行统计里可以看到分流是干净的:frames +60 rows +60 (event +30) written +61 drop 0 err 0。

    同一份 fixtures 还覆盖了:多记录共用一个头(device_sim -m 3)、
    MODBUS 四种响应头(9/7/2/0)、HTTP 缺字段写 NULL、
    以及 sequenceId 随便填也不影响解析。


    七、适合谁用

    • 现场设备不能改(或改不动),但要把数据接进时序库;
    • 一台设备既要周期状态、又要突发事件,还不想开两条连接;
    • 需要在边缘侧轻量部署(适配器与 EtherDB 都是单文件、无外部依赖);
    • 数据要留原始值,标定/换算放在后面做。

    八、小结

    etherAdapter 做的是"最后一公里"里最脏的那段活:
    把设备怎么说的话,原样、低延迟地翻译成 EtherDB 的列。

    • 一条 TCP,两种采样 —— 靠 frameType;
    • 状态数据来了就提交,事件数据按批落表;
    • 配置即接入:三张表、一个进程。

    设备不用动,数据库不用改,中间只多了一个进程。

    赞(0)
    未经允许不得转载:网硕互联帮助中心 » 一条 TCP,两种采样:用 etherAdapter 把现场设备数据汇进 EtherDB
    分享到: 更多 (0)

    评论 抢沙发

    评论前必须登录!