# 实时设备数据网关最终方案 本文档用于收敛当前关于 GPS 模拟、中转、监控、规则判定、回放、通知分发等讨论,给出一版可直接进入实现设计的最终方案。 --- ## 1. 目标与定位 本系统不再定义为“GPS 模拟器”,而定义为: - 一个独立部署的实时设备数据网关 - 负责实时接入、标准化、路由、订阅、最新状态同步 - 默认不依赖数据库,不承担历史存储职责 - 通过插件扩展规则判定、通知分发、回放、归档等能力 一句话定义: > 一个以实时中转为核心、以插件扩展业务能力的轻量遥测网关。 --- ## 2. 设计原则 ### 2.1 核心优先级 核心目标按优先级排序如下: 1. 实时性能 2. 稳定性 3. 低耦合 4. 易扩展 5. 易观测 ### 2.2 核心边界 核心服务只负责: - 长连接接入 - 消息标准化 - 路由转发 - 订阅管理 - latest state 缓存 - 连接管理 - 心跳和断线清理 - 基础鉴权 - 限流与基本防护 核心服务不负责: - 历史存储 - 业务报表 - 复杂规则引擎 - 第三方通知发送 - 业务数据库查询 - 面向家长端或场控端的专属业务逻辑 --- ## 3. 总体架构 ### 3.1 拓扑关系 ```text 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 角色使用流程 #### 开发模拟 1. Controller 在管理台创建 `channel` 2. 网关返回 `channelId / producerToken / consumerToken` 3. 老模拟器作为 Producer 加入 channel 并开始发 `telemetry.location / telemetry.heart_rate` 4. 管理台和调试 Consumer 实时观察数据 5. 业务服务器此时可以不参与,或仅做低频归档 #### 家长端监控 1. 真机设备作为 Producer 向网关持续上报位置和状态 2. 家长端作为 Consumer 订阅某个 `deviceId` 或 `groupId` 3. 网关负责实时分发 4. 业务服务器负责账号关系、历史数据和业务页面 #### 场控 1. 场控端作为 Consumer 订阅多个设备或一个分组 2. 网关持续推送实时位置、状态和事件 3. 如果有控制需求,场控端再以 Controller 身份下发命令 4. 规则插件可基于同一条实时流做违规判定 #### 归档 1. Producer 只向网关上报实时数据 2. Recorder 插件异步消费网关流 3. 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 统一信封 所有进入网关的数据统一使用一个标准信封: ```json { "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.location` - `telemetry.heart_rate` - `telemetry.motion` - `state.device` - `event.rule_violation` - `event.sos` - `command.replay.start` - `command.simulation.stop` ### 7.3 source mode 建议显式区分数据来源模式: - `real` - `mock` - `replay` - `control` 这样业务侧可以明确识别是真实数据还是模拟/回放数据。 --- ## 8. 主要数据载荷 ### 8.1 位置 telemetry ```json { "lat": 31.2304, "lng": 121.4737, "altitude": 12.3, "speed": 1.5, "bearing": 90, "accuracy": 8, "coordSystem": "GCJ02" } ``` 最少保留字段: - `timestamp` - `lat` - `lng` - `speed` - `bearing` - `accuracy` - `coordSystem` ### 8.2 心率 telemetry ```json { "bpm": 142, "confidence": 0.92 } ``` ### 8.3 运动 telemetry ```json { "cadence": 168, "stepCount": 3201, "calories": 248.5, "movementState": "run" } ``` ### 8.4 设备状态 ```json { "online": true, "battery": 76, "network": "4g", "charging": false } ``` ### 8.5 事件 ```json { "eventType": "rule_violation", "ruleId": "geo-fence-01", "severity": "high", "text": "leave allowed area" } ``` ### 8.6 控制命令 ```json { "command": "simulation.start", "args": { "sessionId": "sim-001", "speedRate": 2 } } ``` --- ## 9. 订阅与路由模型 ### 9.1 路由单位 实时网关按以下维度路由: - `deviceId` - `groupId` - `topic` ### 9.2 推荐订阅粒度 - 订阅某个设备全部实时消息 - 订阅某个设备某类消息 - 订阅某个组某类消息 示例: - `device:child-001` - `device:child-001/topic:telemetry.location` - `group: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` - 低频或关闭实时上报 - `monitor` - `1-5s` 实时上报 - `debug` - `1 Hz` - `alert` - 临时升频 --- ## 14. 模拟与回放 ### 14.1 模拟器定位 模拟器不再是一个独立特殊系统,而是一个标准 Producer。 它可以上报: - `telemetry.location` - `telemetry.heart_rate` - `telemetry.motion` - `state.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 插件完成” 这套方案能同时覆盖: - 开发模拟 - 家长端监控 - 场控 - 规则判定 - 通知分发 - 数据回放 - 后续更多传感器接入 同时又能把实时性能放在系统设计的首位。