windy path

凌虚

服务器开发工程师
勤学苦练,年复一年

怒其不争!致2025年HLTV的Top18-NiKo

杂谈 CS2 电竞评论

2025年就要过去了。野榜(HLTV)也开始干活,进行一年一度的CS2职业选手的排名。 常年Top前10,甚至排到前5的波黑步枪手NiKo,今年被排到了18名。 这无疑让人有些大跌眼镜,但是仔细想来又挺正常。今年的NiKo不再有过去的统治力。或者说他一直是这样,有他在的队伍总是起伏不定。没有起错的外号,“虾哥”这个外号绝对不是随便起的。 我只是一个关注两年CS2比赛的新人观众,太多的数据我也没有时间去HLTV翻阅,本文仅从一位普通观众视角,对NiKo作为职业选手的缺陷进行简单的观测。 本文同时也是一位NiKo粉丝的脱粉宣告。 从2024年初到2025年底,CS2的王朝更迭令人眼花缭乱。 2024年赛事回顾 NiKo在2024年上半年,哥本哈根Major依靠着运气侥幸踏过传奇组,然后四强收尾。仅在IEM达拉斯拿下一冠,且此冠军是在Hooxi不在,由雇佣兵Stewie2K临时顶替,而拿下的。 2024年G2进行人员更迭,马超和Snax的加入,让他们拿下Blast秋决和总决。秋决Bo5中结束了对NAVI的九连败,而总决更是把2024年的魔童Donk打了个3-0剃光头。 磨合得这么好的G2,在12月上海Major,有无数的中国粉丝为其呐喊,绝对的主场优势。如何? 很遗憾,NiKo带领G2倒在了上海Major的四强。 这件事最有意思的是,在上海Major开打前,社会上已经得知NiKo要离开G2,前往Falcons。 NiKo的错误:缺乏团队精神 将转会信息泄露,是一个极其错误的决定,是一个没有职业道德的选择。即使CS2赛程紧密,但每半年至少快一个月的休赛期。 对比Faze的Ropz,在上海Major前终于找到了自己世一自的状态,开出了“冲上云霄的银河战舰”,然后才完成了转会。我们不排除Ropz是否提前接触过Vitality的可能性,但是Ropz没有在赛前漏出任何转会信息,并且战斗到了上海Major的最后一回合。 对比cadian,为什么有那么多人时至今日还是喜欢cadian,正是因为他身上有一种团队精神,能够在赛前说出鼓舞人心的话。虽然后续与总监在Liquid纷纷扰扰,虽然动态防守的战术和指挥狙已经过时,但是有一段时间很多粉丝还是喜欢他,A队也还愿意再给他一次机会。 在那个NiKo锤桌子的回合,1v5最后没有完成,但显然隔壁小马受到了鼓舞,下一把马上在A1完成双杀打开局面。这就是明星选手的力量。关键局站出来,即使没有完成惊世壮举,也能给团队带来力量。 但NiKo不知因为什么原因释放了离队信号。团队的绳松了。他释放离队信号的行为,让原本拿下Major前最后一个大赛冠军的G2失去了信心。他似乎从来都不是团队里的大当家,但是又握着极高的话语权,左右着团队的战术走向。而在比赛之外,又由于他平常时华丽的瞄准,Major冠军的缺憾,悲壮英雄的故事,加上长期健身和帅气的欧洲面容,让他的粉丝越来越多。而粉丝也同时在给他和G2压力。压力、士气、战术都在影响那只好不容易磨合完毕的G2,最后过不了Faze也是情理之中。 另外,有爆料称在2023年,NiKo就已和Falcons私下进行了近半年的接触。这真的让人很难评价他是否真的愿意和G2同进共退。 2025年赛事回顾 NiKo在2025年上半年,在m0NESY还未到来时,在PGL布加勒斯特拿下了第一个冠军。参赛的队伍中没有Vitality和Spirit,但是至少也是S+赛事。但随后就再未得过冠军。 在拿下2025年首冠之后,m0NESY跟随NiKo的步伐来到了Falcons。 然后就在IEM墨尔本与Vitality大战五场,2-3惜败,而最后一张图的nuke,Falcons甚至12-6领先Vitality,最后被逆转。就算暂时把失败原因归结于磨合问题,那我们也无法解释奥斯汀Major的Falcons倒在了stage2,甚至在那个小镇B点下包5V3中被逐个击破。 黑豹Furia强势崛起,而NiKo在布达佩斯Major前最后一场大赛中,最后一局在荒漠迷城交出了糟糕的答卷,隔壁的m0NESY终于找到了自信和手感,NiKo却没法保持过去的高水平了。 NiKo的错误:对m0NESY太过宠溺 在各路媒体和自媒体的叙事中,我们总是听到所谓“NiKo看到m0NESY,仿佛看到年轻时的自己”等类似的表述。 但是这真的是一件好事吗?m0NESY真的成长了吗? 2022年的m0NESY那飘逸自信的打法,到2025年已经很少见了。这其中确实有CSGO到CS2的变革,但是ZywOo和s1mple依旧能发光发热,却没有像m0NESY那样不稳定。 NiKo在潜移默化中把自己性格中的缺陷,慢慢地转移到了m0NESY身上。 这确实是我的猜测,但是从数据上看,2024年上半年的G2唯一当人的m0NESY,和下半年失恋分手的m0NESY简直是两个人。而2025年更甚,上半年因为NiKo一句“hide”而获得隐形人的称号,然后在布达佩斯Major前终于找回状态大杀四方,网友们像失忆了一样,忘记了四个月前大家还在玩hide梗。 这就是m0NESY,在NiKo身边学会了极致的瞄准,也学会了状态波动。 NiKo在Falcons的比赛中,几乎只与m0NESY有互动。在Falcons,他不在有自己的表哥,也不再有自己最信赖的指挥Hooxi,整个队伍被切成两块。这就是为什么他们很多战术打不出来,就妄图增加火力解决问题。 NiKo的错误:来到Falcons话语权太高,但又不管赛训 2025年的比赛已全部结束,我们可以得到一个结论:Falcons的赛训组甚至不如G2。G2时期的Hooxi和Snax承担了非常多的战术任务,NiKo当时还没有那么高的话语权。但是到了Falcons,在年中居然曝出“无电脑训练”的笑话,在休赛期选择与m0NESY来中国圈米,而白白错过宝贵的团队磨合时间。当我听闻他要来参加“盐城Major”,作为粉丝真是怒其不争,奥斯汀Major stage2的耻辱收场,难道不应该回去好好反思吗?而且同时存在没有团队精神的问题,他至少可以带上kyxsan,TeSeS和kyousuke一起过来打个表演赛,一起品尝中国美食,给大家都涨涨粉丝,但是他也没有提出这个想法。 NiKo的错误:没有纪律性,打法严重落后这个时代,火力也无法保持 在Hooxi指挥时期,NiKo少见的服从Hooxi的调配,甚至你能看见Hooxi不停说教的场面。到了Hooxi火力衰弱,换上Snax,再到kyxsan,你肉眼可见能看到两位新指挥的窘迫。新来的CS2观众越来越多,所有人都在举着NiKo,m0NESY的牌子,队内地位不足的情况下,很难真正完成战术配合。s1mple和ZywOo为什么最终能有统治力?一方面是他们手握最强武器AWP,另一方面他们都选择了团队,21年的s1mple终于磨平棱角融入NAVI,而ZywOo更是对apex言听计从。但是NiKo不是这样。他表面上总是在采访中说一些会努力会加油的话,但是实际上又没有跟随队友打出非常有纪律性的配合。G2时期就已经有过多次小镇多打少被翻盘,到了Falcons更甚。而他性格中的软弱让他在重要时刻只相信自己的瞄准,偏偏他又在一个全部围绕他建队的石油佬队伍中,他能够拿到CT方的指挥权,然后发挥失常。他会在队友杀穿的时候开始虾,而现在年纪终于开始拖累他的反应,他越来越无法在团队需要的时候站出来。 然而他所在的队伍里已经很久没有打出优美的道具覆盖配合了。他们也许练过,但是到了关键时刻还是希望依靠队伍里的明星选手硬解。硬解,就意味着放弃了轻松的路,选择消耗精神力去得分,这样在长时间的比赛中必然会更加疲惫。所以你会听到NiKo在第四局说自己要睡着了。 而有意思的是,在G2完成换血之后,表格Hunter带着G2居然在强者如林的2025年也拿下了一个冠军,BLAST 伦敦公开赛 2025 决赛,甚至面对的是上半年如日中天的Vitality。G2变得纪律了起来,依靠对Vitality的针对进行得分。即使队里不再有顶尖明星选手,比NiKo更年长的Hunter还是承担了责任。 杜兰特?Uzi?抱团?软脚? 当有不看CS比赛的朋友问起NiKo类比NBA、LOL比赛的谁,我们总是在沉思之后说出两个名字,凯文杜兰特和Uzi。 但今年看完比赛之后,我认为NiKo既没有凯文杜兰特独步天下的射术和硬解能力,也没有Uzi巅峰时从S3到S9在下路的统治力。 2018年杜兰特在金州勇士拿到第二个冠军,而2018年NiKo也选择去Faze组建银河战舰。关于抱团,他们是一样的。但很可惜的是,NiKo的性格和关键时刻的硬解能力根本无法做团队里的大当家,却长期拥有大当家的粉丝数和权利。而杜兰特虽然性格也有缺陷,但是能在勇士完成硬解,甘愿当库里身后的二当家。即使杜兰特身负太多争议甚至骂名,但他最后抓住了自己的巅峰期,拿到了NBA总冠军。NiKo在2025年已经尽显老态,不得不承认他真的要离开自己的巅峰期了。 而NBA另一位著名软脚虾是詹姆斯哈登。虽然哈登也有夜店王子的称号,身材管理也一直是波动的,但是哈登近年来一直在寻找合适的团队,今年还能拿下50+的得分表演。而NiKo肉眼可见的越来越软弱,在关键时刻越来越查无此人。Vitality在下半年也有一段时期非常挣扎,但是我们肉眼可见ZywOo和Vitality从未低头放弃,整个团队虽然有噪音传出,但是大家还在咬牙努力,最后完成了Major连冠。 结语 2025年,在Falcons的NiKo最终真的成了一位大少爷,他潜移默化地影响着m0NESY职业生涯,他的能力在慢慢下滑,他不再是最顶尖的那批选手,观众们已经很久没有看见他在大赛时关键时刻的硬解能力。 由于话语权过高而队伍中的指挥无法完成现代的战术,过于依赖个人能力,而他终于到了能力下滑的时候了。 粉丝过多、石油佬的赞助,让他不需要真的拥有Major冠军就能轻松得到财富。 同时缺乏团队精神,使他即使拥有丰富的CS比赛经验,他也不愿意潜心研究各大战队战术,无法真的主动坐到团队正中央完成指挥任务。他一直以来,从头到尾都只相信自己的瞄准,正如他出道时那样,从没有变过。 关于解说玩机器说过的“NiKo最终会拿到Major冠军,只不过是以指挥的身份。”我过去也相信这句话,但我看完了布达佩斯Major,仔细回顾这两年NiKo急剧下滑的速度,各种性格缺陷和能力下滑的情况,我认为他已经无缘Major冠军。 我已无法喜欢这个没有Major冠军,屡败屡战的英雄故事。我也无法再继续关注NiKo的比赛。当卸下NiKo和他的故事滤镜,会发现NiKo的CS并不优美。而2025年的ZywOo,展现出了一位明星选手应该有的风范,他有志气,谦逊,努力,团队,并且有一颗大心脏。这才是我们大家应该喜欢的职业选手。

