Skeyevss 技术分享:VSS 状态机设计

3 阅读3分钟

Skeyevss 技术分享:VSS 状态机设计

试用安装包下载 | SMS | 在线演示

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


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 关键事件通道

  • SipSendCatalogSipSendVideoLiveInviteSipSendBye
  • SipCatalogLoopSipHeartbeatLoop
  • SipLog

这套设计把“状态存储”和“状态驱动事件”分离,避免业务线程直接互锁。


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 关键转移规则(对应的实现)

  1. REGISTER 到来

    • 验证 ID/鉴权
    • 写库更新设备状态
    • Expire=0 => 离线,清理 catalog/heartbeat 任务
    • Expire>0 => 在线,创建 catalog + heartbeat 任务
  2. KEEPALIVE 到来

    • 更新 SipHeartbeatLoopMap.Now
    • 若服务重启导致 catalog 任务缺失,自动补建 catalog 任务
    • 投递 SetDeviceOnline 保持在线状态
  3. 定时离线检测(heartbeat_offline_loop

    • 条件:now - RegisterExpireAt > 10now - 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 并发保护与幂等策略

  1. InviteRequestState(防并发击穿)

    • 进入 invite 流程前先 Add(streamName)
    • 流程结束 defer Remove(streamName)
    • 已存在则直接拒绝重复 invite
  2. PubStreamExistsState(流存在状态)

    • Invite + Ack + ack_rtp_pub 成功后 Add(streamName)
    • 媒体回调 on_pub_stopRemove(streamName)
  3. 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. 事件源与状态更新

事件源典型事件更新状态关键动作
SIPREGISTER在线/离线、catalog/heartbeat建立或删除任务
SIPKEEPALIVE心跳时间戳、在线状态刷新超时计时
HTTP/gbs/inviteinvite请求中、流存在状态触发播放链路
HTTP/notify/on-pub-stop流存在状态删除流状态
定时器heartbeat loop在线状态超时置离线
定时器catalog loopcatalog任务状态周期发送catalog

7. 可观测性设计

VSS 状态机调试依赖三类可观测信号:

  1. SIP 报文日志

    • 实时(SSE)+ 文件日志
    • 关键链路:REGISTER / INVITE / ACK / BYE
  2. 状态容器监控

    • SipCatalogLoopMap 数量
    • SipHeartbeatLoopMap 数量
    • PubStreamExistsState 数量
  3. 媒体回调事件

    • on_pub_start / on_pub_stop / on_sub_start

8. 设计收益

  • 状态拆分明确,避免单点大锁
  • 事件通道削峰,便于并发处理
  • 通过媒体回调对流状态进行闭环修正

9. 总结

VSS 的状态机并不是一个对象、一个状态字段而是:

  • 设备状态机 管在线生命周期
  • 流状态机 管播放会话生命周期
  • 定时任务状态机 管 catalog/heartbeat 保活
  • 媒体回调 做最终一致性修正

这套组合式状态机设计,是 VSS 在复杂信令场景下保持稳定运行的关键。