Skeyevss 技术分享:VSS 状态机设计
1. 为什么 VSS 要做状态机
VSS 同时处理:
- SIP 注册/心跳
- Catalog 定时任务
- 实时播放 Invite/Ack
- 媒体回调(
on_pub_stop等) - WebSocket/SSE 实时状态同步
如果没有状态机约束,很容易出现:
- 重复 Invite(并发击穿)
- 流状态脏数据(实际停流但系统仍认为存在)
- 设备上下线抖动误判
因此 VSS 的核心设计是:事件驱动 + 状态缓存 + 定时校验。
2. 状态机实现骨架
VSS 不是单一大状态枚举,而是由多套状态集合组合构成。
2.1 关键状态容器(ServiceContext)
SipCatalogLoopMap:设备 catalog 定时任务状态SipHeartbeatLoopMap:设备心跳检测状态InviteRequestState:按streamName的 invite 并发保护PubStreamExistsState:流是否已成功建立AckRequestMap:保存可用于 BYE 的请求上下文SetDeviceOnline+DeviceOnlineStateUpdateMap:设备在线状态异步更新队列
2.2 关键事件通道
SipSendCatalog、SipSendVideoLiveInvite、SipSendByeSipCatalogLoop、SipHeartbeatLoopSipLog
这套设计把“状态存储”和“状态驱动事件”分离,避免业务线程直接互锁。
3. 设备生命周期状态机
3.1 状态图
stateDiagram-v2
[*] --> Unregistered
Unregistered --> Registering: REGISTER
Registering --> Online: 注册成功(Expire>0)
Registering --> Offline: 注销(Expire=0)
Online --> Online: KEEPALIVE刷新Now
Online --> Offline: 心跳超时/注册过期
Offline --> Online: REGISTER或有效KEEPALIVE
3.2 关键转移规则(对应的实现)
-
REGISTER 到来
- 验证 ID/鉴权
- 写库更新设备状态
Expire=0=> 离线,清理 catalog/heartbeat 任务Expire>0=> 在线,创建 catalog + heartbeat 任务
-
KEEPALIVE 到来
- 更新
SipHeartbeatLoopMap.Now - 若服务重启导致 catalog 任务缺失,自动补建 catalog 任务
- 投递
SetDeviceOnline保持在线状态
- 更新
-
定时离线检测(
heartbeat_offline_loop)- 条件:
now - RegisterExpireAt > 10或now - Now >= HeartbeatTimeout - 动作:移除
SipHeartbeatLoopMap记录并置设备离线
- 条件:
4. 流生命周期状态机(播放核心)
4.1 状态图
stateDiagram-v2
[*] --> Idle
Idle --> Precheck: 收到播放请求(/gbs/invite)
Precheck --> InviteLocked: 参数校验通过\nDeviceUniqueId/ChannelID/PlayType
InviteLocked --> DeviceOnlineChecked: InviteRequestState.Add(streamName)
DeviceOnlineChecked --> StreamProbe: 设备在线 + 已有SipCatalog上下文
StreamProbe --> ReuseStream: SMS中已存在有效Pub(SessionID等有效)
ReuseStream --> Streaming: 直接复用已有流(短路返回)
StreamProbe --> StaleStreamFix: PubStreamExistsState存在\n但SMS无有效SessionID
StaleStreamFix --> MSReady: stop_stream纠偏完成
StreamProbe --> MSReady: 没有流,进入新建流流程
MSReady --> StartRtpPubSent: VSS->SMS start_rtp_pub
StartRtpPubSent --> InviteSent: start_rtp_pub成功
StartRtpPubSent --> Rollback: start_rtp_pub失败
InviteSent --> WaitDevice200: VSS->设备 INVITE
WaitDevice200 --> Device200OK: 设备返回200 OK
WaitDevice200 --> Rollback: INVITE超时/非200
Device200OK --> SDPParsed: 解析SDP(IP/Port/filesize)
SDPParsed --> AckSent: VSS->设备 ACK
SDPParsed --> Rollback: SDP解析失败
AckSent --> DevicePushing: ACK后设备向SMS推RTP
AckSent --> Rollback: ACK发送失败
DevicePushing --> WaitPubStart: 等待SMS on_pub_start/可用会话
WaitPubStart --> AckRtpPubSent: VSS->SMS ack_rtp_pub
WaitPubStart --> Rollback: 设备未推流/推流超时
AckRtpPubSent --> StreamMarked: ack_rtp_pub成功
AckRtpPubSent --> Rollback: ack_rtp_pub失败
StreamMarked --> Streaming: PubStreamExistsState.Add(streamName)\nAckRequestMap.Set(streamName)
Streaming --> Stopping: stop_stream / BYE / on_pub_stop / 无人观看超时
Stopping --> SendBye: 向设备发送BYE(若有ACK上下文)
SendBye --> StopSMS: 通知SMS停流
StopSMS --> WaitPubStop: 等待on_pub_stop回调
WaitPubStop --> Cleanup: PubStreamExistsState.Remove\nAckRequestMap.Remove
Cleanup --> Idle: InviteRequestState.Remove(streamName)
state Rollback {
[*] --> RollbackEntry
RollbackEntry --> Fail
Fail --> CleanupFailedState: 清理临时状态\nInviteRequestState.Remove\n必要时stop_stream
CleanupFailedState --> RollbackExit
RollbackExit --> [*]
}
Rollback --> Idle: 回滚完成
4.2 并发保护与幂等策略
-
InviteRequestState(防并发击穿)
- 进入 invite 流程前先
Add(streamName) - 流程结束
defer Remove(streamName) - 已存在则直接拒绝重复 invite
- 进入 invite 流程前先
-
PubStreamExistsState(流存在状态)
- Invite + Ack +
ack_rtp_pub成功后Add(streamName) - 媒体回调
on_pub_stop时Remove(streamName)
- Invite + Ack +
-
AckRequestMap(回收资源)
- 保存 ACK 请求上下文
- BYE/stop_stream 时用于回收会话
4.3 “流占用修正”机制
若 PubStreamExistsState 认为流存在,但媒体侧查不到有效 SessionID,系统会触发本地 stream/stop 进行纠偏,避免僵尸状态长期占用流名。
5. Catalog 与心跳两个辅助状态机
5.1 Catalog 任务状态机
stateDiagram-v2
[*] --> NoTask
NoTask --> TaskActive: 设备上线/注册
TaskActive --> TaskActive: 达到CatalogInterval触发发送
TaskActive --> NoTask: 设备下线/注销
对应容器:SipCatalogLoopMap
对应动作:定时向设备发送 catalog 查询。
5.2 心跳状态机
stateDiagram-v2
[*] --> NoHeartbeat
NoHeartbeat --> HeartbeatActive: 注册成功
HeartbeatActive --> HeartbeatActive: Keepalive刷新Now
HeartbeatActive --> TimeoutOffline: 超过HeartbeatTimeout或注册过期
TimeoutOffline --> NoHeartbeat: 清理心跳状态
对应容器:SipHeartbeatLoopMap
6. 事件源与状态更新
| 事件源 | 典型事件 | 更新状态 | 关键动作 |
|---|---|---|---|
| SIP | REGISTER | 在线/离线、catalog/heartbeat | 建立或删除任务 |
| SIP | KEEPALIVE | 心跳时间戳、在线状态 | 刷新超时计时 |
| HTTP | /gbs/invite | invite请求中、流存在状态 | 触发播放链路 |
| HTTP | /notify/on-pub-stop | 流存在状态 | 删除流状态 |
| 定时器 | heartbeat loop | 在线状态 | 超时置离线 |
| 定时器 | catalog loop | catalog任务状态 | 周期发送catalog |
7. 可观测性设计
VSS 状态机调试依赖三类可观测信号:
-
SIP 报文日志
- 实时(SSE)+ 文件日志
- 关键链路:REGISTER / INVITE / ACK / BYE
-
状态容器监控
SipCatalogLoopMap数量SipHeartbeatLoopMap数量PubStreamExistsState数量
-
媒体回调事件
on_pub_start/on_pub_stop/on_sub_start
8. 设计收益
- 状态拆分明确,避免单点大锁
- 事件通道削峰,便于并发处理
- 通过媒体回调对流状态进行闭环修正
9. 总结
VSS 的状态机并不是一个对象、一个状态字段而是:
- 以 设备状态机 管在线生命周期
- 以 流状态机 管播放会话生命周期
- 以 定时任务状态机 管 catalog/heartbeat 保活
- 以 媒体回调 做最终一致性修正
这套组合式状态机设计,是 VSS 在复杂信令场景下保持稳定运行的关键。