开发心得:信令发送流水线
本文是 2.4.1.0 VSS流播放详解 的配套开发笔记。
1. 三个要点
- 国标出站 SIP 不要绕过
SendLogic:业务、HTTP、定时器只往svcCtx.SipSendXXX里投递;直接拿gosip在别处Send,等于拆掉「单写出口」,并发事务和排障都会变差。 types.Request+SipCatalogLoopMap是设备真实来源:没注册进 map,大量 HTTP/信令路径会DeviceUnregistered;NewGBSSender第三参在 Invite 等场景常是通道 ID,与 Catalog 用的设备 ID 别混(详见 5.1)。SipSend*channel 有界(默认 100):洪峰时<-会阻塞生产者;要考虑节流、异步、或扩容缓冲,别假设「发进 channel 就等于已经发出 SIP」。
2. 总线模型:一条 select,多路投递
for {
select {
case v := <-l.svcCtx.SipSendCatalog:
go func(v *types.Request) {
if err := l.catalog(v); err != nil {
functions.LogError("send catalog failed err: ", err)
}
}(v)
// ... SipSendDeviceInfo / VideoLiveInvite / ...
}
}
- 外层一条
select:保证同一时刻只从一个SipSend*取一条;不会在select里做重逻辑。 - 内层普遍
go func:Invite / MS / SDP 慢指令不卡住其它信令;代价是 错误大多只打日志,HTTP 层拿不到SendLogic的 error—— heart flow 要靠StepRecord/日志/MS 状态(见 §8)。
3. InitFetchDataState.Wait():和谁对齐?
SendLogic 与其它 gbs_proc 一样在 DO 开头 Wait():FetchData 两条分支各 Done 一次之后才消费 SipSend*。
心得:冷启动结束前,往 SipSendCatalog 里塞任务可能被长时间阻塞在 send 侧(若 nobody 消费);集成测试里要 先等数据就绪 或 mock 缩短启动链。
4. Catalog
与 2.4.1.2 及 catalog_loop.go 一致:
| 来源 | 行为 |
|---|---|
| 注册成功(+1s) | SipSendCatalog 立即 + SipCatalogLoop 登记 |
| 心跳 | map 无记录时 补 Catalog + 补 loop |
CatalogLoop.proc | 每秒扫表,判据 item.Now%val.Unix()==CatalogInterval 在常见配置下几乎不命中 |
规划时每 N 秒自动 Catalog,只改 YAML 不够,要对齐 proc;否则线上目录仍主要靠 注册/心跳/手工。若要严格周期,需要改 catalog_loop.go 或另起定时任务往 SipSendCatalog 投递。
5. ThrottleFixedGridTrailing:Catalog 上的节流器
catalog 里对 同一 req.ID 做 约 3 秒栅格合并,避免短时间多次 Catalog 打爆设备。
- 调试时狂点「刷新目录」,可能只看到 最后一次 真正发出;前后请求在日志里像是被丢弃了。
- 别在节流键里用错 ID(应用 设备国标 ID,与
NewGBSSender(..., req, req.ID)一致)。
6. SipCatalogLoopMap:扩展 HTTP 时的必查点
大量 video_live_invite、手工 Catalog、部分诊断 都是 SipCatalogLoopMap.Get(deviceUniqueId)。
- 设备 刚上线 1s 内、或 心跳未补 loop 前,map 可能暂时没有;前端连点播放会得到 未注册。
- 注销路径要
SipCatalogLoop Online:false,否则恶意请求仍可能往里塞(与register.go一致性有关)。
7. VideoLiveInvite:不在 GBSSender 里结束
流水线:MS RTPPub → INVITE → ACK → 解析 200 SDP → MS ACKRtpPub → 状态位。并写入 AckRequestMap 供 BYE。
- Invite 成功不等于浏览器已能播;播放器问
/video/stream,信令问 SIP/MS。见 [5.4 流播放开发心得]。 - From/To/Via/SDP 填错全是身份混淆问题,见 5.1 / 2.4.1.1 SDP。
8. 错误可见性
SipSendVideoLiveInvite <- msg只说明 任务进入总线;真正失败可能在VideoLiveInvite内(MS、SIP 状态码、SDP 解析),最终LogError。inviteStep/StepRecord:给前端 时间线;若产品要 HTTP 同步失败码,要在 Invite 之前 或 在轮询、WS设计,不能假设SendLogic会把 error 抛回 HTTP。
9. 新增一种出站信令时
types.ServiceContext增加SipSendXxx chan *YourReq(容量根据需求约定,默认 100)。service_context.NewServiceContext里make(chan ...)。SendLogic.DO增加case:go func+ 独立SendLogic方法。- 业务侧只做
svcCtx.SipSendXxx <- ...,禁止复制粘贴GBSSender.Send到 handler。 - 慢调用必须
context/超时 或可取消,避免 goroutine 堆积。 - 若依赖 已注册:先
SipCatalogLoopMap.Get再组GBSSender。
10. 联调清单
- 新逻辑是否只通过已有
SipSend*发出 SIP? - 是否误用
NewGBSSender第三参(设备 vs 通道)? - 是否会在 channel 满 时阻塞 SIP 收包线程 / HTTP 请求?
- Catalog/目录类是否接受 3s 节流、定时 proc 可能不触发 的现状?
- Invite/Media 类是否验证
AckRequestMap清理(BYE、stop_stream)无泄漏? -
InitFetchDataState与集成测试启动顺序是否一致?
11. 相关文档
| 文档 | 内容 |
|---|---|
| 2.4.1.2 信令发送流水线 | 正文设计与表 |
| 2.4.1.4 国标设备注册 | 注册与 map 写入 |
| 5.1 信令与 SDP | INVITE 头与 SDP |
| 5.0 ServiceContext | channel / map 总览 |
主源码:internal/logic/gbs_proc/send_sip_proc.go、catalog_loop.go、internal/pkg/sip/gbs_send.go、internal/svc/service_context.go。