直播 INVITE 防击穿与流状态去重
背景
同一路 实时预览 可能被多个客户端同时点击,或前端重试导致 重复 INVITE。若不加控制,会对设备连续发多路 INVITE、对流媒体重复 start_rtp_pub,造成信令混乱与媒体资源浪费。本仓库用 进程内集合 做「互斥键」与「流是否已存在」判断。
项目中的做法
1. InviteRequestState:按 streamName 防并发击穿
在 video_live_invite 逻辑中:
- 若
InviteRequestState.Contains(streamName),直接拒绝或短路后续流程; - 否则
Add(streamName),并用defer Remove(streamName)保证退出路径释放。
同一流名在同一时刻只允许一条 INVITE 流水线,避免信令风暴。
flowchart TD
Req["预览请求 streamName"]
Req --> C{"InviteRequestState\nContains?"}
C -->|是| Rej["拒绝/快速返回"]
C -->|否| Add["Add + defer Remove"]
Add --> Inv["INVITE 流程"]
2. PubStreamExistsState:流已存在时跳过重复建流
发送侧在推流建立成功路径会 PubStreamExistsState.Add(req.StreamName);on_pub_stop 等通知里 Remove。邀请逻辑里会结合流媒体查询判断 是否已有 Pub,避免重复发布。
3. FetchDeviceVideoState:录像能力查询互斥
device_videos 中若某 设备+通道 已在拉取录像列表,后续请求等待或短路,避免对同一设备并发 QueryRecord 打爆。
flowchart LR
subgraph Keys["互斥/去重键"]
A["streamName\n直播"]
B["device+channel\n录像查询"]
end
subgraph Sets["CSet 集合"]
I["InviteRequestState"]
P["PubStreamExistsState"]
F["FetchDeviceVideoState"]
end
A --> I
A --> P
B --> F
要点
defer Remove必须覆盖错误分支:否则异常退出会 永久占位,后续同流永远无法 INVITE(卡死现象)。合理使用defer,扩展时务必注意。- 集合容量:
set.New[string](1000)等为预分配,流名极多时关注 内存与清理 是否及时(依赖bye/stop_stream/on_pub_stop)。 - 多实例部署:上述集合 仅在单进程内有效;多 VSS 实例前需 负载均衡粘性 或 分布式锁/Redis,否则防重失效。
相关代码路径
core/app/sev/vss/internal/logic/http/gbs/video_live_invite.gocore/app/sev/vss/internal/logic/gbs_proc/send_sip_proc.go—PubStreamExistsState.Addcore/app/sev/vss/internal/logic/http/notify/on_pub_stop.go—Removecore/app/sev/vss/internal/logic/http/base/device_videos.go—FetchDeviceVideoState