Appearance
游戏服务器分布式架构
游戏服务器既有普通分布式系统的故障、一致性和扩缩容问题,又有长连接、实时状态、房间内顺序和低尾延迟等约束。设计时应先区分大厅类无状态请求、玩家私有状态、房间实时状态和跨服持久化数据,它们不需要使用同一种一致性方案。
常见逻辑分层
text
Client
↓ 长连接
Gateway / Edge
↓ 会话与路由
Player / Room Shards
↓
Matchmaking、Social、Inventory 等服务
↓
Database、Cache、Message BusGateway
网关通常负责:
- 维护 TCP / QUIC / WebSocket 等连接;
- 身份校验、协议解析、限流和基础反作弊校验;
- 根据玩家、房间或会话路由请求;
- 将后端响应写回对应连接;
- 连接断开后的清理和重连协商。
网关不应成为所有业务状态的唯一权威,否则网关故障会同时丢失连接和业务状态。它可以缓存路由,但权威归属应由带版本的路由 / 租约服务或状态节点确认。
无状态服务与有状态服务
- 大厅查询、配置读取等尽量做成无状态服务,可直接水平扩容;
- 玩家 Actor、房间模拟等持有内存状态,必须明确唯一所有者、迁移和恢复策略;
- “服务进程无状态”不等于整个请求无状态,会话、幂等记录和路由仍需外部存储。
玩家与房间分片
最稳妥的顺序模型是让同一逻辑实体在同一时刻只有一个执行所有者:
text
worker_index = hash(room_id) % worker_count同一房间进入同一 worker / mailbox 串行处理,不同房间并行。若按玩家保证顺序,则使用 player_id。入口只有一个生产者时可使用 SPSC 队列;多个网络线程都可能投递到同一 worker 时至少需要 MPSC,或先在网络线程本地聚合后单生产者转发。
Actor / Mailbox 模型
每个玩家或房间拥有一个 mailbox,worker 一次只处理该实体的一条消息:
text
route(entity_id) → mailbox → single active executor → update state优点是业务代码不需要为实体内部每个字段加锁,状态转移天然有序。需要防止两个 worker 在迁移或故障恢复期间同时消费同一 mailbox,可使用所有权 epoch / fencing token。
串行化不是“系统只能单线程”:并行单位从一条消息变成不同玩家、不同房间或不同分区。真正的瓶颈是单个热点实体,它不能仅靠增加消费者解决。
消息顺序与版本
网络包可能重传、乱序或在重连后迟到。对于必须按序的命令,可携带:
session_id:区分旧连接和新连接;sequence:同一会话 / 实体内单调递增;epoch:区分迁移前后的所有者;request_id:重复请求去重。
服务端可拒绝 epoch 较旧的请求,对重复 sequence 返回原结果,对超前 sequence 暂存或请求重传。不要只依赖消息到达时间判断顺序。
有界队列与背压
text
网络线程 → 有界请求队列 → 逻辑 worker → 有界响应队列 → 网络线程有界队列用于限制内存、形成背压并控制尾延迟。队列满时应根据消息等级选择:拒绝新会话、丢弃可重建的位置快照、合并重复状态更新、降低广播频率,或断开持续过载连接。关键交易和状态迁移消息不能与可丢弃的表现层消息使用同一丢弃策略。
实时状态同步
权威服务器
服务端通常是最终权威:客户端发送输入或意图,服务端验证并推进状态。客户端预测可以降低操作延迟,但不能让客户端位置、伤害或资产直接成为可信结果。
帧同步、状态同步与混合方案
| 方案 | 服务端传播内容 | 优点 | 难点 |
|---|---|---|---|
| 帧同步 / Lockstep | 各玩家输入 | 流量较小、所有端执行同一逻辑 | 确定性要求高、慢客户端和作弊处理复杂 |
| 状态同步 | 权威状态或增量 | 服务端控制明确、客户端实现灵活 | 带宽较高,需要插值、预测和纠正 |
| 混合方案 | 输入 + 周期性权威校验 | 兼顾流量与纠错 | 协议和调试更复杂 |
固定 tick 能让模拟和性能预算更可控。状态同步常只发送增量,并通过基线版本确认客户端从哪个快照继续;丢失关键基线时需要重发完整快照。
AOI(兴趣区域)
玩家通常只需要接收附近或相关对象的状态。AOI 可使用网格、空间树或区域订阅减少广播:
text
全房间对象 → 按空间 / 关系筛选 → 每个玩家的可见集合 → 增量推送AOI 计算本身也可能成为热点。应监控每 tick 的可见对象数、订阅变更数和出站带宽,而不是只看在线人数。
跨服与状态迁移
玩家从服务器 A 迁移到服务器 B 时,不能只把内存对象复制过去并修改路由。可将迁移设计为幂等状态机:
text
Preparing → Frozen → Transferred → Activated → Completed
↘ Failed / Compensating一种通用流程:
- A 停止接收会改变迁移状态的新命令,或把它们排队;
- 生成带版本的状态快照和迁移 ID;
- B 幂等接收快照,但尚不对外成为所有者;
- 路由服务原子地把所有权 epoch 从 A 切换到 B;
- B 激活,A 拒绝旧 epoch 请求并清理状态;
- 超时或失败时按状态机恢复 / 补偿,而不是从头盲目重试。
关键是任意时刻只有一个合法写所有者。若 A 在网络分区后恢复,B 已获得更高 epoch,下游存储和路由都应拒绝 A 的迟到写入。
跨服请求还应避免同步调用链过长。可使用事件和 Saga 协调非关键流程,但资产扣减、奖励发放等必须有幂等键、唯一约束和可审计流水。
热点房间与广播
普通一致性哈希只能把不同房间分散到不同节点,无法拆散一个超大热点房间。常见办法包括:
- 限制单房间容量或按玩法拆成多个实例;
- 将空间划为区域,分区计算后在边界交换必要状态;
- 使用 AOI 限制每个玩家收到的对象;
- 把网络广播、序列化、持久化与核心模拟拆成流水线;
- 对可合并状态只保留最新版本,而不是发送每次中间变化;
- 使用分层 fan-out 节点分担大规模广播。
拆分一个强交互房间会引入跨分区同步。若每个 tick 都需要全局屏障,增加节点可能反而增加延迟,因此要根据交互边界拆分,而不是机械分片。
故障恢复与持久化
不同状态使用不同恢复目标:
- 连接状态:网关故障后客户端重连,使用新 session 并重新查询路由;
- 临时房间状态:可选择直接结束、从最近快照恢复,或从事件日志重放;
- 玩家关键状态:用数据库事务、版本号和审计流水持久化;
- 高频非关键状态:批量异步落盘,接受少量回退;
- 跨服迁移状态:持久化状态机阶段和迁移 ID,确保可重试。
仅依赖心跳无法无损恢复内存状态。若要求房间进程故障后继续,需要周期快照、输入 / 事件日志、备用节点或可重建的确定性模拟;它们都会增加正常路径成本,应根据 RPO、RTO 和玩法价值选择。
路由和旧实例隔离
路由项可包含:
text
entity_id → {server_id, epoch, lease_expire_at}新所有者必须使用更高 epoch。下游服务不应只检查 server_id,还要拒绝旧 epoch,以防旧进程在长暂停或网络恢复后继续写入。
跨地域部署
跨地域 RTT 很难通过代码优化消除。实时房间通常把强交互玩家放在同一区域,地域之间异步同步大厅、社交和分析数据。若强行跨地域逐 tick 共识,最低延迟受最慢多数派网络决定。
地域选择需要考虑玩家延迟、数据合规、故障域和容量。跨地域切换不能只修改 DNS,还要处理会话重建、状态归属和尚未复制的数据。
监控指标
除 CPU 和内存外,实时服务应重点观察:
- 每个 worker / 房间的队列长度和最老消息等待时间;
- tick 平均、P99、最大耗时以及超预算次数;
- 活跃房间、热点实体和单实体消息速率;
- 入站 / 出站带宽、广播放大倍数和丢弃数量;
- 重连率、路由失败、sequence 跳跃和重复请求;
- 跨服务调用 P95 / P99、超时率和重试放大倍数;
- 快照耗时、事件日志积压和故障恢复时间。
平均延迟无法反映卡顿体验,必须关注尾延迟和连续超预算 tick。
常见错误
- 让任意业务线程并发修改同一房间,再用大量细粒度锁补救;
- 认为按房间哈希后所有负载都均匀,忽略超级热点房间;
- 队列无界,过载时延迟和内存同时无限增长;
- 把位置快照和资产交易使用同一可靠性、顺序和丢弃策略;
- 跨服迁移只复制内存并改路由,没有状态机和 epoch;
- 仅凭心跳超时让新实例接管,却不阻止旧实例继续写;
- 客户端预测结果直接作为服务端权威状态;
- 为了“分布式”拆出大量同步 RPC,导致每 tick 延迟由最长调用链决定;
- 只监控平均 tick 和平均队列长度,不看 P99 与热点实体。
面试中的简洁回答
游戏服务器通常按玩家或房间把消息路由到固定 worker,使同一实体串行处理、不同实体并行,避免任意线程并发修改状态。网关负责长连接和路由,房间或玩家节点持有权威状态;消息要携带 session、sequence 和 ownership epoch 处理重连、重复和迁移。跨服迁移应是可恢复的幂等状态机,并用更高 epoch 隔离旧实例。实时链路使用有界队列和背压,过载时优先丢弃可重建状态,不能丢关键交易。热点单房间无法靠一致性哈希解决,需要 AOI、区域拆分、流水线或容量限制。