性能优化实战
网络服上线后能扛多少人,取决于这几个层面:性能开关(省 CPU)、快速切服(提体验)、Redis 与数据库(省 IO)、部署调优(榨机器)。加上编码层面的高效写法,才能稳住高并发。
性能开关
关闭用不到的原生游戏逻辑能显著提升承载力,但会让部分生存玩法失效。分统一开关和单独开关,默认全开(is_disable=False),调用接口才关。
只在初始化时调用
强烈建议在服务器初始化时设置性能开关,不要在运行中途修改。小游戏服可以关掉大量生存逻辑,把承载从 20~30 人拉到 100~200 人。
快速切服
频繁切服的玩法(如小游戏)用它消除跳转等待。
| 场景 | 方案 |
|---|---|
| 目标服 mod 相同 | SetKeepResourceWhenTransfer(最快) |
| 目标服 mod 不同 | 方案1 SetResourceFastload(无一致性前提,稍慢);方案2 合并资源包后仍用 SetKeepResourceWhenTransfer |
方案2 操作:右键服务器 →【应用快速切服】→ 生成快切网络(大厅/游戏服自动生成 commonRes Mod 并勾选)→ 调 SetKeepResourceWhenTransfer → 部署。注意:会改变原网络服 mod 结构,建议在新生成的 mod 上开发;不同服若有相对路径相同的资源文件会合并失败并报错。
Redis 的三种用法
Redis 是单线程、基于内存的键值存储,能干 service 的活但不占额外进程。
- 分布式锁:跨进程业务保原子性——业务前 setkey 获锁,完成后删 key 释放(参考
neteaseChunkRes)。 - 全局数据:多服频繁读写的数据(玩家在线状态、全局弹幕)放 Redis,多条操作时配分布式锁(参考
neteaseFriend、neteaseDanmu)。 - SQL 缓存层:内存→Redis→SQL 分级落盘。比直接用 server 内存好在:避免崩溃丢大量数据、更少 SQL 请求、降低内存峰值。参考
neteaseQuest:进度先入内存,每 4 秒写 Redis,每 444 秒写 SQL。
数据库优化
存储方案设计(比编码优化更重要)
把日期编进 key,规避定时清理。如记录每日杀怪数:
# 好:key 含日期,设 1 天过期,无需零点批量清零
key = "killMob:{}:{}-{}-{}".format(uid, y, m, d)
dbIncrby(key, number); dbExpire(key, 86400)坏方案在零点遍历全部玩家清零,耗时与当日活跃玩家数成正比——这就是瓶颈。
定时+离线存档代替实时存档:高频变化、丢失可容忍的数据(金币)只在内存更新,每几分钟+离线时存一次;低频、丢失不可接受的数据(物品栏)才每次同步。
建表与查询红线
| 项 | 建议/红线 |
|---|---|
| 字符集 | 统一 utf8mb4、引擎 InnoDB |
| 精确浮点 | 用 DECIMAL 而非 FLOAT(如 DECIMAL(19,4) 精确到分) |
| 单表数据量 | 建议 ≤ 5 千万;必须 ≤ 1 亿,超了自行分表(按 uid%N) |
| 单表字段数 | 建议 ≤ 30,太多考虑垂直分表 |
| 主键 | 每表显式指定 |
| 字段 | 显式 NOT NULL;类型最短原则;别存二进制(转 base64) |
- 加索引:针对常见查询加,单索引字段 ≤ 5,单表索引 ≤ 20,选区分度高的列(性别这种没用)。新 SQL 上线前先
EXPLAIN。 - 查询:避免
SELECT *(显式列字段);IN代替OR(超 1000 拆批);避免SELECT COUNT(用SHOW TABLE STATUS的 rows);避免左/全模糊LIKE '%x'(用不上索引)。 - 大表遍历:用
ORDER BY updateTime+LIMIT分段查,WHERE updateTime>=maxUpdateTime续查(用>=防漏,注意去重取最新)。 - 连接数:单进程连接数 × 进程数 + 300~500 余量 = 预估上限。默认上限 5000,超了上线前通知运营调整。
部署调优
先按经验值铺,再按监控调。
承载经验值:每 proxy ~500 人;无性能开关的生存服 game/lobby 20~30 人,开了性能开关可到 100~200 人。
线程/核心:master、service、game、lobby 各占 ~1 核;proxy 占 ~3 核(2 加解密 + 2 raknet + 1 主,忽略次要线程)。32 核物理机可部署 6 proxy + 14 game/lobby,或 32 个 game/lobby。
上线后调优:周末晚 8~9 点高峰,看 Grafana 挑人数最多的服,SSH 用 top -Hp <pid> 看线程占用。最高线程常驻 CPU 控制在 70% 左右、不长期超 80%:低于就加最大人数,高于就别加、卡了就降。
高效 Python 编码
Apollo 服务端跑 Python,这些微优化在热点循环里累积可观:
| 做法 | 提升 |
|---|---|
临时变量缓存 ./[] 属性访问 | ~50%+ |
xrange 代替 range | ~43% |
分支逻辑改 dict 分发 _FuncMap.get(k, default) | 分支越多差距越大 |
| 分支把高命中判断放前面 | 截断后续判断 |
in 代替 has_key | ~52% |
直接 a.name 代替 getattr | ~60% |
热点循环里避免 map/zip/filter | ~50% |
| 减少小粒度函数调用、少用默认/拓展参数 | 明显 |
属性多且固定、实例众多的类用 __slots__ | 内存降至 1/3~1/4 |
已知性能深坑
- 地图物品过多(几百几千张
filled_map)拖垮服务器:禁用地图物品,或ChangePerformanceSwitch关闭地图信息同步。 - 复活点离末地传送门太近:复活点随机逻辑死循环——地图设计时避开。
RegisterEntityAOIEvent区域过大:每帧扫描太耗 CPU,慎用。SetAddArea常驻区块:额外加载实体带来运算量,慎用。
