惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

S
Schneier on Security
The Register - Security
The Register - Security
月光博客
月光博客
freeCodeCamp Programming Tutorials: Python, JavaScript, Git & More
OSCHINA 社区最新新闻
OSCHINA 社区最新新闻
The GitHub Blog
The GitHub Blog
博客园 - 司徒正美
罗磊的独立博客
U
Unit 42
S
SegmentFault 最新的问题
Y
Y Combinator Blog
博客园_首页
Hugging Face - Blog
Hugging Face - Blog
J
Java Code Geeks
Schneier on Security
Schneier on Security
Know Your Adversary
Know Your Adversary
C
Check Point Blog
Cyber Security Advisories - MS-ISAC
Cyber Security Advisories - MS-ISAC
Simon Willison's Weblog
Simon Willison's Weblog
V
Vulnerabilities – Threatpost
让小产品的独立变现更简单 - ezindie.com
让小产品的独立变现更简单 - ezindie.com
阮一峰的网络日志
阮一峰的网络日志
The Hacker News
The Hacker News
博客园 - 叶小钗
C
Cybersecurity and Infrastructure Security Agency CISA
Spread Privacy
Spread Privacy
L
LINUX DO - 热门话题
T
The Exploit Database - CXSecurity.com
P
Palo Alto Networks Blog
cs.CL updates on arXiv.org
cs.CL updates on arXiv.org
Latest news
Latest news
L
Lohrmann on Cybersecurity
A
About on SuperTechFans
L
LangChain Blog
Stack Overflow Blog
Stack Overflow Blog
S
Securelist
A
Arctic Wolf
D
Darknet – Hacking Tools, Hacker News & Cyber Security
T
Threatpost
Scott Helme
Scott Helme
博客园 - 聂微东
博客园 - 【当耐特】
T
Tenable Blog
I
Intezer
D
DataBreaches.Net
B
Blog RSS Feed
Security Latest
Security Latest
C
Cisco Blogs
T
Tor Project blog
N
Netflix TechBlog - Medium

风萧古道 - 勤学苦练,年复一年

游戏服务器开发经验(五)应对复杂需求 沉浸式体验东汉末年生活 - 《真三国无双 起源》玩后感 怒其不争!致2025年HLTV的Top18-NiKo Linux家用服务器维护指南 游戏服务器开发经验(四)避免写Bug的习惯、技巧和心态 游戏服务器开发经验(二)避免内存泄露 游戏服务器开发经验(一)道具防刷 35岁找不到工作,绝对不会是软件开发人员的结局 用爱发电项目开发两个月的心得体会 以魏延“子午谷奇谋”讨论软件需求可行性问题 MQTT协议中可变长度的具体计算方式(有计算过程解析) 关于游戏服务器配置表功能的探讨 Java并发编程中上锁的几种方式 如何用C++分割一个字符串? CSAPP第二章-信息的表示与处理 我的自我介绍 Windows XP虚拟机中文版无需激活下载 Java TreeSet的一些用法和特性 Linux C++ Socket实战 传统软件服务器与游戏服务器架构区别 独立个人项目开发心得 - 任务切分、挑战性、实用性和半途而废 使用Python实现简单UDP Ping 使用Python开发一个简单的web服务器 Kotlin手动实现一个最简单的哈希表 Kotlin实现二叉堆、大顶堆、优先级队列 搭建Spark实战环境(3台linux虚拟机集群)(一)样板机的搭建 Springboot操作MongoDB,包括增改查及复杂操作 Unison在Linux下的安装与使用 Java实现类似WINSCP访问远程Linux服务器,执行命令、上传文件、下载文件 一个被废弃的项目——自动爬取信息然后发给我自己邮箱上 Python连接MongoDB和Oracle实战 MongoDB常用查询语句 vue和springboot项目部署到Linux服务器 Python的一些用法(可能不定时更新) java正则表达式 - 双反斜杠(\)和Pattern的matches()与find() 简述爬虫对两种网站的不同爬取方式 Vue的路由配置及手动改地址栏为啥又跳转回来?? [JavaScript]JS基础知识 [Mybatis]逆向工程中Select语句查询不出‘TEXT’字段 [编译原理]FIRST、FOLLOW和SELECT [Spring]Spring学习笔记 [算法]分布估计算法 - 一种求解多维背包问题的混合分布估计算法_王凌 [日常]我做独立博客的原因 人间值得 深入理解计算机系统 想想就开心! 最重要的事,只有一件 藏书 被讨厌的勇气 关于 简历 朋友 尊重自己:给予与接收的心灵艺术
游戏服务器开发经验(三)线上维护
JonathanLin · 2025-07-13 · via 风萧古道 - 勤学苦练,年复一年

