更新说明
All checks were successful
ci/woodpecker/push/my-first-workflow Pipeline was successful

This commit is contained in:
xinian
2026-04-09 13:11:59 +08:00
parent 487ee0e726
commit d2cd601802
6 changed files with 442 additions and 24 deletions

View File

@@ -0,0 +1,224 @@
# PVP Match Via RPC, Battle Via Redis
## 目标
本次调整先不解决 `login` 更新期间的排队保活和补偿问题只收敛到一个更简单可控的方案
- 匹配请求走 `logic -> login` 的同步 RPC
- 对战过程仍走 `logic` 本地战斗 + Redis 转发战斗指令
- `login` 不可用时`logic` 直接返回匹配服务不可用
- 前端通过轮询重新发起 / 更新匹配请求不在后端保留离线补偿队列
这个方案的核心是先把能否立即判断匹配服务可用做好不继续依赖 Redis PubSub 做匹配入口
## 当前现状
### 现有匹配入口
- 前端 `2458` 进入 [logic/controller/fight_巅峰.go](/workspace/logic/controller/fight_巅峰.go#L19)
- 当前 `JoINtop` 直接调用 [logic/service/fight/pvp/service.go](/workspace/logic/service/fight/pvp/service.go#L83) `JoinPeakQueue`
- `JoinPeakQueue` 当前实现是本地建 `localQueueTicket`并通过 Redis `publish` `queue_join`
### 现有跨服协调
- `logic` 侧订阅 PVP Redis topic 的入口在 [common/rpc/func.go](/workspace/common/rpc/func.go#L153)
- PVP 匹配状态当前存在 `logic/service/fight/pvp/service.go` manager 内存里
- `queues`
- `lastSeen`
- `localQueues`
- `sessions`
- `userSession`
### 现有 RPC 能力
- `logic` 启动时通过 [common/rpc/rpc.go](/workspace/common/rpc/rpc.go#L113) 建立到 `login` RPC client
- `login` `/rpc/*` 入口绑定在 [modules/base/middleware/middleware.go](/workspace/modules/base/middleware/middleware.go#L152)
- `login` RPC server [common/rpc/rpc.go](/workspace/common/rpc/rpc.go#L101) 暴露
### 当前问题
Redis PubSub 适合广播消息不适合同步判断服务是否可用
如果继续让匹配入口走 PubSub
- `logic` 无法在请求当下知道 `login` 是否真能处理
- `login` 更新重启未订阅时匹配请求可能直接丢失
- 前端即使轮询也只是重复投递不能精确表达当前匹配服务可用/不可用
## 收敛后的职责划分
### login
`login` 只负责匹配控制面
- 接收 `logic` 发来的同步匹配 RPC
- 判断当前匹配服务是否可用
- 维护匹配队列
- 找到对手后记录 match 结果
- 再通过 Redis 或其他异步方式通知对应 `logic` 开始 Ban/Pick / Battle
### logic
`logic` 只负责
- 接收前端匹配请求
- 同步 RPC `login`
- RPC 失败时立即返回匹配服务不可用
- RPC 成功时返回排队中
- 收到 match 结果后负责真正 `fight.NewFight(...)`
- 对战期间继续使用现有 Redis topic 转发战斗指令
### Redis
Redis 只保留在对战消息面
- `match_found`
- `ban_pick_submit`
- `battle_command`
- `packet_relay`
- `session_close`
也就是说
- 匹配入口走 RPC
- 对战过程走 Redis
## 推荐目标链路
### 1. 前端加入/更新匹配
前端定期轮询 `logic` 的加入/更新接口
`logic` 处理流程
1. 校验玩家当前战斗状态
2. 同步调用 `login` 的匹配 RPC
3. 如果 RPC 成功返回排队中
4. 如果 RPC 失败清理本地匹配状态返回匹配服务不可用
### 2. login 完成匹配
`login` 维护排队队列和匹配结果匹配成功后
1. 确定 host / guest 所在 `logic`
2. 通过 Redis 通知两个 `logic`
3. host `logic` 开战
4. guest `logic` 设置远端代理并进入 Ban/Pick 或战斗态
### 3. 对战期间
继续复用当前 `logic/service/fight/pvp/service.go` 内的 Redis 指令转发模式
- 战斗操作通过 Redis topic 转发
- host `logic` 维持真实战斗对象
- guest `logic` 维持 remote proxy
## 失败语义
本阶段不做补偿不做离线保队列
### login 不在线
如果 `logic -> login` RPC 调用失败
- 本次匹配直接失败
- `logic` 清理本地匹配状态
- 返回前端匹配服务不可用
### 前端轮询停止
如果前端不再轮询
- 视为用户不再持续请求匹配
- `logic` 不负责继续保活
- 是否从 `login` 队列移除 `login` 的超时策略决定
### login 更新中
如果 `login` 正在更新
- `logic` 的同步 RPC 会失败
- 前端当前轮询会收到匹配服务不可用
- `login` 恢复后前端下一轮再发起匹配
这是本阶段明确接受的行为不在后端做补偿
## 最小实现建议
### 先增加 RPC 健康/匹配接口
[common/rpc/rpc.go](/workspace/common/rpc/rpc.go) 增加面向 `logic -> login` RPC 方法
建议最小接口
- `MatchJoinOrUpdate(PVPMatchJoinPayload) error`
- `MatchCancel(userID) error`
如果需要单独健康检查也可以加
- `MatchPing() error`
但在最小方案里`MatchJoinOrUpdate` 自身就可以承担健康检查职责
### logic 的匹配入口改为同步 RPC
改造 [logic/controller/fight_巅峰.go](/workspace/logic/controller/fight_巅峰.go#L19) [logic/service/fight/pvp/service.go](/workspace/logic/service/fight/pvp/service.go#L83)
- 入口不再直接发布 `queue_join`
- 先发 RPC `login`
- 成功才更新本地匹配状态
- 失败直接返回错误
- 取消匹配时通过 `MatchCancel` best-effort 清理
### 保留 Redis 对战链路
[logic/service/fight/pvp/service.go](/workspace/logic/service/fight/pvp/service.go#L170) 之后的 Redis 消费match result 处理Ban/Pick战斗 relay 不需要一次性重写可以继续保留
调整重点是
- 不再让匹配入口依赖 PubSub
- 让对战过程继续走 Redis
## 对前端的要求
前端不要无脑重复 join而是按轮询更新匹配状态处理
建议行为
1. 首次点击匹配时发一次加入
2. 匹配中每隔 `3~5s` 轮询一次更新
3. 如果返回匹配服务不可用前端退出匹配态并提示
4. 如果返回已匹配/进入 Ban/Pick前端切换到对应界面
## 本阶段不做的事
以下内容明确不在这次最小改造内
- `login` 更新期间的排队保活
- 持久化消息补偿
- `login` 重启后的队列恢复
- Redis Stream
- `login` 实例协调
- 匹配服务自动拉起目标 `logic`
## 后续可选增强
如果后面要继续提高可用性可以再逐步演进为
1. 匹配入口仍走 RPC
2. `login` 内部把队列落 Redis
3. 加入 ticket 和续租机制
4. login 更新时支持恢复匹配状态
但这不是当前阶段的目标
## 最终收敛结论
当前阶段建议明确成一句话
`匹配走 RPC对战走 Redis。`
对应业务语义
- 需要立即判断服务可用性的时候 RPC
- 需要跨服转发战斗消息的时候 Redis