《MMO 服务器开发》3.2 指标与 Prometheus 打点
日志能还原一次请求,但要回答「现在系统健康吗」就得靠指标。这一节讲清 Counter、Gauge、Histogram 三种类型的语义与最常见误用,重点剖析标签基数这个能把 Prometheus 拖垮的坑,再用 client_golang 真起一个 HTTP server 暴露 /metrics,并用 HTTP 真拉取断言关键指标存在。
category
日志能还原一次请求,但要回答「现在系统健康吗」就得靠指标。这一节讲清 Counter、Gauge、Histogram 三种类型的语义与最常见误用,重点剖析标签基数这个能把 Prometheus 拖垮的坑,再用 client_golang 真起一个 HTTP server 暴露 /metrics,并用 HTTP 真拉取断言关键指标存在。
日志不是越多越好。这一节先把一条请求日志该带的最小字段集钉死——trace_id、player_id、cmd、seq、cost_ms、result——再用 log/slog 写出一个能跑的实现,讲清日志分级、错误不采样的采样策略、手机号与邮箱的写盘前脱敏,最后给出一张该记与不该记的清单。
无界队列把「慢」变成「OOM」,热区不隔离让一段慢拖垮整帧。本节把 AOI、战斗等 CPU 热点拆到各自的有界队列与独立 worker 上,用三种溢出策略——拒绝、丢弃最旧、降级——显式决定「满了怎么办」,并用可跑的测试断言慢 AOI 不会阻塞移动、战斗事件永不被静默丢弃。
2.1 的剖析显示一帧的时间大头是分配与回收,不是算法。这一节把双栈项目的隐藏成本摊开:用同一份「分配 N 个对象并触发 GC」的工作负载,Go 侧读 runtime.MemStats 与 debug.GCStats,Java 侧读 ManagementFactory 的 MemoryMXBean 与 GarbageCollectorMXBean,在同机上真跑并对照,最后给出选型结论。
30Hz 的 MMO 服务端每帧只有 33ms,一 tick 里 AOI 广播、移动、战斗、定时器各占多少必须先量化再谈优化。本节把帧预算写成一张可断言的表,用 runtime/pprof 与 net/http/pprof 真跑一次剖析,并给出本机实测的相位占比与火焰图读法。
对照三个主流游戏服务端框架——Lua Actor 的 skynet、Go 分布式的 pitaya、轻量单进程的 leaf——各自的定位、适用规模与 aegis 的差异;再做全书收官:回顾四卷从网络内核到工程化运营的贯穿线,汇总可运行产物与实测数字,给出下一步建议,附一个能跑的全书产物清单校验器。
把全书四处双栈对照的实测收进一张跨栈矩阵:并发模型、内存与 GC、序列化、网络栈、部署形态、生态与招聘、开发效率与可维护性逐维度对照,再给出可判定的选型结论,并附一个能跑的选型打分器——输入团队与场景特征,输出推荐栈与逐条理由,权重可改可测。
压测器就位,接下来回答那个最初的问题:一台机器到底能扛多少。本节先设计两条扫描——连接扫描逼出单进程的连接天花板,负载扫描逼出吞吐与延迟曲线,再把本机实测数字(约两千连接、每秒数千消息、P99 分档)套进容量公式,算出集群要几台、留多少余量,以及瓶颈究竟落在连接数、CPU、内存还是带宽上。
站内没有 k6、wrk,压测器只能自己写。本节从零搭一个可编排的机器人压测平台:机器人状态机走完连接→登录→进场景→移动→心跳,并发编排用错峰建连绕开 macOS 的 accept 队列上限、用起跑线屏障把建连爬坡挤出测量窗口,最后真起一个本地回环服务端加五十个机器人跑通,并把结果落成 JSON。
「在线五千人」从来不是一句承载结论。本节先把承载指标的定义钉死——要有单位、要有变坏的方向、要能说出超标后怎么坏,再列出连接数、消息吞吐、P50/P90/P99、错误率、内存、GC 暂停这张表,最后拆穿只看在线人数、只看平均值、不看错误率、忽略慢连接这四种最常见的自欺欺人。
卷三《分布式与跨服》共 33 节,回答一个问题:一个单服能跑的 MMO 服务端,怎么变成一群机器上跑、彼此发现、跨服组队、跨服打仗、数据还能对得上的集群。含目录、三条阅读路径、可运行性口径与《晨曦大陆》跨服战场验收清单;全部代码在本机真跑,中间件以独立容器运行。
数据涨上来以后一台库装不下,按什么键分片决定了以后能不能平滑扩容。本节对比按 player_id 哈希与按 server_id 范围两种分片键,用一致性哈希把再均衡的搬迁量压到最低,讲清扩分片时的双写、校验、切读三步,并给出「4 分片扩到 8 分片后数据无丢失、路由正确」的真跑断言。
同一个角色的数据可能被两个写者同时改:定时快照在写,退出登录也在写,一份旧快照就能把刚发出去的奖励盖回去。本节讲清脏写的成因与三类防护——版本号乐观锁、定时快照加关键操作即时落库、脏标记批量合并,并给出「两个并发写只有一个成功、版本号递增」的真跑断言。
一个满级角色的数据散落在账号、等级、坐标、货币、背包、任务、属性七八个维度上,全塞进一张宽表会让每次位置更新都重写整行。本节讲清主表与扩展表的拆分原则:什么进热表、什么进冷表、大字段为什么必须分出去,并给出在 Postgres 上真跑的建表 DDL 与读写验证。
读写分离换来的读扩展能力,代价是主从复制延迟带来的「写后读旧」。本节用真跑的 Redis 复现「写后立即读可能读到旧值」,对照读主库、版本令牌、等待追上三种解法,并给出 CQRS 轻量版的完整实现:写侧落事件、读侧投影宽表,附本机实测数字与取舍。
冷启动、批量过期、热点活动都会把请求同时打到数据库上。本节用真跑的 Redis 给出四道防线:预热、单飞(singleflight)、逻辑过期、热点 key 探测,并给出「1000 并发请求同一个未缓存 key、源只被查 1 次」的可运行断言与实测数字。
缓存与数据库不一致是游戏服务端最常见的事故源。本节用真跑的 Redis 讲清三件事:缓存键怎么设计成命名空间加版本号、写库和删缓存到底谁先谁后、延迟双删怎么覆盖并发窗口;并给出并发读写下缓存与源最终一致的可运行断言、本机实测数字,以及失效风暴的三种摊平手段。
时钟回拨是雪花算法唯一的软肋。本节讲清它的三种成因(NTP 步进校时、虚拟机迁移、闰秒回拨)与三种应对(等待追平、换 worker id、抛错拒绝)各自的代价,并给出一个完整可跑、可注入假时钟的 L1 容错生成器与五条测试用例,让回拨从线上偶发变成可参数化的测试场景。
号段模式:用 Redis INCRBY 一次批发一批 ID,在内存里用双 buffer 预取慢慢发,把发号的远程调用摊薄到步长分之一。讲清主段与备用段切换的临界区、并发安全与跳号取舍,并在本机真连 Redis 跑出两个实例共用同一号段的互斥断言。
从零实现雪花算法:把 64 位切成符号位、41 位时间戳、10 位 worker id 与 12 位序列,讲清 worker id 如何由区服号合成、同毫秒序列耗尽怎么处理、为什么吞吐天花板由位布局而非核数决定,并给出本机实跑的并发吞吐与重复率 0 断言。