游戏项目公测上线之后,玩家总能找到我们预期之外的方式,做出超乎预料的行为,线上服务器就会有各式各样的错误情况。即使在开发过程中我们已经尽力去编写健壮的代码,完成黑盒白盒测试和代码Review,也无法完全避免线上的各种问题。

不过我们不需要为此悲观。在Java中,底层的虚拟机足够健壮,此外JVM也提供了很多工具给我们进行排查和修复问题。

重载class以修复错误逻辑

在线上服务器中业务逻辑有问题时,我们可以针对出问题的类,在本地修复代码逻辑后,编译出新的.class文件,然后上传到服务器能访问的文件夹目录中,使用Java虚拟机的redefineClasses()方法,将新编译的正确业务逻辑的类class重载到线上的JVM虚拟机中,覆盖之前的class。

如此一来,线上执行的class被替换,后续具体的方法被调用时,执行的是新class的代码业务逻辑。

在线上执行本地编写的代码

使用Groovy脚本,可以在线上服务器中运行代码。在本地编写好groovy脚本代码,通过线上提供的接口(可以是后台接口,或者是GM管理员指令入口)执行groovy脚本内容。

如此一来,当我们对线上服务器的情况有疑惑,需要打印具体的内容;或是希望能修复线上用户的错误数据,就可以使用直接执行脚本代码进行操作。

需要注意的是,Groovy脚本实际上是对使用者要求非常高的一项技术,几乎是线上游戏修改器般、黑魔法般的存在。用得好,可以快速修复玩家数据,能够快速查到服务器异常状态;用得不好,甚至可以直接删库跑路。

关于重载class和执行groovy脚本,代码可参考 https://github.com/jonathanlin768/JavaHotFix 仓库。

策划配表重新加载

有一些在线无感知的游戏热更新是通过策划配表重新加载来实现的。偶尔有一些版本开发过程中,可以先把代码更新到服务器线上,但是不着急马上把新功能开启给玩家,策划可以在功能还未和玩家见面时,修改新功能相关的数值,也可以通过修改活动时间等方式来动态调整功能的开启。

既然策划配表重载可以调整功能的开启,那么必然也可以完成功能的关闭。在开发过程中可以定义一张系统功能表,上面记录各种功能的id,并且有一个强制关闭的标记。开发过程中如果判定功能id关闭,则不允许执行后续逻辑。

如此一来,如果线上已出现异常情况(如新玩法无法正常继续、奖励被刷),则可以快速先关闭具体的功能id,让后来的玩家无法继续造成错误情况。

日志

所有人都知道要打印日志,但是到底打印什么日志最有用的呢?不同的项目有不同的答案。但是打日志是非常有用的,这一点毋庸置疑。

根据我的理解,要打印日志的情况有:

  1. 策划配表配错时,打warning。在开发阶段,策划要对自己的表进行测试。他们很有可能填错了数值而不知道。这时候打印出日志可以快速定位到底哪里配错了。
  2. 千万不能执行到的错误逻辑,打error。开发过程中如果发现一些情况是异常的,是正常逻辑肯定走不进来的,甚至可能因为异常而导致记录了错误的状态,则一定要打印日志。比如某个玩法一定要组队才能游玩,但是玩家在没组队的情况下请求游玩,说明玩家在之前已有错误,因此需要打印日志以记录情况。
  3. 玩家多次反馈出问题的功能逻辑,在最关键处打info。比如某个玩法结束后发奖失败,则需要在发奖的各个阶段打印日志,用以分析哪一个步骤出现问题。
  4. 玩家核心玩法行为日志记录,这个可以打info,也可以使用额外的行为日志记录(因为玩家行为可能很多,全部打到log文件会太大,所以可以将其通过消息队列发送到另一个数据仓库中进行汇总分析)。比如MMO游戏的玩家地图跳转,SLG游戏的战斗记录等。

不要打印日志的情况有:

  1. 接口的业务逻辑中,已有明确错误提示后返回的情况。如在商店道具购买时道具不足,客户端已能收到有意义的文字错误信息,则再在服务器打印错误日志是意义不大的。
  2. 尽量不要在太公用的业务逻辑里打印日志,即使要打印也要加一些条件。这是因为公用逻辑会被各个不同的业务调用,并且一般是主要的玩法。打印日志不加条件限制的话,会造成日志过多而不方便排查。

什么时候用info,warning,error? 这个问题也是不同的项目有不同的答案。

我的理解是:为了观察玩家和服务器状态的用info,不影响玩家继续游玩,但是确实可能有异常的用warning,要是执行到就一定是有错误的用error。

版本更新前的内部服务器日志一定要关注 当版本开发完毕,到测试阶段时,一定不要觉得测试人员没有反馈就完事大吉了。一定要关注内部服务器的日志,尤其是新打印的日志。内网能看到的日志,外网一定能看到;而外网能看到的日志,内网也一定能复现。

