hyapp-server/docs/房间Outbox补偿开发.md

10 KiB
Raw Permalink Blame History

Room Outbox Worker

本文档定义当前 room-service outbox 的实际语义。房间写命令只负责提交 Room Cell 状态、command log、snapshot 和 room_outbox;腾讯云 IM、activity-service、notice-service、后续 push/inbox 等外部副作用都通过 outbox worker 或 RocketMQ 下游 consumer 异步完成。

核心结论

项目 当前规则
状态 owner Room Cell 和 MySQL snapshot/command log 是房间状态权威来源
持久事实 room_outbox 是房间外事件的持久事实源,和房间命令提交链路一起落 MySQL
发布模式 outbox_worker.publish_mode 支持 directmqdual
MQ 职责 RocketMQ 只做事件分发和火箭延迟唤醒,不保存房间核心状态
下游语义 下游服务按 event_id 幂等消费,失败由自己的位点、重试和死信处理
主链路 HTTP/gRPC 请求不等待 IM REST、activity、notice、push 或 inbox 投递完成

不做的事:

不做 原因
在 Room Cell 中保存投递状态 投递状态属于 outbox 或下游消费位点,不属于房间权威状态
用 Redis 替代 outbox Redis 只保存热状态和短 TTL lease不能作为恢复事实来源
用 RocketMQ 替代 room_outbox 房间状态提交成功但 MQ 发布失败时,必须能从 MySQL 补偿发布
追求精确一次投递 外部系统和进程崩溃窗口只能做到 at-least-once消费者必须按 event_id 去重

写命令链路

gateway -> room-service command
  -> command_id idempotency check
  -> ensure Redis lease and Room Cell
  -> mutate cloned RoomState
  -> optional wallet debit before room gift state is committed
  -> SaveMutation(command log + room_outbox + room meta)
  -> snapshot when version hits snapshot_every_n
  -> update list/presence projections best effort
  -> return response

outbox worker
  -> claim pending/retryable rows
  -> if RoomRocketIgnited, schedule rocket launch delayed wakeup when configured
  -> publish by mode:
       direct: Tencent IM publisher + activity gRPC publisher
       mq:     RocketMQ room_outbox publisher
       dual:   direct publishers + RocketMQ publisher
  -> delivered / retryable / failed

主请求成功只表示房间状态已经提交;不表示腾讯云 IM、activity-service、notice-service 或任何后续通知已经投递完成。客户端实时表现应以请求响应、房间 snapshot 和腾讯云 IM 系统消息共同补偿。

发布模式

publish_mode 行为 适用场景
direct worker 直接调用腾讯云 IM publisher 和 activity gRPC publisher所有 direct publisher 成功后才标记 delivered 本地开发、未接 MQ 的隔离测试
mq worker 只把 room_outbox_event 发布到 hyapp_room_outboxRocketMQ 接收成功后标记 delivered 线上推荐模式activity、notice、IM bridge 各自 consumer group 解耦消费
dual 同时 direct 投递和 MQ 发布,全部成功后标记 delivered 迁移或灰度期;要求所有消费端都能按 event_id 去重

mq 模式下,room_outbox.status=delivered 的含义是“事件已经可靠进入 RocketMQ”不是“所有下游都已处理成功”。下游的成功、重试和死信由各自服务的消费位点负责不能反写 room_outbox.status

数据状态

room_outbox.status 使用:

状态 含义
pending 已落库,尚未被 worker 处理
delivering 已被 worker 抢占,lock_until_ms 前其他 worker 不抢
delivered 当前发布模式要求的 publisher 已处理成功MQ 模式下表示 RocketMQ 已接收
retryable 上次发布失败,等待 next_retry_at_ms 后重试
failed 达到最大重试次数,进入死信,需要后台工具查看或重放

旧数据里可能存在历史 published 状态。当前 worker 不再写这个状态,新链路只写上表状态。

RocketMQ Topics

Topic Producer Consumer Group 语义
hyapp_room_outbox room-service outbox worker hyapp-activity-room-outbox activity-service 消费房间事实,做活动、播报、系统消息等后续处理
hyapp_room_outbox room-service outbox worker hyapp-notice-room-outbox notice-service 消费私有通知相关事实,例如 RoomUserKicked
hyapp_room_outbox room-service outbox worker hyapp-room-im-bridge room-service IM bridge 消费房间群系统消息和群成员控制事实
hyapp_room_rocket_launch room-service rocket scheduler hyapp-room-rocket-open 火箭倒计时到点唤醒 Room Cell 发射命令

hyapp_room_rocket_launch 不是业务状态源。消息只携带 app_code、room_id、rocket_id、level、launch_at_ms、command_id 等唤醒参数;真正能否发射仍由 room-service 加载 Room Cell 后校验当前火箭状态、等级、rocket_idlaunch_at_ms 和 UTC 重置边界。

配置

