5.7 KiB
5.7 KiB
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_id,outbox 投递靠 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_ms、snapshot_ms 和 projection_ms。
验收
必须满足:
| 场景 | 验收 |
|---|---|
| IM 不可用 | 房间写命令仍成功,outbox 进入 retryable 或最终 failed |
| activity 不可用 | 房间写命令仍成功,outbox 进入重试 |
| poison event | 达到 max_retry_count 后进入 failed,不再每秒刷日志 |
| 正常投递 | 同房间事件最终全部 delivered |
| 真实链路 | create/join/mic/sendGift/leave 均有 room_command_timing |