外观
路线 B|C++ Linux 游戏服务端
B3|Server Engineering:Tick、Session、任务与可观测性
适用基础: B1、B2、Stage07 目标: 把网络连接和业务状态组织成可维护的服务进程。
一、服务不是一个大循环
可维护服务通常分层:
text
接入层:Socket / 协议 / 限流
会话层:连接、认证、玩家状态
领域层:房间、战斗、任务
基础设施:Timer、Queue、DB、日志、监控分层的根本目的是明确状态 owner。接入层拥有连接和解析缓冲,Session 层拥有认证与连接版本,领域层拥有玩家/房间规则,基础设施只提供时间、存储和通信能力。让数据库回调直接操作 Socket,或让网络线程直接修改房间状态,都会让生命周期和并发边界失去控制。
二、Tick 与 Timer
固定 Tick 适合确定性房间模拟;真实时间 Timer 适合超时和调度。不要用网络事件到达次数代替游戏时间,也不要让长任务阻塞 Tick。
20Hz Tick 的时间步是 50ms,但它不是“每 50ms 必须执行一次”的魔法。服务必须定义积压策略:补跑有限次数、丢弃表现 Tick、降低模拟精度或把房间标记为过载。若每次超时都无限补跑,服务会进入追赶—更慢—继续追赶的正反馈。
三、Session 生命周期
text
Connected → Authenticating → Online → Closing → Closed每个异步结果携带 session/version,旧连接的回调不能修改新连接。关闭必须幂等,所有 Timer、任务、Socket 和玩家引用都要有 owner。
四、Task Queue 与 Thread Pool
网络线程负责短操作,数据库和压缩放入 worker;结果回投业务线程。队列需要长度、拒绝、超时、取消和停止语义,不能只创建无限线程。
任务粒度过小会让入队、唤醒和上下文切换成本超过计算本身;任务过大又会造成队尾延迟和停止困难。线程池大小不是 CPU 核数的固定公式:CPU 密集任务受核心数限制,IO 等待任务可以更多,但仍受连接池和下游容量约束。
五、RPC、错误码与重试
错误至少区分参数错误、认证失败、资源不存在、限流、超时和内部故障。重试必须有幂等键、次数和退避;对非幂等写操作盲目重试会重复扣费或重复发奖。
错误码是跨进程契约,不应直接暴露内部异常文本。稳定错误用于客户端决策,诊断细节进入带 trace ID 的服务端日志。RPC 总超时应覆盖排队、网络和下游时间,不能让每一层都使用相同的完整超时,否则调用链越深总等待越长。
六、日志、监控和健康检查
指标包括连接数、认证耗时、Tick P99、队列长度、请求错误率、DB 延迟、内存和 fd。健康检查区分“进程活着”和“能接受业务”;就绪失败不一定需要重启。
日志解释单次事件,指标解释整体趋势,trace 解释跨服务因果,profile 解释资源消耗。四者不能互相替代。高基数玩家 ID 不适合作为普通指标标签,但适合进入日志和 trace;否则监控系统本身会被标签数量压垮。
七、Session 与任务系统的知识模型
Session 是连接、认证、玩家身份和版本的组合,不等于一个 Socket。异步任务完成时必须携带 session/version,避免旧连接结果写入新连接。任务队列还要有容量、拒绝、取消和停止语义;线程池解决执行位置,不自动解决业务顺序和对象所有权。
八、优雅退出
text
停止接收新连接
→ 标记 draining
→ 拒绝新写请求
→ 排空/取消任务
→ 保存必要状态
→ 关闭 DB/缓存/线程池
→ 退出九、练习与答案
1. 为什么 session ID 不能只用玩家 ID?
同一玩家可能快速重连,旧连接和新连接共享玩家 ID;必须再加连接版本或随机 session token。
2. 健康检查应该访问数据库吗?
取决于检查目标。存活检查应轻量;就绪检查可验证关键依赖,但必须有超时并避免检查本身压垮系统。
十、阶段输出
提交一份服务状态机、线程边界、错误码表、指标面板定义和可压测的 SessionServer。