十二月 27, 2025 · JonathanLin

Linux家用服务器维护指南

技术文章 DIY折腾

2025年,对绝大部分普通人来说,真的没必要折腾家用服务器。除了安装好的那一刻感到的快乐之外,剩下的就只有吃灰和浪费电而已。 但是这却是一次难得的和Linux亲密接触的机会。如果你感兴趣,那么玩玩也不错。 标题中“家用服务器”指的是什么 本文中探讨的家用服务器,指的是购买的小型主机设备(Intel NUC系列等),自己安装操作系统进行使用的服务器。不包括群晖等成品NAS设备。 用户购买小型服务器,在上面安装Linux系统,然后自己安装各类服务,自己管理端口,内存,磁盘,自己进行运维。 一般人的家用服务器无非用来做两件事: 照片存储 下载4K超高清无删减电影 程序员可能会多做几件事: 部署自己的git私有仓库 定时任务执行自定义脚本 在线电子书(有些技术类电子书真的只有影印版pdf,临时想看还要下载,很麻烦) 极客玩家可能会多做几件事: 部署游戏服务器私服(幻兽帕鲁等) 养宠物的朋友可能会多做几件事: 将家用摄像头的录像上传到自己的服务器永久保存 以下是各种建议 建议一:一定要用Docker进行部署 既然选择了自己维护,那么你大概率下载到的都是开源的软件。而这些软件的开发人员的想法是不一样的。 有的程序员喜欢通过全局变量的方式控制软件,有的程序员只建议用特定的方式启动软件等等。 而且安装的方式也不一定是规范的。比如有的软件允许你用root安装,在没有经验的时候你可能会这么做,而有的软件在文档里强烈建议你为它的软件建立一个用户组和用户。你可能这个软件跟着文档安装在了/usr/local里,但是下一个软件可能放在/home/用户 下。而这会让未来的你一头雾水,毕竟这台服务器一般不会连上去使用。 而docker提供了一种更优雅的方案,每个软件都运行在自己的容器中,容器里有自己独立的变量和目录。然后你可以在docker-compose.yml中指定容器内端口对应的服务器端口,容器内目录对应的服务器目录等。另外,如果你运行的软件都需要用数据库,那么你可以用容器安装一个数据库,比如MySQL8,然后定义一个数据库的容器network。其他软件的容器可以访问这个network。 比如,我的gitea的docker-compose.yml内容如下: services: gitea: image: docker.gitea.com/gitea:1.24.0 container_name: gitea restart: always ports: - "13300:3000" - "13022:22" volumes: - /data/gitea/data:/data - /etc/timezone:/etc/timezone:ro - /etc/localtime:/etc/localtime:ro environment: - USER_UID=1000 - USER_GID=1000 - GITEA__database__DB_TYPE=mysql - GITEA__database__HOST=mysql8 - GITEA__database__PASSWD=gitea123 - GITEA__database__NAME=gitea - GITEA__database__USER=gitea networks: - mysql8_database_network networks: mysql8_database_network: external: true 而我的MySQL8容器的docker-compose.yml内容如下: services: mysql8: image: mysql:8....