services/room-service/configs/*.yaml 需要同时维护本地、Docker 和腾讯云示例配置:

rocketmq:
  enabled: false
  name_servers: []
  name_server_domain: ""
  access_key: ""
  secret_key: ""
  namespace: ""
  send_timeout: "3s"
  retry: 2
  room_outbox:
    enabled: false
    topic: "hyapp_room_outbox"
    producer_group: "hyapp-room-outbox-producer"
    tencent_im_consumer_enabled: false
    tencent_im_consumer_group: "hyapp-room-im-bridge"
    consumer_max_reconsume_times: 16
  rocket_launch:
    enabled: false
    topic: "hyapp_room_rocket_launch"
    producer_group: "hyapp-room-rocket-open-producer"
    consumer_group: "hyapp-room-rocket-open"
    consumer_max_reconsume_times: 16

outbox_worker:
  enabled: true
  publish_mode: "direct"
  poll_interval: "1s"
  batch_size: 100
  publish_timeout: "3s"
  retry_strategy: "exponential_backoff"
  max_retry_count: 10
  initial_backoff: "5s"
  max_backoff: "5m"

配置规则:

字段 规则
rocketmq.enabled 控制 RocketMQ client 是否启用;本地默认关闭,线上示例开启
rocketmq.room_outbox.enabled 控制 room outbox 是否可发布到 MQpublish_mode=mq/dual 时必须开启
rocketmq.room_outbox.tencent_im_consumer_enabled 控制 room-service 是否作为 IM bridge consumer 消费 hyapp_room_outbox
rocketmq.rocket_launch.enabled 控制火箭点火后是否发布和消费延迟发射唤醒消息
outbox_worker.publish_mode 本地默认 direct,线上建议 mq,迁移期可用 dual
poll_interval worker 空轮询间隔,必须大于 0避免 busy loop
batch_size 每轮最大抢占条数,必须大于 0避免全表扫描
publish_timeout 单条 outbox 发布 deadline避免外部系统拖死 worker
max_retry_count 达到次数后标记 failed,防止 poison event 永久刷日志

投递对象

Publisher / Consumer 行为
RocketMQ room outbox publisher 把 protobuf outbox 包装成统一 room_outbox_event,发布到 hyapp_room_outbox
Tencent IM direct publisher RoomCreated 对已同步创建的房间群执行幂等 EnsureGroup房间系统事件发送腾讯云 IM 群自定义消息
Activity direct publisher 本地或未接 MQ 时直接调用 activity-service room event 消费入口
Rocket launch scheduler RoomRocketIgnited 后发布到点延迟消息,唤醒 room-service 发射
Activity MQ consumer 独立 consumer group 消费 hyapp_room_outbox,按 event_id 幂等推进活动/播报逻辑
Notice MQ consumer 独立 consumer group 消费 hyapp_room_outbox,写 notice_delivery_events 后投 C2C 私有通知
Room IM bridge MQ consumer 独立 consumer group 消费 hyapp_room_outbox,投递房间群系统消息和群成员控制

同一个事件允许被多个 consumer group 消费。任何 consumer 失败都不能阻塞 Room Cell 主链路,也不能影响其它 consumer group 的进度。

重试和死信

发布失败处理:

publish failed
  -> retry_count + 1
  -> if retry_count >= max_retry_count: status=failed
  -> else: status=retryable, next_retry_at_ms=now+backoff

failed 不再自动重试。后台应提供人工查看和重放工具,重放时必须保留原 event_id 或生成明确的新修复事件,避免消费端无法去重。

RocketMQ consumer 失败时不修改 room_outbox。consumer 应依赖 RocketMQ 重投、自己的幂等表和死信处理;对于已经执行业务副作用但 ACK 失败的窗口,下一次消费必须按 event_id 安全去重。

阶段耗时日志

每条房间写命令都会输出 room_command_timing

字段 含义
idempotency_ms command log 幂等检查耗时
lease_ms Redis lease / Room Cell 装载耗时
wallet_debit_ms 送礼扣费耗时;非送礼为 0
save_mutation_ms command log + outbox + 房间 meta 提交耗时
snapshot_ms 快照持久化耗时;未触发为 0
projection_ms 列表和 presence 投影耗时
activity_ms 主链路 activity 同步耗时;当前固定为 0
tencent_im_ms 主链路腾讯 IM 同步耗时CreateRoom 记录同步建群耗时,其余命令当前为 0
activity_async_outbox 本命令是否产生异步 activity/outbox 投递
tencent_im_async_outbox 本命令是否产生异步 IM/outbox 投递

排查接口卡顿时先看 total_ms,再看各阶段字段。送礼慢通常优先看 wallet_debit_ms,普通房间命令慢优先看 save_mutation_mssnapshot_msprojection_ms

验收

场景 验收
IM 不可用,direct/dual 房间写命令仍成功outbox 进入 retryable 或最终 failed
activity 不可用,direct/dual 房间写命令仍成功outbox 进入重试
MQ 不可用,mq/dual 房间写命令仍成功outbox 进入重试MQ 恢复后补发
activity consumer 不可用,mq room_outbox 不回退RocketMQ 和 activity 消费位点负责积压与重试
poison event 达到 max_retry_count 后进入 failed,不再每秒刷日志
火箭倒计时 RoomRocketIgnited 发布延迟唤醒;到点后 room-service 重新校验状态再发射
正常投递 同一 event_id 可被多个 consumer group 独立消费且重复投递不产生重复副作用
真实链路 create/join/mic/sendGift/leave 均有 room_command_timing