最后,不用担心日志打太多的问题。查不到问题的原因才是更坏的情况。

服务器状态监控

版本更新后,新玩法开启的几天,一定要有意识观察线上服务器的CPU和内存使用状态。就算没有搭grafana那样的工具,你也可以连到线上服务器通过Linux命令查看是否异常。如果CPU过高或者内存不够,则需要尽快把问题反馈出来,从而尽快分析并且处理问题。

这绝对是一个好习惯,原因是有一些问题可能玩家遇到了没反馈出来,他们可能觉得无伤大雅,但是那些没反馈的问题,对服务器的状态也许存在一些风险。

线上问题定位

前文所述内容都是为了避免和修复线上问题。而如何定位线上问题,才是服务器开发中最困难的一环。

这件事的困难之处在于,

  1. 无法复现玩家所遇到的情景;
  2. 玩家反馈的信息是玩家理解的内容,不一定是服务器上的问题所在
  3. 情况是耦合发生的,触发条件苛刻

而当我们花了大量时间排查,最后可能发现只要改不超过10行代码就能解决问题。这或许有一点幽默,但是却是经常发生的情况。

但当我在此回看过去,我发现线上问题一定是能查到结果的,只不过是时间成本问题罢了。真正阻碍我们查到线上问题症结的是我们的内心。

要相信问题是一定能被排查到的

计算机科学是一门年轻的学科。它远没有达到物理学中“电子双缝干涉”那样深奥而暂时无解的情况(当然你一定要拿“比特跳变”这种极端罕见而异常的情况抬杠的话,我就认输)。而游戏服务器的环境配置,都是由你我这样的凡人搭建的。游戏服务器的业务逻辑更是由团队里的大家编写的。所以一切的问题是一定能定位得到的。

而在排查过程中,要相信一点:

“所有的问题都会解决的”

当你在排查线上问题的时候,不要惧怕那些线上问题,也不要惧怕那些给你施压的人。程序员常有的完美主义让我们认为一切的代码问题都是可控的,因为业务逻辑是我们一行一行写出来的。但是这不代表代码就一定没有问题。恰恰相反,出问题才是常态。出问题不代表我们不好不优秀,而是“我们还有未知的内容需要去学习”。不要惧怕那些给你施压的人,因为与问题正面对抗的人,就是屏幕前的你。你本应该是伟大而强大的。专注于你要排查的问题,像侦探一样耐心地分析各种蛛丝马迹,所有的问题都会解决的。

分析玩家所述内容,排查异常情况

当玩家反馈错误的情况时,根据玩家的描述定位到具体的玩法,这时不要直接钻到玩法的业务逻辑代码之中,那就如大海捞针了。我们应该根据玩家提供的玩法信息,游玩时间,反过来去查找日志,观察其在出问题的前后执行了什么操作、消耗和获得了什么道具、游玩了其他的什么玩法,在此期间是否有可疑的错误日志等等。

这一步同时你需要去理解玩法的具体内容,比如时间,状态等。通过日志和玩法内容,去勾勒玩家当时的情况。

模拟玩家所遇到的环境

有条件的话,尽量能把出问题的玩家的数据导入到自己的服务器上。这样我们就可以在本地进行各种尝试。如果能把玩家出问题前后的玩家数据拿到本地则更佳。即使没有玩家数据也没关系,在内部环境中模拟玩家所遇到的情况,通过玩家提供的视频,和服务器上的日志,可以得到玩家的状态,进行游玩,也一定会有新的理解。如果实在是无法在本地复现,你也可以用脚本到服务器上执行一些代码,打印玩家现在的状态(现在的状态也是有意义的,玩家有自己的方式再次复现)。

仔细甄别“问题情况”和“猜测的原因”

当线上问题发生时,如果是大问题或者难排查的问题,会有很多人的噪音在耳边叽叽喳喳。他们提供的思路不一定是错的,但是可能和当前最重要的问题没有关系。一定要分析出问题的情况在哪里,专注于要修的问题的情况。会有一些对游戏很了解,但没有看代码的人提出很多猜测,要理性地分辨这些猜测,小心仔细地验证各方论点。

如果真的查不出,就打印排查日志

分析玩家行为、导玩家数据到本地、模拟出问题的环境、并且各方讨论也没有结果,那么请打印好排查日志。

排查日志一般是为了以下目的:

  1. 检查关键代码逻辑是否被访问到;
  2. 当执行代码逻辑到某一处时,检查玩家的状态是否正确。

因此这样的排查日志,需要打印的细节一定要越丰富越好。如果打印的内容很多,最好能用StringBuilder将多行内容拼成一个String,然后直接用一行log打印出来,以避免日志收集器收录到只言片语。

你甚至可以针对某一个玩家游玩时,仅针对他打印各种细节日志,甚至可以包括程序调用的堆栈信息。