十二月 6, 2025 · JonathanLin

游戏服务器开发经验(四)避免写Bug的习惯、技巧和心态

技术文章 游戏服务器开发经验

编写此文的目的,并不是教大家如何写出没有Bug的代码。 没有Bug是不可能的。 话虽如此,但是我们完全可以在开发阶段,避免一些低级的Bug。笔者在游戏上线后维护服务器的时间较长,我发现线上所谓的严重问题,真正出问题的代码就是那么几行(一般不会超过10行)。也就是说,这些容易出问题的部分,并不是系统中复杂的部分。因此,我们一定有办法在事前对其进行规避。 我认为避免Bug需要我们有良好的开发习惯,和冷静耐心的心态。于此之上,再通过经验加以一些技巧,就可以避免很多大小Bug了。其中,我认为最重要的是习惯。好的习惯不会让你多花太多时间,但是能尽早发现问题。 习惯和技巧 自测 自测,即代码编写结束后,在本地或开发环境中运行,然后通过白盒或者黑盒的方式测试自己开发的代码。 在本地开发时,务必拥有一个开发环境,这个开发环境可以测试自己正在开发的、还未合并到主干的代码。有了这个环境,你就可以快速地将自己新开发的内容运行起来。如果可以Debug查看每一行的结果就更好了。 自测的意义在于,你可以成为自己代码最早的测试人员,验证自己的思路是否真的被正确开发。自测能给你一个机会,让你执行自己的代码,并且能让以下容易遗漏和犯错的部分得到快速验证: 仔细检查if语句的逻辑判断是否正确 检查玩家数据状态是否正确改变 注销后再登录,即可检查玩家数据是否正确写入 如果你的环境不允许你打断点,或者一些情况比较复杂(多线程环境或有延时函数等),你可以在本地多打一些debug级别的日志,查看数据的处理过程。 Q&A 我等客户端同学开发完,联调的时候再一起测不行吗? 可以,但是在联调时你的工作重心已经到了其他地方,你会忽略或遗忘当时写这段代码的一些想法。而这些想法很有可能就是容易出错的地方。另外客户端同学一般只会测自己感兴趣或者担心出问题的部分,他们关注的部分不一定和你一致。 要是我都写正确了,自测不就是浪费时间吗? 不。我们人类一定会犯错,即使我们逻辑写对了,也有可能会把诸如道具产销日志等一些不影响功能,但是运营、策划需要的部分遗漏,也有可能会忘记打warning日志或者error日志,或者是发现一些变量名不合适,或者是重新回看的时候发现代码结构冗余臃肿。这些都是值得修改优化的点。如果我们不自测,那么可能未来就没有时间拿来安排自测了。 服务器开发逻辑的时候,没有现成可用的客户端包进行测试,自测很麻烦。 是的,很麻烦,但是并非毫无办法,接下来会介绍一些自测的技巧。 自测的技巧 对于简单的客户端和服务器交互的接口,可以想办法在客户端手动输入协议号及协议结构,发送指定的协议数据,然后在服务器打Debug断点进行检查。 对于玩家数据经常变化的新功能,可以在数据变化推送的时候打印debug日志,通过日志来确认玩家数据正确性。 对于情况比较复杂的功能,可以先写一些私下用的一次性gm,先模拟复杂的情况,然后再进行目标功能的测试。、 总之,只要你想测到你的功能,在环境允许的情况下,一定有办法测得到。 变量及函数命名技巧 表示某事的次数不要用Times,而是用Count。原因是Times容易和Time混淆,无法一眼区分到底是次数还是时间 表示时间长度不要用Time,而是用Duration。因为“时间”这个概念本身就有歧义,可以表示某个时刻,也可以表示某一段时间。建议用Time甚至Timestamp,TimeMillis,TimeSeconds来具体指代某个时刻的时间戳,用duration来指代时间段。 如果针对某个对象要设置某个属性的值,并且设置时还要对其有特殊逻辑,并且项目中已经装了lombok依赖,则最好不要再命名setXXX(),因为这样会重写lombok的@Setter省略的函数,并且容易让阅读者忽略这个函数内有特殊逻辑。 不要使用setXXX(),但是你可以针对这个业务逻辑,定义一个更贴合业务的逻辑,比如设置玩家积分时,同时更新排行榜上的玩家数据,则setScore()会让人误以为这个函数只是给对象里的score属性赋值,而如果是叫updateScore()或updateScoreAndRank(),则可以直观的知道,这个函数要更新积分,并且可能会更新排行榜。 代码分段 代码一定要分段写。印象中有一些书籍或者公司里的开发规范中约定,每个函数不要超过多少多少行。我想也许是担心大家把函数写得太长,可读性变差而设的规则。但是,我认为真正的问题在于可读性。在某些特定情况下,一些函数可能来不及优化,不得不写得很长,但我们至少可以通过分段和注释,对代码进行梳理。 一个好的代码分段结构,大概如下: //单行注释A:介绍接下来一小段要执行什么内容 aaaaa; bbbbb; if (c) { ddd; ..... } //单行注释B:介绍接下来一小段要执行什么内容 aaaaa; bbbbb; if (c) { ddd; ..... } //... 注意,在两段代码之间,有一个空行。这个空行很重要。他代表着上面这段代码的含义是最上面单行注释A,而下一段代码的含义是单行注释B。如此,阅读者可以很轻松地理解这两段代码到底分别执行了什么逻辑。 分段的技巧 在理清楚需求的业务逻辑后,你可以先写一个空函数,然后再函数内只写注释,不写逻辑。 public void reward() { //判断功能开启 //判断活动开启 //判断玩家传参 //判断玩家领取状态 //扣除玩家消耗 //修改状态 //发放奖励 //推送奖励弹窗 //返回成功给客户端 } 当你把这个函数需要做的事情梳理清除,简单写出注释,分段也是水到渠成的事情了。...

