6.3 KiB
6.3 KiB
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、更新 rooms 和 room_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_version和last_publish_event_time_ms用于丢弃旧 RTC 事件。pending_publish超过publish_deadline_ms后由 worker 自动下麦。LeaveRoom与 RTCroom_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_id和lease_token,用于排查谁在什么 lease 下提交了命令。- stale presence worker 和 mic publish timeout worker 只能扫描本节点已加载 Cell;清理前必须确认当前 Redis lease 仍属于本节点,并在读完快照后再次用相同
lease_token校验,最后由命令提交链路再做 fencing。 - 节点进入 draining 后不能再接收新的 Room Cell 命令,也不能恢复或创建新的 Cell;gRPC 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_id和lock_until_ms。- 其他 worker 不能抢到未过期的
delivering事件。 - 投递成功后才能标记
delivered。 - 投递失败必须保留 envelope 和 last_error,转入
retryable等待下一轮抢占。 - outbox 事件必须带
app_code,消费者按 App 隔离幂等。