`FetchDataLogic`:VSS 统一定时数据获取

2 阅读4分钟

FetchDataLogic:VSS 统一定时数据获取

本文对应源码 core/app/sev/vss/internal/logic/proc/fetch_data_proc.go,将说明它在进程里做了哪些事为什么要集中在一个逻辑里按时间表拉取,以及典型场景(ONVIF 发现设备在线状态快照等)如何体现「少请求DB/少外部依赖、多读内存」的思路。

项目地址 github.com/openskeye/g…


1. 本质

FetchDataLogic 实现 types.SipProcLogic,在 VSS 启动时与其它 SIP 协程任务一起 DO 常驻运行。可以概括为:

  1. 启动阶段两条并行分支并行拉第一批数据,并通过 InitFetchDataState 通知全进程「可调业务」。
  2. 运行阶段每秒Ticker 唤醒一次,按 不同周期 异步触发各类 「刷新 ServiceContext 里缓存字段」 的函数。

也就是说:它是 ServiceContext——字典、平台设置、媒体节点列表、ONVIF 局域网发现结果、设备/通道在线状态快照等, 都尽量 在这里统一更新,供 HTTP / SIP / SSE / WS 等逻辑 直接读内存,而不是每个请求各自去 RPC 或扫库。


2. 启动

WaitGroup: 两个 goroutine
├── goroutine A: dictionaries() → InitFetchDataState.Done()(sync.Once)
└── goroutine B: setting() → mediaServers() → onvifDiscover() → deviceOnlineState()
                → InitFetchDataState.Done()(sync.Once)

wg.Wait() 后进入下方定时循环
  • dictionaries()

    • RPC:Config.DictionaryTrees
    • 写入:svcCtx.DictionaryMap(按字典 uniqueId 构建的map)
  • setting()

    • RPC:Config.SettingRow
    • rule.NewConfig(...).Conv().Setting() 与本地 Config 合并后写入 svcCtx.Setting
    • 后序任务依赖「设置已就绪」,因此放在同一分支里 先于 mediaServers
  • mediaServers()

    • RPC:Config.MsList(媒体服务器node列表)
    • 写入:svcCtx.MediaServerRecords
  • onvifDiscover()

    • 设备探测 ONVIF 设备,解析 SOAP,写入 svcCtx.OnvifDiscoverDevices
    • 下一节将详细介绍。
  • deviceOnlineState()

    • RPC:Device.OnlineState(DB 聚合设备/通道 online 列表)
    • 写入:svcCtx.DeviceOnlineState*DeviceOnlineStateResp,设备 map + 通道 map)

InitFetchDataState 作为全局数据基础,在入口程序中需要先行完成,其它模块才能放行(例如 SIP ListenSendLogic 等),避免启动程序错乱。


3. 定时循环

for v := range time.NewTicker(time.Second).C,用 Unix 秒做取模区分不同场景等数据获取:

每次触发都是 异步执行,不阻塞下一秒的 Ticker,避免某次 RPC 或 UDP 发现拖死调度。

这里需要注意的是,每一个携程中没有做状态处理机制,如果说当前周期执行时间较长,下一个(n)周期完成后的数据可能将会被覆盖,这里可以自行优化以下。


4. 典型案例:ONVIF设备发现

  1. 按配置 Onvif.MulticastIPWsDiscoveryPort 发送固定 XML wsDiscoveryMessageNetworkVideoTransmitter)。
  2. 遍历网卡 JoinGroup / SetMulticastInterface,提高多网卡环境下收到应答的概率。
  3. DiscoveryTimeout 内收 UDP 包,解析 types.OnvifWSDiscoveryResponse,从 Scopes / XAddrs 等抽出名称、厂商、型号、IP、服务 URL 等。
  4. 使用包级 onvifKeyMaps:对 EndpointReference.Address 稳定映射到本服务生成的 UUID,便于列表里去重与展示主键一致。
  5. 最终 svcCtx.OnvifDiscoverDevices = records(切片替换)。

为什么放在 FetchData 里、而不是每个 HTTP「搜索设备」再 discover 一次?

  • 发现涉及 组播、多网卡、超时等待、XML 解析成本高、耗时长、结果短时内相对稳定
  • 集中 每 2 分钟刷一次(可再调配置),前端或其它接口 只读 OnvifDiscoverDevices,体验上是「列表可能最多延迟一个周期」,但换来极快的数据响应。
  • 这与「减少对DB的频繁/重复查询」是同一思路:把重 I/O / 重计算挪到后台周期任务,请求路径直接读取内存数据

5. 与 DB RPC 的关系

FetchDataLogic 自身 仍会调 RPC(因此 DB 侧仍会被访问),但 频次被限制为周期任务,且 单份缓存服务多消费者

  • 设备/通道在线状态deviceOnlineState 每 10 秒拉全量 map 到 DeviceOnlineState。例如 SSE device_online_state、诊断页等若都改为只读该字段,就不会每个 SSE 连接或每个 HTTP 请求都打一遍 OnlineState
  • 字典 / 设置 / 媒体服务每 4 秒刷新,业务代码在热路径上读 DictionaryMapSettingMediaServerRecords,避免散落各处的「每次来一下 SettingRow」。

权衡

  • 优点:请求延迟低、后端负载平稳、代码边界清晰(获取数据优先看 FetchDataLogic 填充的字段)。
  • 代价:数据延迟 TTL(4s / 10s / 120*s 不等),强一致场景仍需直连DB RPC读取;ONVIF 列表也可能短暂滞后于真实网络。

6. 小结

方法主要依赖缓存位置默认刷新节奏
dictionariesConfig RPCDictionaryMap启动 + 每 4s
settingConfig RPCSetting启动 + 每 4s
mediaServersConfig RPCMediaServerRecords启动 + 每 4s
onvifDiscoverUDP 组播 + XMLOnvifDiscoverDevices启动 + 每 120s
deviceOnlineStateDevice RPCDeviceOnlineState启动 + 每 10s

7. 源码与索引

  • fetch_data_proc 本文件:core/app/sev/vss/internal/logic/proc/fetch_data_proc.go
  • 初始化:core/app/sev/vss/main.goInitFetchDataState.Add(2)
  • 消费 DeviceOnlineState 的典型逻辑:core/app/sev/vss/internal/logic/sse/device_online_state.go
  • 设备/通道在线:写入路径与详细解释见:设备与通道在线状态.md

FetchDataLogic 是VSS的 统一定时数据入口——把字典、配置、媒体节点、ONVIF 发现、在线状态等 按不同周期刷新进 ServiceContext,让业务在请求路径上 多读内存、少重复访问 DB/RPC/网络,ONVIF 则是其中最典型例子。