八月 10, 2025 · JonathanLin

游戏服务器开发经验(三)线上维护

技术文章 游戏服务器开发经验

游戏项目公测上线之后,玩家总能找到我们预期之外的方式,做出超乎预料的行为,线上服务器就会有各式各样的错误情况。即使在开发过程中我们已经尽力去编写健壮的代码,完成黑盒白盒测试和代码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,让后来的玩家无法继续造成错误情况。 日志 所有人都知道要打印日志,但是到底打印什么日志最有用的呢?不同的项目有不同的答案。但是打日志是非常有用的,这一点毋庸置疑。 根据我的理解,要打印日志的情况有: 策划配表配错时,打warning。在开发阶段,策划要对自己的表进行测试。他们很有可能填错了数值而不知道。这时候打印出日志可以快速定位到底哪里配错了。 千万不能执行到的错误逻辑,打error。开发过程中如果发现一些情况是异常的,是正常逻辑肯定走不进来的,甚至可能因为异常而导致记录了错误的状态,则一定要打印日志。比如某个玩法一定要组队才能游玩,但是玩家在没组队的情况下请求游玩,说明玩家在之前已有错误,因此需要打印日志以记录情况。 玩家多次反馈出问题的功能逻辑,在最关键处打info。比如某个玩法结束后发奖失败,则需要在发奖的各个阶段打印日志,用以分析哪一个步骤出现问题。 玩家核心玩法行为日志记录,这个可以打info,也可以使用额外的行为日志记录(因为玩家行为可能很多,全部打到log文件会太大,所以可以将其通过消息队列发送到另一个数据仓库中进行汇总分析)。比如MMO游戏的玩家地图跳转,SLG游戏的战斗记录等。 不要打印日志的情况有: 接口的业务逻辑中,已有明确错误提示后返回的情况。如在商店道具购买时道具不足,客户端已能收到有意义的文字错误信息,则再在服务器打印错误日志是意义不大的。 尽量不要在太公用的业务逻辑里打印日志,即使要打印也要加一些条件。这是因为公用逻辑会被各个不同的业务调用,并且一般是主要的玩法。打印日志不加条件限制的话,会造成日志过多而不方便排查。 什么时候用info,warning,error? 这个问题也是不同的项目有不同的答案。 我的理解是:为了观察玩家和服务器状态的用info,不影响玩家继续游玩,但是确实可能有异常的用warning,要是执行到就一定是有错误的用error。 版本更新前的内部服务器日志一定要关注 当版本开发完毕,到测试阶段时,一定不要觉得测试人员没有反馈就完事大吉了。一定要关注内部服务器的日志,尤其是新打印的日志。内网能看到的日志,外网一定能看到;而外网能看到的日志,内网也一定能复现。 最后,不用担心日志打太多的问题。查不到问题的原因才是更坏的情况。 服务器状态监控 版本更新后,新玩法开启的几天,一定要有意识观察线上服务器的CPU和内存使用状态。就算没有搭grafana那样的工具,你也可以连到线上服务器通过Linux命令查看是否异常。如果CPU过高或者内存不够,则需要尽快把问题反馈出来,从而尽快分析并且处理问题。 这绝对是一个好习惯,原因是有一些问题可能玩家遇到了没反馈出来,他们可能觉得无伤大雅,但是那些没反馈的问题,对服务器的状态也许存在一些风险。 线上问题定位 前文所述内容都是为了避免和修复线上问题。而如何定位线上问题,才是服务器开发中最困难的一环。 这件事的困难之处在于, 无法复现玩家所遇到的情景; 玩家反馈的信息是玩家理解的内容,不一定是服务器上的问题所在 情况是耦合发生的,触发条件苛刻 而当我们花了大量时间排查,最后可能发现只要改不超过10行代码就能解决问题。这或许有一点幽默,但是却是经常发生的情况。 但当我在此回看过去,我发现线上问题一定是能查到结果的,只不过是时间成本问题罢了。真正阻碍我们查到线上问题症结的是我们的内心。 要相信问题是一定能被排查到的 计算机科学是一门年轻的学科。它远没有达到物理学中“电子双缝干涉”那样深奥而暂时无解的情况(当然你一定要拿“比特跳变”这种极端罕见而异常的情况抬杠的话,我就认输)。而游戏服务器的环境配置,都是由你我这样的凡人搭建的。游戏服务器的业务逻辑更是由团队里的大家编写的。所以一切的问题是一定能定位得到的。 而在排查过程中,要相信一点: “所有的问题都会解决的” 当你在排查线上问题的时候,不要惧怕那些线上问题,也不要惧怕那些给你施压的人。程序员常有的完美主义让我们认为一切的代码问题都是可控的,因为业务逻辑是我们一行一行写出来的。但是这不代表代码就一定没有问题。恰恰相反,出问题才是常态。出问题不代表我们不好不优秀,而是“我们还有未知的内容需要去学习”。不要惧怕那些给你施压的人,因为与问题正面对抗的人,就是屏幕前的你。你本应该是伟大而强大的。专注于你要排查的问题,像侦探一样耐心地分析各种蛛丝马迹,所有的问题都会解决的。 分析玩家所述内容,排查异常情况 当玩家反馈错误的情况时,根据玩家的描述定位到具体的玩法,这时不要直接钻到玩法的业务逻辑代码之中,那就如大海捞针了。我们应该根据玩家提供的玩法信息,游玩时间,反过来去查找日志,观察其在出问题的前后执行了什么操作、消耗和获得了什么道具、游玩了其他的什么玩法,在此期间是否有可疑的错误日志等等。 这一步同时你需要去理解玩法的具体内容,比如时间,状态等。通过日志和玩法内容,去勾勒玩家当时的情况。 模拟玩家所遇到的环境 有条件的话,尽量能把出问题的玩家的数据导入到自己的服务器上。这样我们就可以在本地进行各种尝试。如果能把玩家出问题前后的玩家数据拿到本地则更佳。即使没有玩家数据也没关系,在内部环境中模拟玩家所遇到的情况,通过玩家提供的视频,和服务器上的日志,可以得到玩家的状态,进行游玩,也一定会有新的理解。如果实在是无法在本地复现,你也可以用脚本到服务器上执行一些代码,打印玩家现在的状态(现在的状态也是有意义的,玩家有自己的方式再次复现)。 仔细甄别“问题情况”和“猜测的原因” 当线上问题发生时,如果是大问题或者难排查的问题,会有很多人的噪音在耳边叽叽喳喳。他们提供的思路不一定是错的,但是可能和当前最重要的问题没有关系。一定要分析出问题的情况在哪里,专注于要修的问题的情况。会有一些对游戏很了解,但没有看代码的人提出很多猜测,要理性地分辨这些猜测,小心仔细地验证各方论点。 如果真的查不出,就打印排查日志 分析玩家行为、导玩家数据到本地、模拟出问题的环境、并且各方讨论也没有结果,那么请打印好排查日志。 排查日志一般是为了以下目的: 检查关键代码逻辑是否被访问到; 当执行代码逻辑到某一处时,检查玩家的状态是否正确。 因此这样的排查日志,需要打印的细节一定要越丰富越好。如果打印的内容很多,最好能用StringBuilder将多行内容拼成一个String,然后直接用一行log打印出来,以避免日志收集器收录到只言片语。 你甚至可以针对某一个玩家游玩时,仅针对他打印各种细节日志,甚至可以包括程序调用的堆栈信息。

