145 lines
5.7 KiB
Markdown
145 lines
5.7 KiB
Markdown
# 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` 去重 |
|
||
|
||
## 写命令语义
|
||
|
||
```text
|
||
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` 当前配置:
|
||
|
||
```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` | 指数退避上限 |
|
||
|
||
## 重试和死信
|
||
|
||
失败处理:
|
||
|
||
```text
|
||
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` |
|