1. 从存档文件到服务器数据库两种存储哲学的分野单机游戏和网络游戏在数据存储上的差异本质上不是技术选型的不同而是信任模型的不同。单机游戏信任玩家数据放在玩家自己的机器上网络游戏不信任玩家数据必须放在自己手里。这个根本差异决定了后面所有技术细节的走向。1.1 单机存档一个文件搞定但坑比你想的多单机游戏的数据存储最朴素的做法就是写一个本地文件。早期游戏用二进制格式直接 dump 内存结构体后来逐渐演化出 INI、XML、JSON 乃至自定义二进制格式。我做过一个模拟项目最初用 JSON 存玩家进度开发阶段爽得不行——可读性好、调试方便、改个数值直接文本编辑器打开就行。但上线前压测发现两个致命问题第一JSON 的解析开销在低端设备上不可忽视。一个 2MB 的存档用某主流解析库在低端安卓机上要 300ms 以上玩家每次进游戏都要等体验很差。第二JSON 太容易被篡改。玩家用文本编辑器把金币从 100 改成 999999游戏毫无察觉。所以后来我改成了二进制格式加校验。具体做法是序列化时先写一个头部包含魔数、版本号、数据长度和一个 CRC32 校验值写入前对整个数据块算 CRC读取时先校验再解析。这样玩家改一个字节CRC 就对不上游戏直接拒绝加载。当然这只能防手改防不了逆向——但单机游戏本来也没必要防到那个程度毕竟玩家改自己的存档本质上是在改自己的体验。提示单机存档设计时版本号字段一定要留够。我见过太多项目因为存档格式升级没做兼容导致老玩家更新后存档全废社区直接炸锅。建议版本号用 4 字节整数并且每次格式变更都写一个迁移函数从旧版本逐级升到新版本。1.2 网游存储服务器说了算但说了算的代价是什么网络游戏的数据存储核心原则只有一条所有关键数据的所有权归服务器。玩家的金币、装备、等级、任务进度全部存在服务端数据库里客户端只是一个展示层。玩家在客户端看到的数字是服务器告诉他的玩家做的任何操作都要发请求给服务器服务器校验通过后才改数据再把结果同步回客户端。这个模型听起来很清晰但落地时有几个绕不开的难题。第一个难题是数据库选型。早期网游常用关系型数据库MySQL 居多因为事务支持好、数据一致性强。但关系型数据库在超高并发写入时容易成为瓶颈。后来很多项目转向 Redis 做缓存层热数据放 Redis冷数据落 MySQL写操作先写 Redis 再异步落库。这个方案性能好但引入了缓存与数据库不一致的风险——如果异步落库失败Redis 里的数据和 MySQL 里的就对不上。我见过一个项目因为这个问题玩家充值后金币到账了但订单表没写进去对账时差了十几万。第二个难题是分库分表。当单表数据量过亿查询性能断崖式下跌。常见的做法是按玩家 ID 哈希分片把数据分散到多个库多个表。但分片之后跨服交易、全服排行榜这类需求就变得很麻烦。我参与过一个模拟项目排行榜最初用 MySQL 的 ORDER BY 直接查数据量上来后每次查询要十几秒。后来改成 Redis 的 Sorted Set实时更新分数查询 O(log N)问题才解决。存储方案适用场景优点缺点本地文件单机游戏存档实现简单、无需服务器易篡改、无法跨设备关系型数据库网游核心数据事务强、一致性好高并发下瓶颈明显内存数据库热数据缓存读写极快持久化需额外保障混合方案大型网游兼顾性能与可靠架构复杂、一致性难1.3 为什么单机不能照搬网游的存储方案有人可能会想既然网游的服务器存储这么可靠单机游戏为什么不也搞个服务器答案很简单单机游戏的核心卖点就是不依赖网络。玩家在飞机上、在地铁里、在没网的环境下照样能玩。如果单机游戏强制联网存数据那就变成了需要联网的单机体验反而倒退。但这不意味着单机存储就可以随便做。我个人的经验是单机存档要重点考虑三件事原子性写存档时断电不能把存档写坏、备份至少保留上一个版本的存档、迁移版本升级时老存档能继续用。这三点做到位单机存储就稳了。原子性的实现常见做法是写临时文件再重命名。先把新存档写到save.tmp写完并校验通过后再重命名为save.dat。重命名操作在大多数文件系统上是原子的这样即使写到一半断电原来的save.dat还在不会丢档。备份则是在重命名之前先把旧的save.dat复制为save.bak。迁移就是前面说的版本号加迁移函数。2. 外挂与内挂同一枚硬币的两面聊完存储必须聊外挂。因为存储和外挂是紧密相关的——外挂的很多手段本质上就是在攻击游戏的存储和通信机制。而内挂这个词则是另一个维度的概念很多人会把两者混为一谈其实差别很大。2.1 外挂的本质篡改客户端或通信外挂的核心逻辑用一句话概括就是让游戏按照非设计者的意图运行。实现手段主要有三类。第一类是内存修改。外挂程序读取游戏进程的内存找到关键数值比如血量、金币、攻击力的地址然后直接改掉。早期单机游戏的修改器就是这类比如经典的金山游侠式工具搜索数值、锁定数值。网游里这类外挂也有但因为关键数据在服务器改本地内存只能骗自己服务器一校验就露馅。第二类是封包篡改。外挂拦截客户端和服务器之间的通信数据包修改其中的字段再转发。比如把使用普通攻击改成使用秒杀技能或者把移动速度参数改大。这类外挂的防御主要靠服务器端校验——服务器不能信任客户端发来的任何数据必须自己算一遍。比如移动服务器要根据时间和速度上限判断玩家是否瞬移比如伤害服务器要根据装备和技能自己算不能直接用客户端报上来的数字。第三类是自动化脚本。这类外挂不修改数据只是模拟玩家操作比如自动打怪、自动采集、自动跑商。它的防御最难因为从服务器角度看这些操作和真人操作的数据包一模一样。常见的对抗手段是行为分析——检测操作频率是否过于规律、是否 24 小时在线、是否走位过于精准。但这是一场持久战没有一劳永逸的方案。注意做网游时有一条铁律——客户端发来的任何数据都不可信。我见过一个项目伤害计算放在客户端做服务器只负责转发结果外挂直接把伤害改成百万级一刀秒 Boss。后来改成服务器计算伤害客户端只负责播放动画问题才解决。这个教训值多少钱那个项目因为外挂流失了大概三成玩家。2.2 内挂是什么设计内的自动化与设计外的灰色地带内挂这个词在不同语境下含义不同。一种理解是游戏官方内置的自动化功能比如自动寻路、自动战斗、自动挂机。这类功能是设计者主动提供的目的是降低玩家的操作负担尤其是手游和页游里非常常见。另一种理解是利用游戏机制实现的、介于合规与违规之间的自动化比如利用游戏内的宏命令、按键精灵式的操作录制。从设计角度看内挂的存在其实反映了一个现实很多玩家的核心乐趣不在于操作而在于成长和收集。如果游戏强制要求大量重复操作玩家就会累就会流失。与其让玩家去找外挂不如官方提供内挂把体验控制在设计范围内。我参与过一个模拟项目最初没有自动战斗玩家反馈太肝后来加了自动战斗但限制了使用场景比如只能在已通关的副本里用留存率明显提升。但内挂也有风险。如果内挂太强玩家会觉得游戏在替我玩参与感下降。如果内挂太弱玩家还是觉得累。这个度很难把握。我的经验是内挂应该处理重复劳动但不应该处理决策。比如自动打怪可以但自动选择装备、自动分配技能点就不应该由内挂代劳因为那些是玩家表达策略的地方。2.3 单机外挂为什么管不了也不该管单机游戏的外挂更准确说是修改器从技术上讲几乎无法根治因为玩家拥有完整的客户端和本地数据。你加密存档他可以逆向解密你校验内存他可以用调试器绕过。而且从伦理上讲单机游戏是玩家买断的产品玩家怎么玩是自己的事。他改金币、改等级、改剧情影响的只是自己的体验没有伤害其他玩家。所以单机游戏对待修改器的态度通常是不鼓励但不禁止。有些开发者甚至会主动提供控制台命令或作弊码作为彩蛋或调试工具。这反而成了一种社区文化。我个人的做法是单机游戏不做强反作弊但会做存档完整性校验防止玩家因为存档损坏而丢进度。至于玩家主动改存档那是他的自由。网游就完全不同了。网游是一个多人在线的共享世界一个玩家开外挂会破坏其他玩家的体验会破坏经济系统会缩短游戏寿命。所以网游的反外挂不是要不要做的问题而是做到什么程度的问题。3. 反外挂的工程实践从被动封号到主动防御反外挂是一个系统工程不是加一个模块就能解决的。我把它分成三个层次预防、检测、响应。预防是让外挂难以生效检测是发现谁在用外挂响应是处理用外挂的人。三个层次缺一不可。3.1 预防层让服务器成为唯一真相来源预防层最核心的原则前面已经说过服务器不信任客户端。具体落地时有几个关键点。关键点一关键计算全部在服务器做。伤害、掉落、经验、金币这些数值的计算逻辑必须放在服务器。客户端只负责发送我使用了技能 A目标是 B这样的意图服务器根据当前状态算出结果再同步给客户端。这样外挂改客户端数值没有意义因为服务器根本不看。关键点二通信协议要加校验。常见做法是给每个数据包加时间戳和序列号服务器检查时间戳是否在合理范围内、序列号是否连续。如果外挂重放旧包或者篡改包内容校验就会失败。更严格的做法是加 HMAC 签名客户端和服务器共享一个密钥每个包都带签名服务器验签通过才处理。但密钥存在客户端里理论上可以被逆向提取所以这只是提高门槛不是绝对安全。关键点三移动和操作要做合理性检查。服务器要记录玩家的位置和速度如果两次上报的位置之间的距离超过了最大速度乘以时间间隔就判定为异常。这个检查要做在服务器不能做在客户端。我见过一个项目移动检查放在客户端外挂直接把检查函数 hook 掉形同虚设。3.2 检测层数据驱动的异常发现预防层能挡住大部分低级外挂但高级外挂尤其是自动化脚本很难靠预防挡住必须靠检测。检测的核心思路是正常玩家的行为有随机性外挂的行为往往过于规律。具体可以监控的指标包括操作间隔的方差正常玩家按键间隔有快有慢外挂的间隔往往非常固定。如果某个账号的操作间隔方差低于阈值就值得怀疑。在线时长正常玩家要睡觉、要工作外挂可以 24 小时在线。连续在线超过 20 小时的账号需要重点观察。移动轨迹正常玩家的移动路线有探索性外挂的路线往往是最优路径重复度极高。资源获取速率如果某个账号的单位时间资源获取量远超正常水平要么是外挂要么是游戏经济系统出了问题。这些指标单独看都可能有误报比如一个职业玩家可能操作很规律、在线时间很长。所以实际做的时候通常是多指标加权评分分数超过阈值才进入人工审核队列。我参与过的项目里这套系统把外挂检测的准确率从最初的 60% 提升到了 90% 以上误封率控制在 1% 以下。检测指标正常玩家特征外挂特征误报风险操作间隔方差较大极小职业玩家可能偏低连续在线时长通常小于 12 小时常超过 20 小时代练可能长时间在线移动路径重复度较低极高固定路线刷怪的正常玩家资源获取速率波动大稳定且偏高氪金玩家可能偏高3.3 响应层封号不是唯一手段也不该是首选发现外挂之后怎么处理很多人第一反应是封号。但封号是一把双刃剑封对了大快人心封错了玩家直接流失还会在社区里骂你。所以响应策略要分层。第一层是软性限制。比如把疑似外挂的账号移到一个隔离服务器只能和其他疑似外挂的账号一起玩。这样即使误判正常玩家也只是体验差一点不会直接丢账号。而且隔离服务器里的外挂互相竞争资源产出效率下降外挂的收益也会降低。第二层是收益清零。把外挂账号通过非法手段获得的资源、装备、等级回滚。这个操作要谨慎因为回滚可能影响正常交易——如果外挂把金币交易给了正常玩家回滚时怎么处理常见的做法是只回滚外挂账号自身的收益不追溯交易链避免误伤。第三层才是封号。封号通常用于屡教不改或者情节特别严重的账号。封号时要注意保留证据因为玩家可能会申诉。我见过一个项目因为封号时没有留日志玩家申诉时拿不出证据最后只能解封反而助长了外挂的气焰。提示反外挂的响应策略一定要有申诉通道。再好的检测系统也有误判给玩家一个申诉的机会既能纠正误判也能收集反馈优化检测模型。我个人的经验是申诉通道的响应时间不要超过 24 小时否则玩家会觉得你在敷衍。4. 存储安全与外挂防御的交汇点几个真实场景的拆解前面分别聊了存储和外挂但实际项目中这两者是交织在一起的。下面拆解几个我遇到过的真实场景已做脱敏处理看看存储设计如何影响外挂防御外挂又如何倒逼存储方案升级。4.1 场景一客户端存档被改导致排行榜污染早期我参与过一个模拟项目玩法是单机闯关加排行榜。排行榜的数据来自玩家通关后的得分得分存在客户端本地玩家通关后上传到服务器。这个设计有个致命漏洞玩家可以改本地存档里的得分再上传。结果排行榜前一百名全是 999999 分正常玩家根本挤不进去。修复方案是把得分计算移到服务器。玩家通关时客户端只上传通关了哪一关、用了哪些操作的摘要服务器根据摘要重新计算得分。这样玩家改本地存档没用因为服务器不算本地存档。但这个方案也有代价服务器要存储每一关的关卡配置和计算逻辑而且如果玩家操作摘要太大上传流量也会增加。最后我们做了折中只上传关键操作序列服务器用简化模型估算得分精度控制在 5% 以内。这个场景的教训是任何要参与全服比较的数据都不能存在客户端。排行榜、竞技场、公会贡献这些数据必须服务器计算、服务器存储。4.2 场景二内存修改器改金币服务器如何发现另一个场景是网游里的金币。玩家用内存修改器把本地显示的金币改成 999999然后去商店买东西。如果商店购买逻辑在客户端做玩家就真的买到了。如果购买逻辑在服务器做服务器一看玩家实际金币只有 100就会拒绝购买。但这里有个细节服务器怎么知道玩家实际金币是多少如果服务器每次都查数据库性能扛不住。所以通常是在玩家登录时把金币加载到服务器内存之后的操作都基于内存里的值。这时候如果外挂能改服务器内存那就麻烦了——但服务器内存外挂一般改不到因为外挂运行在玩家机器上和服务器是两个进程。所以这个场景的防御其实不难核心就是购买逻辑放服务器。我见过出问题的项目都是因为图省事把购买逻辑放客户端结果被外挂钻了空子。4.3 场景三自动化脚本刷资源存储层如何配合检测自动化脚本不修改数据只是模拟操作所以前面说的服务器校验、内存保护都挡不住。这时候存储层可以配合检测记录每个账号的资源获取日志分析获取模式。具体做法是每次玩家获得资源时服务器写一条日志包含时间戳、资源类型、数量、来源。然后离线分析这些日志找出异常模式。比如某个账号连续 72 小时每 5 秒获得一次资源每次数量完全一样这几乎可以确定是脚本。这个方案的关键是日志要存够久。如果只存 7 天很多慢速脚本比如每天只刷几小时就检测不到。我建议至少存 30 天有条件的话存 90 天。存储成本确实会增加但相比外挂造成的损失这个成本是值得的。场景外挂手段防御核心存储层配合排行榜污染改本地存档服务器计算得分不存客户端得分金币修改改本地内存购买逻辑放服务器服务器内存加载金币脚本刷资源模拟操作行为模式分析长期资源获取日志4.4 场景四存档回滚与外挂收益追溯的冲突最后一个场景比较微妙。假设一个玩家开了外挂刷了大量金币然后把金币交易给了另一个玩家。后来外挂被封金币要回滚。但接收金币的玩家可能是无辜的他的金币被回滚了他会不满。这个问题的本质是存储层要能追溯数据的来源和流向。如果存储设计时只存当前金币数不存交易流水那就无法区分哪些金币是外挂刷的、哪些是正常赚的。所以大型网游的存储设计通常会有交易流水表记录每一笔交易的双方、时间、数量。回滚时根据流水追溯只回滚外挂直接获得的收益不追溯已经正常消费的部分。这个设计的代价是存储量大幅增加。一笔交易一条记录一天可能几百万条。所以流水表通常要分表、要定期归档。但这是必要的成本没有流水外挂收益追溯就是一笔糊涂账。5. 给开发者的实操建议从设计阶段就考虑存储与反外挂聊了这么多原理和场景最后落到实操。如果你正在做一个项目不管是单机还是网游下面这些建议可以直接参考。5.1 单机项目存档设计的三条硬规则规则一存档必须原子写入。前面说过写临时文件再重命名。这个规则看起来简单但很多新手会忽略。我见过一个项目直接往存档文件里写玩家在保存时断电存档直接损坏几十小时的进度没了。这种问题一旦发生玩家会给差评而且无法挽回。规则二存档必须带版本号和校验。版本号用于迁移校验用于防损坏。校验用 CRC32 就够了不需要加密级别的哈希。版本号建议用整数每次格式变更加一并且写一个从旧版本到新版本的迁移函数。规则三至少保留一个备份。写新存档前把旧存档复制为.bak。这样即使新存档写坏了玩家还能回到上一个存档。备份文件不要和主存档放在同一个目录最好放在子目录里避免被误删。5.2 网游项目服务器校验的优先级排序网游的服务器校验不可能所有逻辑都放服务器因为那样服务器压力太大。所以要排优先级。我的经验是按下面的顺序排经济相关金币、装备、道具的获取和消耗必须服务器校验。这些直接影响游戏经济被外挂钻空子后果最严重。战斗结果伤害、死亡、掉落必须服务器计算。客户端只上报操作意图不上报结果。移动和位置服务器要做合理性检查但可以允许一定的客户端预测避免延迟感太强。社交和聊天可以放客户端但要有频率限制防止刷屏。这个排序的逻辑是越影响其他玩家体验的越要服务器校验。经济影响所有人战斗影响对手移动影响周围玩家聊天影响范围最小。5.3 反外挂的投入产出比什么时候该收手反外挂是一个无底洞投入再多也不能保证 100% 防住。所以要有投入产出比的意识。我的经验是当反外挂的投入超过外挂造成的损失时就该收手了。具体怎么衡量可以算一笔账外挂导致的玩家流失、收入下降、客服成本加起来是多少反外挂的人力、服务器、误封补偿加起来是多少。如果后者大于前者说明当前的防御强度已经够了再加强就是过度防御。但这不意味着可以放任外挂。我的建议是保持一个基础防御水平然后根据外挂的活跃程度动态调整。外挂猖獗时加强检测外挂平静时降低投入。这样既能控制成本又不会让外挂失控。提示反外挂的误封率是一个关键指标。误封率太高正常玩家会流失误封率太低外挂会漏网。我个人的经验是误封率控制在 0.1% 到 1% 之间比较合理。低于 0.1%说明检测太松高于 1%说明检测太激进。5.4 一个容易被忽略的点日志的存储和清理最后说一个容易被忽略的点日志。反外挂依赖日志但日志本身也要存储也要清理。如果日志无限增长磁盘会满数据库会慢。所以日志要有生命周期管理。我的做法是热日志最近 7 天存数据库支持实时查询温日志7 天到 30 天存对象存储需要时异步加载冷日志30 天以上压缩归档只在处理申诉时调取。这样既保证了检测需求又控制了存储成本。日志清理时要注意合规涉及玩家个人信息的日志清理前要脱敏涉及交易流水的日志要保留足够长时间以备对账和追溯。这个时间通常由业务和法务共同确定不能由技术单方面决定。5.5 从项目一开始就做对存储与反外挂的设计检查清单如果你正在立项下面这份检查清单可以直接用单机存档是否原子写入是否有备份是否有版本号和校验网游的关键数据经济、战斗是否服务器计算客户端是否只上报意图通信协议是否有时间戳、序列号、签名校验是否有行为检测系统检测指标是否覆盖操作间隔、在线时长、移动轨迹、资源获取速率是否有申诉通道响应时间是否在 24 小时内是否有交易流水能否追溯外挂收益日志是否有生命周期管理热、温、冷是否分层反外挂的投入产出比是否合理误封率是否在 0.1% 到 1% 之间这份清单看起来长但每一条都是踩过坑之后总结出来的。项目早期多花一天做对后期可能省下一周的救火时间。我在实际项目中的体会是存储和外挂这两个话题表面上是技术问题实际上是信任设计问题。单机游戏信任玩家所以存储可以简单外挂可以不管网游不信任玩家所以存储要服务器化外挂要严防死守。理解了这个底层逻辑具体的技术选型和方案设计就有了方向不会跑偏。
阅读完成 · 觉得有帮助?