七月 13, 2025 · JonathanLin

游戏服务器开发经验(二)避免内存泄露

技术文章 游戏服务器开发经验

内存泄漏(Memory Leak)通常指的是计算机程序中一种资源管理失当的问题。简单来说,就是程序申请了内存空间,但在不再使用它的时候,没有将其释放,导致这部分内存一直被占用,且无法被程序再次访问或操作系统回收。 为什么会内存泄漏? 在Java中,当一个对象A拥有另一个对象B的引用时,如果B只有来自A的这一个引用,并且A一直没有释放,则B也不会被释放。因为JVM并不知晓代码的业务逻辑,他只知道B还被其他对象引用,并且A也正在使用之中。因此JVM会认为,B对象还不可以回收。 然而,A对象没有移除到B对象的这个引用,有可能是因为业务逻辑处理不当所致。例如,当A是一个单例的全局对象管理器,管理一系列对象,而B对象恰好在业务逻辑中应该被回收,而没有回收时,B会一直存在于A的引用中。此时B就无法进入正常的回收逻辑中了。 如何避免内存泄漏 在任何时刻,创建对象时,都要思考一下生命周期。 可以试着问问自己:目前的这个对象被创建后,它应该在什么时候被销毁?是这个函数结束时、副本玩法结束时、组队结束时、玩家下线时、还是一直到服务器停止后? 考虑不使用引用,而使用id等基础数据类型进行关联 这个对象创建之后,要离开创建它的函数,就一定要被其他对象管理。在管理器之外,还会被其他对象引用吗?如果是,那么要考虑其引用什么时候结束。或者,如果管理器能提供唯一的id进行管理,那么在其他对象上就存储一个唯一的id,在需要时再通过id向管理器查询出对象,在函数调用栈中使用,随着函数结束后栈帧弹出回收。 在外部补充业务逻辑,检查内存泄漏 如果业务足够复杂,系统陈旧,无法进行彻底排查泄露的位置,可以考虑每隔一段时间检查内存中是否有内存泄漏,然后强行将其清除。相当于是在业务层执行一次垃圾回收行为。 定义过期时间,限制使用条件 如果业务复杂,可以考虑在对象使用时加一个过期时间,超过时间还被访问,则执行清理。

六月 9, 2025 · JonathanLin