17 KiB
实时设备数据网关最终方案
文档版本:v1.0 最后更新:2026-04-02 08:28:05
本文档用于收敛当前关于 GPS 模拟、中转、监控、规则判定、回放、通知分发等讨论,给出一版可直接进入实现设计的最终方案。
1. 目标与定位
本系统不再定义为“GPS 模拟器”,而定义为:
- 一个独立部署的实时设备数据网关
- 负责实时接入、标准化、路由、订阅、最新状态同步
- 默认不依赖数据库,不承担历史存储职责
- 通过插件扩展规则判定、通知分发、回放、归档等能力
一句话定义:
一个以实时中转为核心、以插件扩展业务能力的轻量遥测网关。
2. 设计原则
2.1 核心优先级
核心目标按优先级排序如下:
- 实时性能
- 稳定性
- 低耦合
- 易扩展
- 易观测
2.2 核心边界
核心服务只负责:
- 长连接接入
- 消息标准化
- 路由转发
- 订阅管理
- latest state 缓存
- 连接管理
- 心跳和断线清理
- 基础鉴权
- 限流与基本防护
核心服务不负责:
- 历史存储
- 业务报表
- 复杂规则引擎
- 第三方通知发送
- 业务数据库查询
- 面向家长端或场控端的专属业务逻辑
3. 总体架构
3.1 拓扑关系
Producer
├─ 手机客户端
├─ 外部模拟器
├─ 回放器
└─ 设备接入器
|
v
Realtime Device Gateway
├─ Connection Manager
├─ Protocol Layer
├─ Session Manager
├─ Router / Fanout
├─ Latest State Cache
└─ Plugin Bus
|
+--> Consumer
| ├─ 小程序 / App
| ├─ 家长端
| ├─ 场控端
| └─ 调试端 / 大屏
|
+--> Plugins
├─ Rule Engine
├─ Dispatcher
├─ Recorder
├─ Replayer Adapter
└─ Webhook / Bridge
Business Server
├─ 用户与设备关系
├─ 配置管理
├─ 历史归档
└─ 业务接口
3.2 在整个系统中的定位
实时网关在整个系统中,不是主业务服务器的替代品,而是一层独立的实时中枢。
职责分工建议固定为:
- 设备、模拟器、回放器
- 负责产出实时数据
- 实时网关
- 负责接入、路由、订阅、latest state、实时分发、运行态观察
- 业务服务器
- 负责用户、设备归属、配置、历史存档、业务查询
- 插件
- 负责规则、通知、归档、回放等异步能力
一句话理解:
主业务服务器负责“谁是谁”,实时网关负责“现在发生了什么,发给谁看”。
3.3 服务角色
-
Producer- 实时数据生产者
- 包括真实设备、模拟器、回放器
-
Consumer- 实时数据消费者
- 包括家长端、场控端、调试端、大屏
-
Controller- 可向 Producer 或 Gateway 下发控制命令
- 包括调试控制台、场控后台、回放控制器
3.4 角色使用流程
开发模拟
- Controller 在管理台创建
channel - 网关返回
channelId / producerToken / consumerToken - 老模拟器作为 Producer 加入 channel 并开始发
telemetry.location / telemetry.heart_rate - 管理台和调试 Consumer 实时观察数据
- 业务服务器此时可以不参与,或仅做低频归档
家长端监控
- 真机设备作为 Producer 向网关持续上报位置和状态
- 家长端作为 Consumer 订阅某个
deviceId或groupId - 网关负责实时分发
- 业务服务器负责账号关系、历史数据和业务页面
场控
- 场控端作为 Consumer 订阅多个设备或一个分组
- 网关持续推送实时位置、状态和事件
- 如果有控制需求,场控端再以 Controller 身份下发命令
- 规则插件可基于同一条实时流做违规判定
归档
- Producer 只向网关上报实时数据
- Recorder 插件异步消费网关流
- Recorder 再按
10-30s批量写入业务服务器
这样可以保证:
- 实时链路和归档链路解耦
- 客户端可以逐步从双写演进到单写网关
- 实时数据和历史数据口径更一致
4. 部署原则
4.1 独立部署
实时网关必须独立于主业务服务器部署。
这样做的原因:
- 不占用主业务服务器的实时长连接资源
- 不让实时 fanout 干扰主业务接口
- 实时服务可独立扩容
- 便于分离故障和性能问题
4.2 与主业务服务的关系
主业务服务只提供控制面能力:
- 用户与设备归属关系
- 权限配置
- 策略配置
- 历史存档入口
实时网关处理数据面:
- 高频 telemetry
- 最新状态同步
- 实时下行通知和控制
原则上,不允许每条实时上报都回主业务服务查库。
5. 0 数据库方案
5.1 可行性
第一版核心网关可以做到 0 数据库依赖。
这里的含义是:
- 不接 MySQL
- 不接 PostgreSQL
- 不接业务持久化存储
- 不存历史轨迹
- 不做持久化 session
5.2 保留的内存态
即使 0 数据库,网关仍然必须保留以下运行时内存数据:
- 连接表
- 订阅关系
- 设备 latest state
- 会话元数据
- 限流计数
- 心跳状态
5.3 重启语义
网关重启后允许丢失:
- 当前连接
- 当前订阅
- latest state
- 临时限流计数
系统恢复方式:
- 客户端自动重连
- 重新鉴权
- 重新订阅
- Producer 继续上报最新点
这对实时网关是可接受的。
6. 性能目标
6.1 第一版容量目标
第一版至少满足:
1000-2000设备同时在线上报- 默认
1 Hz实时上报能力 - 支持部分调试设备升频到
2-5 Hz - 端到端实时链路延迟目标
< 500 ms - 插件故障不阻塞核心中转
6.2 带宽认知
实时中转一定消耗网关所在服务器的带宽,但不会占用主业务服务器带宽。
不做历史存储,只能减少:
- 磁盘 IO
- 存储成本
- 数据库压力
不能减少:
- 长连接压力
- 实时 fanout 压力
- 网关入口和出口带宽
6.3 核心优化原则
- 不允许全量广播
- 必须按设备或组定向订阅
- payload 尽量小
- latest state 只保留当前值
- 插件必须异步消费
- 高频调试源必须可限流
7. 消息模型
7.1 统一信封
所有进入网关的数据统一使用一个标准信封:
{
"schemaVersion": 1,
"messageId": "uuid",
"timestamp": 1711267200000,
"topic": "telemetry.location",
"source": {
"kind": "producer",
"id": "watch-001",
"mode": "real"
},
"target": {
"deviceId": "child-001",
"groupId": "class-a"
},
"payload": {}
}
7.2 topic 分类
建议只保留四类顶级 topic:
telemetry.*state.*event.*command.*
示例:
telemetry.locationtelemetry.heart_ratetelemetry.motionstate.deviceevent.rule_violationevent.soscommand.replay.startcommand.simulation.stop
7.3 source mode
建议显式区分数据来源模式:
realmockreplaycontrol
这样业务侧可以明确识别是真实数据还是模拟/回放数据。
8. 主要数据载荷
8.1 位置 telemetry
{
"lat": 31.2304,
"lng": 121.4737,
"altitude": 12.3,
"speed": 1.5,
"bearing": 90,
"accuracy": 8,
"coordSystem": "GCJ02"
}
最少保留字段:
timestamplatlngspeedbearingaccuracycoordSystem
8.2 心率 telemetry
{
"bpm": 142,
"confidence": 0.92
}
8.3 运动 telemetry
{
"cadence": 168,
"stepCount": 3201,
"calories": 248.5,
"movementState": "run"
}
8.4 设备状态
{
"online": true,
"battery": 76,
"network": "4g",
"charging": false
}
8.5 事件
{
"eventType": "rule_violation",
"ruleId": "geo-fence-01",
"severity": "high",
"text": "leave allowed area"
}
8.6 控制命令
{
"command": "simulation.start",
"args": {
"sessionId": "sim-001",
"speedRate": 2
}
}
9. 订阅与路由模型
9.1 路由单位
实时网关按以下维度路由:
deviceIdgroupIdtopic
9.2 推荐订阅粒度
- 订阅某个设备全部实时消息
- 订阅某个设备某类消息
- 订阅某个组某类消息
示例:
device:child-001device:child-001/topic:telemetry.locationgroup:class-a/topic:event.*
9.3 latest state
每个设备保留一个最新状态快照,不存历史。
用途:
- 新订阅者刚连上时立即获得当前状态
- 页面重连后快速恢复
- 插件可从最新态快速计算
10. 鉴权策略
10.1 不建议完全不鉴权
如果完全不鉴权,会带来两个高风险问题:
- 任意客户端都能伪造位置或心率
- 任意客户端都可能下发控制命令
10.2 推荐分级鉴权
-
Producer- 必须强鉴权
- 防止伪造上报
-
Controller- 必须强鉴权
- 防止误操作和恶意控制
-
Consumer- 可轻鉴权
- 可按内网、白名单、临时 token、短期票据放宽
10.3 第一版推荐方式
第一版建议:
- Producer 使用签名 token 或短期接入 token
- Controller 使用管理 token
- Consumer 使用轻量订阅 token
网关本身不查数据库,只做:
- token 基本校验
- 角色识别
- 权限范围校验
复杂鉴权可以由独立鉴权服务承担。
11. 插件体系
11.1 原则
事件处理不是核心同步主链路的一部分,而是插件。
同步主链路只做:
ingest -> normalize -> route -> fanout -> update latest state
然后异步投递到插件总线:
publish -> plugin bus -> plugin async consume
11.2 第一批插件类型
Rule Engine- 实时规则判定
Dispatcher- 通知和下行分发
Recorder- 归档与历史写入
Replayer- 文件或历史流回放
Webhook- 向外部系统桥接
11.3 插件要求
- 不能阻塞主链路
- 失败必须隔离
- 可独立启停
- 最好支持单独部署
12. 规则判定与通知分发
12.1 规则不进核心
规则判定必须在插件层执行,不进入中转核心。
原因:
- 规则计算不稳定
- 容易引入复杂状态
- 容易放大 CPU 和 IO 消耗
- 不应影响实时转发
12.2 推荐规则类型
-
阈值规则
- 心率过高
- 速度异常
- 电量过低
-
时空规则
- 离开围栏
- 进入禁区
- 停留超时
- 偏离路线
-
行为规则
- 长时间静止
- 轨迹跳变
- 设备离线
12.3 动作分发
规则命中后由 Dispatcher 插件执行动作:
- 下发终端警告
- 推送给家长端
- 推送给场控端
- 上报业务服务
- 触发 Webhook
12.4 规则系统必须处理的问题
- 去重
- 冷却时间
- 恢复事件
- 幂等
13. 客户端上报策略
13.1 当前推荐的第一阶段方案
客户端保留两条链路:
- 实时链路
- 有需要时实时上报到网关
- 归档链路
- 每
10-30s打包一次发给业务服务器存档
- 每
这个方案的优点:
- 改造快
- 实时与归档解耦
- 主业务服务器不承受高频位置流
13.2 双写风险
客户端双写存在天然风险:
- 两边成功率不同
- 数据口径可能不一致
- 客户端逻辑更复杂
- 重试和去重更难
13.3 推荐演进方向
推荐第二阶段改成:
- 客户端只向网关实时上报
Recorder插件按10-30s批量归档到业务服务器
这样可以保证:
- 实时和归档数据同源
- 客户端逻辑更轻
- 后续回放、规则、告警直接复用同一条实时流
13.4 客户端升频策略
建议客户端支持策略切换:
normal- 低频或关闭实时上报
monitor1-5s实时上报
debug1 Hz
alert- 临时升频
14. 模拟与回放
14.1 模拟器定位
模拟器不再是一个独立特殊系统,而是一个标准 Producer。
它可以上报:
telemetry.locationtelemetry.heart_ratetelemetry.motionstate.device
14.2 回放器定位
回放器也是 Producer。
区别只是:
- source mode 为
replay - 输入来源是文件或历史流
- 支持播放、暂停、倍速、循环
14.3 地图模拟器要求
地图拖点时不应直接瞬移发点,至少要支持:
- 时间戳
- 插值
- 平滑
- 速度和朝向生成
否则业务侧会看到不真实的轨迹。
15. 技术选型建议
15.1 技术选型原则
本系统技术选型必须遵守以下原则:
- 轻量
- 健壮
- 性能优先
- 少依赖
- 易部署
- 易排障
目标形态应更接近:
- 软路由插件
- 轻量代理
- 边缘网关
- 单二进制网络服务
而不是典型的重业务后台服务。
15.2 第一版推荐
核心网关建议明确采用以下技术栈:
- 服务端语言:
Go - 实时协议:
WebSocket - 管理接口:
HTTP - 消息格式:
JSON - 运行态存储:纯内存
- 配置方式:本地配置文件加环境变量
- 部署形态:单二进制
- 日志:结构化日志
推荐原因:
- 更适合长连接、低延迟、fanout 场景
- 内存和 CPU 占用更可控
- 二进制部署简单
- 依赖少,更接近基础设施服务风格
- 后续扩到更高在线数更稳
如果目标是先稳定支撑 1000-2000 在线连接,推荐优先考虑:
- 服务端:
Go - 实时协议:
WebSocket - 管理接口:
HTTP - 配置来源:本地配置文件或环境变量
- latest state:进程内内存
原因:
- 长连接和 fanout 场景更贴近 Go 的强项
- 资源占用更稳
- 后续扩到更高并发成本更低
15.3 网关内部建议模块
为了保持“轻量但可扩展”,建议核心网关内部拆为以下模块:
listener- 管理 WebSocket 和 HTTP 入口
session manager- 管理连接、身份、心跳、会话元数据
router- 负责 topic、device、group 维度路由
fanout hub- 负责多订阅者分发
state cache- 保存设备 latest state
auth verifier- 校验 Producer、Consumer、Controller 的 token
rate limiter- 限制异常高频上报
plugin bus- 异步发布给规则、通知、归档等插件
这些模块都应保持进程内实现,避免第一版引入外部组件。
15.4 第一版不建议引入的依赖
第一版不建议引入:
- 数据库
- 消息队列
- 服务注册中心
- 复杂配置中心
- 脚本型规则执行器
- 重型 RPC 框架
这些能力都可以在后续容量和业务复杂度真正需要时再补。
15.5 可接受的替代方案
如果团队更熟悉 TypeScript,也可以先用:
Node.js + TypeScript + WebSocket
但要注意:
- 网关层应保持非常薄
- 不要过早塞业务逻辑
- 一开始就准备好以后拆分插件
但如果核心目标明确是“像软路由一样稳定跑实时中转”,仍然优先推荐 Go。
16. MVP 范围
第一版只做以下内容:
16.1 核心网关
- WebSocket 接入
- Producer / Consumer / Controller 三类角色
- 统一消息信封
- 按设备订阅
- latest state 缓存
- 心跳和断线处理
- 基础限流
- 基础鉴权
16.2 模拟器接入
- 地图拖点
- 轨迹文件导入
- 播放 / 暂停 / 倍速 / 循环
- 输出位置和心率等标准 telemetry
16.3 观察端
- 订阅设备位置和状态
- 查看最新点
- 接收实时事件
16.4 插件最小集
- Recorder
- 可选启用
- Rule Engine
- 先做最简单几条规则
- Dispatcher
- 先支持网关内消息通知
16.5 明确不做
- 业务数据库耦合
- 复杂多租户
- 大报表系统
- 历史查询中心
- 集群调度系统
17. 第二阶段演进
第二阶段建议逐步增加:
- 归档从客户端双写迁移到 Recorder
- 多实例网关
- Redis 作为共享状态和实例间消息桥
- 更细粒度权限
- 完整规则库
- 回放服务独立化
- 外部通知集成
第三阶段再考虑:
- 数据库落盘
- 历史检索
- 统计分析
- 高可用和地域分布
18. 最终结论
最终方案建议如下:
- 以“实时设备数据网关”作为系统核心,而不是继续围绕“GPS 模拟器”扩展
- 网关独立部署,不占主业务服务器的实时带宽和连接资源
- 第一版采用 0 数据库设计,仅保留运行时内存态
- 核心只做实时接入、标准化、路由、订阅和 latest state
- 规则判定、通知分发、回放、归档全部插件化
- 第一版先支撑
1000-2000在线上报 - 当前客户端可继续采用“实时发网关 + 10-30 秒批量发业务服”的过渡方案
- 中期应演进为“客户端单写网关,归档由 Recorder 插件完成”
这套方案能同时覆盖:
- 开发模拟
- 家长端监控
- 场控
- 规则判定
- 通知分发
- 数据回放
- 后续更多传感器接入
同时又能把实时性能放在系统设计的首位。