《MMO 服务器开发》6.3 资产归属与跨服结算
战场奖励到底算在哪个服?本节给出跨服结算的归属规则:战场只产出结算单,母服才是账本。用幂等键保证同一张单子重传多少次都只发一次奖,回传失败自动重试,最后用「战场产出总量等于各母服入账总量之和」的对账兜底,把丢单和重复入账都当场抓出来,并给出可直接运行的幂等与守恒断言。
category
战场奖励到底算在哪个服?本节给出跨服结算的归属规则:战场只产出结算单,母服才是账本。用幂等键保证同一张单子重传多少次都只发一次奖,回传失败自动重试,最后用「战场产出总量等于各母服入账总量之和」的对账兜底,把丢单和重复入账都当场抓出来,并给出可直接运行的幂等与守恒断言。
跨服消息怎么找到人?频道广播靠「谁订阅了哪个频道」扇出,定向私聊则要先把玩家路由到它当前所在的服。本节用 NATS 真跑三个服:世界频道只到订阅它的服,定向消息精确投进目标服的收件箱,掉线期间的消息先暂存、换服上线后一次性补投,全程用独立 subject 前缀隔离。
玩家在 A 服报名,却要和 B、C 服的对手同场。本节从零写一个跨服中心:报名带服号与快照落到跨服队列,按战力分桶、蛇形分队完成撮合,结果再沿报名时那条长连接精确推回各自的母服。最后真起三个模拟服进程,断言通知回对了服,并给出并发写同一连接导致帧交错的真实踩坑与修法。
实例进程会崩,内存里的场景状态会随之消失。本节用快照 + 事件重放把状态重建出来:事件覆盖所有状态变更、快照带校验和、恢复时选最新可用快照再补放事件;并用 aegis 真跑「杀掉进程 → 重建 → 断言指纹一致」,最后讨论重建期间玩家怎么办。
上一节的调度器能起实例,但没有回答「起在哪台机器」。本节做分配 Broker:用 CPU、内存、连接数、实例槽位四个维度评估容量,选出余量最大的节点并立即预留,开不出来时进队列等待、超时返回明确错误;并给出 aegis 在本机的容量评估基准数字。
场景服、战斗服、世界服三类实例由同一个调度器管理:分配 ID、起进程、等到就绪、推进生命周期。本节把实例状态机(创建→就绪→分配→使用→回收)写成一张只有合法迁移的表,并用 aegis 真起一个实例进程、读到 READY 后再断言它确实在监听。
网关重启不该把在线玩家直接踢下线。本节实现连接排空:停止接受新连接、广播 CmdDrainNotice 0x1001、下发 CmdReconnectTicket 0x1002、等旧连接自然结束、超时兜底,并真起进程 + 客户端跑完整排空流程,含千连接错峰压测。
新连接该被分到哪个网关?会话粘性有三种实现:一致性哈希、目录查表、客户端重连票。本节把前两种从零实现,量出「摘掉一个节点时迁移多少人」并给出可判定的取舍结论,再讲清网关滚动发布期间在线玩家到底会看到什么、哪种组合才能做到发布无感。全部代码在本机真跑。
网关集群里,玩家的长连接只挂在某一个网关上,路由目录记录「谁在哪」。本节从零实现带代次的 RouteDirectory,把「玩家换服时旧连接迟到断开」这个经典竞态钉死,并真起多个网关进程与客户端,断言目录指向正确节点、旧连接不会误删新绑定、换服后旧代次解绑被拒。
3.1 与 3.2 的实现都在 Go 上,真实的 MMO 项目却常常是双栈:新模块用 Go,历史模块是 Java。本节用同一份 aegis 帧,在 Go 与 Java 21 上各手写一次 gRPC 服务定义(不装 protoc),真跑往返并对照两种栈在本机的量级,并记下 Java 侧容易踩的一个坑。
超时是未知,那么重试就必须先有幂等前提;下游持续失败时靠熔断止损,过载时靠背压把请求挡在门外而不是排队等雪崩。本节用 Go 真跑重试策略、三态熔断与令牌桶,并给出「熔断打开前后」上游与下游的实测对照,说明为什么这三件事必须成套使用,全部在本机实跑验证。
跨进程调用的第一个问题不是选框架,而是一次调用最多能花多久。本节复用卷一的 aegis 帧做跨进程载荷,把客户端 deadline 拆成每一跳的超时预算,再用真跑的 gRPC 往返说清「超时了业务算成功还是失败」,以及为什么必须有幂等键,全部在本机真跑验证。
同一套注册-发现-健康检查,在 etcd、NATS、Consul 上各实现一遍并真连本机中间件跑出数字:注册延迟、发现延迟、心跳中断后的下线收敛时间。三种模型(租约、心跳陈旧判定、原生 TTL 检查)的差异不在毫秒,而在谁负责判定存活这件事上,据此给出可判定的选型结论。
单写者是集群里最难保证也最值钱的性质。本节用 etcd 的 TTL 租约加一次事务 CAS 从零实现 Leader 选举,讲清为什么「键不存在才写」就等价于互斥,再用单调递增的 fencing token 挡住醒过来的旧 leader,真连本机 etcd 断言同一时刻只有一个 leader。
服务发现的核心不是选哪个中间件,而是把「存活」讲清楚:节点用 TTL 租约注册、靠心跳续约维持,etcd 在租约过期时自动删键。本节从零实现注册、续约与健康三态(健康 / 降级 / 下线),并用 Watch 驱动的监视器真连本机 etcd 跑通全部断言。
把整簇跑起来之后,它到底能扛多少?本节用 B 层基准本机实跑:四个节点、单网关千连接、跨服匹配吞吐、跨服消息延迟 P50/P90/P99,落 JSON 并逐条与正文数字对应。沿用基准三条纪律,报同机相对量级,禁用「快 N 倍」式绝对话,最后合成一个集群总承载。
上一节手动起了四个进程,这一节把它变成一条命令:一个 Go 编排器按依赖顺序拉起 hub、网关、战斗实例,每个节点健康检查通过才继续,进程崩溃自动重启,收到信号后按相反顺序优雅关停。四个问题各自落成可测代码,全部真跑,并给出起、杀、关三条可执行验收断言。
把第 6 章的跨服报名匹配、第 4 章的网关路由、第 5 章的实例调度、第 10 章的结算补偿串成一条链路:两个服的玩家报名、匹配成队、按服号路由进同一战场实例、打完把奖励回传各自的服,并用幂等结算证明账目守恒。全部四个进程真跑,给出可执行断言。
玩家申诉「我昨天明明有 3 件装备,今天只剩 1 件」,光看当前背包答不出来——因为没有历史。本节给资产系统装上事件溯源:append-only 的事件流、定期快照、以及从零重放,并写一个可跑的审计查询,回答「某玩家某天的资产为什么变了」。断言重放结果与快照一致、窗口解释能列出当天每一笔增减及其来源,并指出快照与重放如何互为校验。
跨服结算和本服流水对不上,差在哪、谁来修、修完谁签字?本节先把对账口径钉死——谁对谁、按什么周期、比哪些字段;再给差异分级:金额类必须逐笔一致、展示类可以容忍;然后用 aegis 的 recon 包写一个可跑的对账器,造出若干差异样本,断言分级正确、可镜像的被自动修复、金额冲突的生成带审批留痕的修复工单。