今年的工作主要是R项目(名称不能透露,下同)的开发维护。项目的数据有不少亮点,虽然距离总体目标还是有不少差距。但作为海发项目,百万DAU项目,这些都是非常宝贵的经历,简单介绍和总结下。
我们这个项目,是从Z项目(下面简称国服)打出一个分支,然后在该基础版本下,做国际化、本地化及一系列功能迭代的海发项目。从程序的角度,跟别的项目最大的不同点,是合并代码这块。海发的工作方式,需要承担更多的冲突成本。每周我们什么事情都不做,也需要去merge代码,去解决冲突。有些表要重新填,有些merge产生的报错要手工解决。merge完,还要走翻译流程。代码难维护,可读性差,但我们不能在自己的分支重构;而万一国服做了代码重构,或者不兼容的修改,我们这边就崩了,得检查所有相关的代码,手工改过来。而且,还时不时搞反向merge,国服merge了我们的功能,又在该代码上,做了大量修改。使得我们每周merge这些模块都有大量冲突,需要逐一解决。
这些问题,既是最大的不同点,也是最大的痛点。我认为,国服、海发独立分支的开发方式,只适合低频、非全量merge的项目。针对我们现在这种高频merge(每周一次),而且需要全量merge的项目,不太适合。内耗太大,团队成员做这样的工作也不愉快。更合理的开发方式,是大家都在同一个trunk上做开发。遇到不同的逻辑,通过一些变量(可以是开关、配置等),然后if...else...来区分。这样的代码,虽然看起来ugly一点,但起码是清晰的,也比较好维护。更重要的是,不需要面对无穷无尽的代码冲突。实际上我们现在很多功能都是直接在国服的分支做,然后随每周的merge流程合并到我们的分支,因为这些系统太大了,冲突不好解决。
对于表格的处理也是类似的,简单的通过表格ID区分,复杂的通过代码去区分。最终无论需求再复杂,到程序这块,都能通过代码解决。
至于发布流程,也很简单。每周定一个版本稳定的日期,功能已经做完测完,这时可以打出一个外放分支,在这分支上做回归。后面遇到任何bug,包括回归时甚至外放后,都直接在外放分支上修复,并merge回trunk。以前M项目做港台版,其实也是用这样的开发模式,我们也没有专门分出独立的团队去做,这样反而更高效。
后面假如还是做海发产品,开发维护节奏跟这个项目相同,我倾向于用同一个分支做开发。这样无论从效率,质量,还是工作的愉快程度,我认为都会得到很大的提升。
讲讲开发方面的工作。跟往年一样,做了很多系统,还有功能迭代。跟往年不一样的是,这些系统很多采用了微服务的形式去实现,和以前做web的东西很像,所以很多经验和思路,可以借鉴。
微服务形式的开发,从我们项目的情况来说,是必要的。哪怕不用这套go写的微服务,也需要一些独立的http服务(如openresty),去承担一部分计算存储压力。我们服务器是用bw引擎做的世界同服架构,首先,bw本身很脆弱,只要某个逻辑写得不好,比如进程被一个for循环卡住,集群就认为该进程失去响应,从而整个集群宕机。然后,数据库mysql是个彻底的单点,受限于游戏引擎读写数据的机制(玩家数据整取整存,不支持分表),读写压力集中在master这一台机器,也无法利用slave减轻读压力。线上出现了很多次登陆问题,甚至服务器宕机,就是数据库这块撑不住造成的。
微服务可以将一部分逻辑和数据,迁移到独立机器,减轻核心游戏服的压力。一些全服数据的读写运算逻辑,移到外部服务,那么游戏服卡机宕机的风险则会减少很多。而这部分逻辑相关的数据移到外部数据库(mongodb或者redis等),则有效减少核心数据库mysql的压力。
微服务的引入,也让我对玩家数据存储这块,产生了一些优化的想法。一个成功运营的游戏,必然有大量具有时效性的数据,比如活动产生的数据。这些数据有一个特点,只在活动期间有用,活动过了就没用了。而活动又是一个接一个,这些数据存到玩家身上,日积月累,甚至比玩家的核心数据体积还大。大部分游戏引擎,都是把玩家数据加载到游戏进程,定期存盘,这里包含了数据库读写及序列化反序列化的消耗,也是瓶颈之一。这部分过期的数据,实际上占这块消耗的比重是非常大的,这也是大家开发的时候很容易忽视的问题。把时效性数据移到微服务,实际上也是一种lazy evaluation的思路。玩家登陆,只加载核心、必要的数据。一些外围数据,时效性数据,可以等玩家打开对应功能或活动界面,才去读取。而过期的数据就不再去读取,这可以使得玩家数据读写这块获得更经济更高效的提升。
当然,有利则有弊,微服务使得开发维护成本增加。游戏服是python,微服务是go,语言需要切换;本来是一些简单的内存对象操作和同步逻辑,变成复杂的RPC和异步IO逻辑,以及数据库操作。需要考虑更多其他方面的东西,比如数据库索引,查询语句是否高效;代码及线上维护,需要维护一套游戏服和一套微服务的代码及环境,心智负担重,查问题非常耗时。
总体来说,微服务给我带来的最大的收获是go语言。对于游戏逻辑的扩展,周边系统,中间件,go是个非常适合的工具。换作以前,如果做微服务,我肯定直接上openresty。不是因为go不好,而是openresty已经满足业务和性能要求,我也比较熟悉。技术选型很多时候选的都是自己熟悉的工具,正如MH多年积累的lpc+mudos。又比如之前听ST主程的分享,他用了自己熟悉的mysql,而不用当前更适合游戏业务,但自己不懂的mongodb。技术、工具最终是用来解决问题,好坏优劣更多时候需要结合项目,结合团队而言。但是,多一种实践经验,后面就多一种选择,解决问题也会多一些思路。不会因为手里拿着锤子,看到所有的东西都是钉子。很多时候,我们解决问题的方法不够聪明,不够优雅,往往由于我们掌握的东西太少。所以,工作中有机会学新的东西,掌握新的技能,一定要好好把握,这也是提升个人能力的最好途径。
再说说维护方面的工作。包括内网开发机维护,周维护,维护流程梳理优化,工具优化。这些工作的产出,提升了团队开发维护的效率,减少大家心智负担,减少维护出错的情况。
一开始在杭州的时候,服务端维护这块,脚本和流程,都不完善。跑国服的打分支和上传脚本,非常耗时,而且经常出错。回来之后,我对这块脚本,做了大量优化,找到每个运行耗时的点,进行改进;同时,把整个打回归分支,上传代码的步骤,做了大量简化,并整理成wiki,让每个服务端程序都能最高效,最简单,最省时间的完成每周维护工作。
还有就是线上问题的处理工作。由于这个项目是高DAU项目,而且架构复杂,部署的环境也复杂,所以中间出过不少问题和事故。这一年感觉有一半的时间,都在处理线上问题。像是半夜处理事故,经常要看coredump,查服务器负载问题,查各种杂七杂八的环境问题,有时确实感觉非常累。但反过来说,也锻炼了自己线上查错的能力,以及紧急情况下的应对和决策能力。这也是这个项目带给我的最大收获之一。通过这个处理问题的过程,我也看到自己,还有很多不足。很多问题,没有解决,没有搞明白,尤其是线上出的问题。这是我需要继续锻炼的地方。前几年的工作,我一直在寻找高效的开发方法,写高质量的代码,规避出问题的可能。这个想法没错,但过于理想化。很多东西,没拿到线上跑,会出什么问题根本无法预料。能处理好这些问题,也是检验自己能力的途径之一。未来在更多产品的运营开发上,我也会继续提升自己这方面的能力,同时将自己的经验分享给其他人。
一个成功运营的项目,总是能带来宝贵的经验和解决问题的思路,这种机会可遇不可求。正如第一个M项目,lpc和mudos那些我可能已经忘得差不多了,但里面的模块设计思路,运营维护经验,却一直影响着我。到现在,我把这些都带到了这个项目里。甚至在日常工作中,我也把这些经验带给其他程序,互相借鉴,互相进步。同样的,R项目也会有类似的东西,能让我带到日后的工作中。我觉得这正是一个成功项目最宝贵,最有价值的地方。
每一份工作,每一个任务,确实存在难度的差异,挑战性的差异。但工作的收获,很大程度上取决于工作态度,取决于有没有用心发现它的价值。简单的工作,如果专注的去做,不断思考更优,更高效的解决方案,收获一样很大。反之,很有挑战性的工作,如果只是敷衍对待,那可能最终收获并不会很大。做前一个项目的时候是这样,项目虽然失败,但从零做起,锻炼了个人能力,为后面项目蓄力,这就是价值。做海发项目也是这样,虽然经常做的是跟进、merge、替别人查问题、帮忙修bug等“辅助性”的工作,但每一行代码都被几百万DAU的玩家运行着,每一点细微的改进都被成倍的放大着,这就是价值。没有人的能力会被埋没,没有人的努力会被白费。做过的东西,总会在某个时间,间接或直接兑现出价值。
2019年2月