hyapp-server/docs/房间Outbox补偿开发.md
2026-05-12 20:07:49 +08:00

5.7 KiB
Raw Blame History

Room Outbox Worker

本文档记录当前 room-service outbox 的实际语义。房间写命令只负责提交 Room Cell 状态、command log、snapshot 和 outbox腾讯云 IM、activity-service 等外部投递全部由 outbox worker 异步执行。

边界

项目 当前规则
主链路 不同步调用腾讯云 IM不同步投递 activity-service
持久化 命令变更、command log、room_outbox 在同一提交链路内完成
worker 读取 room_outbox,投递到配置的 outbox publisher fanout
腾讯云 IM RoomCreated 由 worker 确保群组存在,其他房间事件由 worker 发送群系统消息
activity-service 由 room outbox worker 异步投递,失败进入 room_outbox 重试
幂等 房间命令靠 command_idoutbox 投递靠 event_id

不做的事:

不做 原因
在 HTTP/gRPC 请求里等待 IM REST 外部慢调用会拖住房间串行命令队列
在 Room Cell 中保存投递状态 投递状态属于 outbox不属于房间权威状态
直接用 Redis 做 outbox MySQL 是恢复和补偿的持久来源
精确一次投递 外部系统和进程崩溃窗口只能做到 at-least-once消费端必须按 event_id 去重

写命令语义

gateway -> room-service command
  -> 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
  -> publish to composite outbox publisher
  -> delivered / retryable / failed

主链路成功只表示房间状态已经落库;不表示腾讯云 IM 或 activity-service 已经完成投递。客户端实时表现应以房间响应、房间 snapshot 和 IM 系统消息共同补偿。

数据状态

room_outbox.status 使用:

状态 含义
pending 已落库,尚未被 worker 处理
delivering 已被 worker 抢占,lock_until_ms 前其他 worker 不抢
delivered 所有配置的 outbox publisher 已处理成功
retryable 上次投递失败,等待 next_retry_at_ms 后重试
failed 达到最大重试次数,进入死信,需要人工处理或后台工具重放

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

配置

services/room-service/configs/*.yaml 当前配置:

snapshot_every_n: 10

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

配置规则:

字段 规则
snapshot_every_n 开发和生产默认 10;命令日志仍保证恢复,不能依赖每条命令快照
poll_interval worker 空轮询间隔,必须大于 0避免 busy loop
batch_size 每轮最大抢占条数,必须大于 0避免全表扫描
publish_timeout 单条 outbox 投递 deadline避免外部系统拖死 worker
retry_strategy 当前为 exponential_backoff
max_retry_count 达到次数后标记 failed,防止 poison event 永久污染日志
initial_backoff 第一次失败后的最小退避
max_backoff 指数退避上限

重试和死信

失败处理:

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 或生成明确的新修复事件,避免消费端无法去重。

投递对象

当前 CompositeOutboxPublisher fanout 到多个 publisher

Publisher 行为
Tencent IM RoomCreated 确保群存在;房间系统事件发送腾讯云 IM 群自定义消息
Activity 将 room outbox 事件投递给 activity-service 的消费入口

fanout 要求所有 publisher 成功后才标记 delivered。任一失败都会进入重试,因此消费端必须幂等处理重复事件。

阶段耗时日志

每条房间写命令都会输出 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 同步耗时;当前固定为 0
activity_async_outbox 本命令是否产生异步 activity/outbox 投递
tencent_im_async_outbox 本命令是否产生异步 IM/outbox 投递

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

验收

必须满足:

场景 验收
IM 不可用 房间写命令仍成功outbox 进入 retryable 或最终 failed
activity 不可用 房间写命令仍成功outbox 进入重试
poison event 达到 max_retry_count 后进入 failed,不再每秒刷日志
正常投递 同房间事件最终全部 delivered
真实链路 create/join/mic/sendGift/leave 均有 room_command_timing