hyapp-server/docs/房间Cell状态机.md
2026-05-12 21:51:39 +08:00

6.3 KiB
Raw Permalink Blame History

Room Cell State Machines

本文档定义 room-service 在微服务多机部署下必须遵守的 Room Cell 状态机。Room Cell 的目标是让同一个房间在同一时间只有一个执行节点提交状态并且在节点重启、发布、RTC 回调延迟和 outbox 重试时不丢状态、不双写、不重复扣费。

Room

Room 是房间生命周期的主状态。只有 active 房间允许进房、麦位、管理和送礼命令。

stateDiagram-v2
  [*] --> creating
  creating --> active: CreateRoom 持久化成功
  active --> closing: CloseRoom 开始收尾
  closing --> closed: command log/outbox/meta 提交
  active --> closed: CloseRoom 当前实现一次提交完成
  closed --> [*]

状态语义:

State Meaning
creating 正在创建房间外部依赖,不对客户端开放
active 可进房、可管理、可上麦、可送礼
closing 正在关闭,不再接收新业务命令
closed 已关闭,JoinRoom 和 guard 查询都必须拒绝

CloseRoom 必须作为 Room Cell 命令提交,不能只从后台直接更新 rooms.status。关闭房间时必须清空业务 presence、释放麦位、写 command log、写 RoomClosed outbox、更新 roomsroom_list_entries 状态。

Presence

Presence 是 room-service 自己维护的业务进房状态,不等同于腾讯云 IM 或 RTC 的真实连接。

stateDiagram-v2
  [*] --> absent
  absent --> present: JoinRoom
  present --> present: JoinRoom 重试或 RoomHeartbeat 刷新 last_seen_at_ms
  present --> left: LeaveRoom
  present --> stale_left: stale worker 超时清理
  present --> kicked: KickUser
  present --> closed_left: CloseRoom
  left --> present: JoinRoom
  stale_left --> present: JoinRoom
  kicked --> present: UnbanUser 后重新 JoinRoom
  closed_left --> [*]

规则:

  • RoomHeartbeat 只能刷新已有 presence不能创建 presence。
  • LeaveRoom 要同步释放用户占用的麦位。
  • RTC room_exited 只释放当前麦位,不清理业务 presence用户可能仍停留在房间页等待重连。
  • KickUser 写入 ban 集合,用户必须先 UnbanUser 才能重新 JoinRoom

Mic

Mic 是麦位和 RTC 发流确认状态。业务上麦不等于已经在 RTC 发流。

stateDiagram-v2
  [*] --> idle
  idle --> locked: SetMicSeatLock(true)
  locked --> idle: SetMicSeatLock(false)
  idle --> pending_publish: MicUp
  pending_publish --> publishing: ConfirmMicPublishing 或 RTC audio_started
  pending_publish --> idle: publish_timeout 或 MicDown 或 LeaveRoom
  publishing --> idle: MicDown 或 RTC audio_stopped 或 LeaveRoom
  publishing --> pending_publish: ChangeMicSeat 保留 mic_session
  pending_publish --> pending_publish: ChangeMicSeat 保留 mic_session

规则:

  • mic_session_id 是单次上麦会话 ID旧会话的确认、停止和超时任务不能清掉新会话。
  • mic_session_room_versionlast_publish_event_time_ms 用于丢弃旧 RTC 事件。
  • pending_publish 超过 publish_deadline_ms 后由 worker 自动下麦。
  • LeaveRoom 与 RTC room_exited 并发时以业务 presence 的 last_seen_at_ms 和当前 mic_session_id 为准;旧的 RTC 退房事件不能释放用户重进房后的新麦位会话。
  • CloseRoom 必须释放全部麦位,但保留麦位编号和锁定结构。

Lease

Lease 是多机部署下的房间执行权。Redis 只保存热路由不是恢复来源MySQL snapshot + command log 才是恢复来源。

stateDiagram-v2
  [*] --> unowned
  unowned --> owned_by_node: EnsureOwner
  owned_by_node --> owned_by_node: 同节点续约并生成新 lease_token
  owned_by_node --> expired: TTL 到期
  expired --> owned_by_other_node: 新节点 EnsureOwner
  owned_by_node --> fenced_out: 提交前 VerifyOwner 失败
  fenced_out --> [*]

规则:

  • Redis runtime route key 统一使用 app_code + "\0" + room_id,包括默认 App不能再使用裸 room_id 作为兼容 key。
  • room:node:{node_id} 保存当前 room-service 实例的 advertise_addr,只用于 owner 转发;它不是服务发现的全局配置中心。
  • gateway 可以连接 room-service 内网负载均衡;命中非 owner 节点时gRPC 边界必须按 Redis owner route 转发到当前 owner 实例。
  • advertise_addr 必须是实例私网地址,不能是 room-service 内网 CLB 地址,否则转发会命中任意节点并触发递归保护。
  • 命令开始前通过 EnsureOwner 获得执行权。
  • 命令真正写 command log/outbox 前必须用 lease_token 再做一次 VerifyOwner
  • 如果提交前 lease 已经过期或 token 已变化,旧节点必须返回 UNAVAILABLE,不能写房间状态。
  • room_command_log 保存 owner_node_idlease_token,用于排查谁在什么 lease 下提交了命令。
  • stale presence worker 和 mic publish timeout worker 只能扫描本节点已加载 Cell清理前必须确认当前 Redis lease 仍属于本节点,并在读完快照后再次用相同 lease_token 校验,最后由命令提交链路再做 fencing。
  • 节点进入 draining 后不能再接收新的 Room Cell 命令,也不能恢复或创建新的 CellgRPC drain 完成后应主动释放本节点已加载房间的 owner lease降低滚动发布等待 TTL 的窗口。

Outbox

Outbox 是房间事件对外投递状态机。它允许重复投递,但多 worker 不应该同时抢同一批。

stateDiagram-v2
  [*] --> pending
  pending --> delivering: ClaimPendingOutbox
  retryable --> delivering: ClaimPendingOutbox
  delivering --> delivered: 投递成功并确认
  delivering --> retryable: 投递失败或状态未知
  delivering --> failed: 明确不可重试
  retryable --> failed: 超过策略上限或人工判定

规则:

  • ClaimPendingOutbox 必须把事件从 pending/retryable 原子改成 delivering,并写入 worker_idlock_until_ms
  • 其他 worker 不能抢到未过期的 delivering 事件。
  • 投递成功后才能标记 delivered
  • 投递失败必须保留 envelope 和 last_error转入 retryable 等待下一轮抢占。
  • outbox 事件必须带 app_code,消费者按 App 隔离幂等。