开篇 - 接受不足和变化
一转眼,毕业都快三年了。最近思考的时候,才很惊讶的意识到我很快就会是一个毕业三年的老同学了。 毕业第一年因为想申PhD所以去了深研院的MMLab做了一年客座。但很不幸的是,在拿到PhD offer后不久新冠就爆发了,无法拿到签证出国。 兜兜转转等了一年后,还是决定放弃PhD开始工作。
写blog是一件我想做了很久但是一直没有付诸行动的事,直到去年12月份,我才正式了开启了博客的筹备计划。 在云平台租服务器,注册域名+备案,找自己喜欢满足自己需求的博客框架,这一系列的事忙完后就到快过年了。
而直到第一篇blog,也就是这篇,已经2022年三月了,迎来一个迟到的2021总结。
工作方面
Unity客户端
我入职的职位是Unity客户端开发。
作为一个玩过非常多游戏的网瘾青年,开始对这份工作还是很好奇和期待的。 游戏开发的思维和互联网其他类型的产品有着非常大的区别,unity作为一个商业游戏引擎,也为小型团队和个人开发者提供了非常大的便利。
2021年这一年中有半年的时间我在从事Unity客户端开发工作,现在回想起来作为客户端的这半年最痛苦的事情莫过于上线测试阶段。游戏客户端的测试看起来就像是大海捞针,并不像我之前想象的那样依靠单元测试可以帮助避免大部分的“机械”测试工作。
unity客户端的第一个挑战
在客户端工作中,遇到的第一个比较“玄学”的问题是Addressable的卡死问题。 最开始是因为Android11的发布,用户反馈说游戏进程频繁闪退崩溃。起初有同事查到是unity版本的bug,于是就将unity的版本向上升级了。
升级之后,测试验证进程崩溃问题修复了,但是也测出了另外一个问题: 游戏在加载资源时极易出现卡死,表现就是画面卡住不动。
这个问题当时令我们非常头疼,因为逻辑上并没有任何错误,这是组件底层的bug。 由于无法接触到引擎和组件的源码,我也只去推测原因:
- 出现问题时游戏进程完全卡住停止,让我联想到死锁,会不会可能是资源加载时产生了死锁?
- 加载单位多时出现概率大,加载单位少时出现的概率小。
项目里加载资源的逻辑所有资源的加载都是独立并行的,比如我需要同时加载100个资源,那么就会有100个 Addressable.LoadAsset请求被在一帧内被调用出去。
所以我做了一个试验,把资源加载的方式改为先压入一个队列中,然后在每一帧的update中只加载指定数量的单位。换句话说,就是限制同时加载的数量。 测试发现每帧加载的单位数控制到比较小的数时,卡死出现的概率会比较小,而且并没有在观感上和之前有非常明显的差别 – 这就是之后上线的处理方案。
但是,其实后来发现换一个Addressable的版本就直接解决了,本质上就是Unity和Addressable版本不兼容导致的。
unity的AssetBundle和热更新
AssetBundle系统实现
在暂时结掉Addressable的问题后,我们意识到使用这种不开源无法调试的组件风险是很大的。 一是出问题我们无从下手调试,二来是我们无法去定制化一些贴合产品的功能。 所以我们决定基于Unity直接提供的AssetBundle接口自己实现一套资源加载、更新系统。
而实现一套在线AssetBundle中主要聚焦解决的点:
- 资源的分包配置管理。允许用户自定义对每个AB包进行
一级资源
(即直接使用的资源)配置。 - 打包时对
依赖资源
的追踪和分包优化
。用户只是制定了一级资源(prefab等)的分包配置,但是打包是更重要的事是依赖资源的分包和优化。 - 资源
加载时依赖资源的追踪
,需要先将依赖资源的ab包定位到并先加载到内存中。 - 实现AB包的下载功能。同时在下载过程中同样需要考虑AB包之间的依赖关系。
逻辑热更新
想要做到不更新包完成版本更新,资源的在线更新只是第一步,逻辑的热更新也是整个流程非常重要的一环。 在调研期间,我们测试了以下三个插件:
- XLua
- HotFix
- ILRuntime
其中XLua通过在Unity中植入Lua解释器,将lua代码作为Unity是文本资源,而XLua和ILRuntime则是通过dll注入实现。
而在做了这些事情后,我有另一个想法,一直依赖项目组对产品的QA测试都是需要打Android包进行的,这很大程度上导致了研发、验收节奏的拖沓。 而利用AB包的下载功能和逻辑热更,其实在测试期可以快速的进行修复的验证。不过由于需要团队大部分人转去写Lua所以这个想法暂时无法推行。
转到服务器
五月之后,由于服务器组缺人,我被调到了服务器组,回到了我一开始所期望的工作方向上。
PHP
之前做深度学习的时候使用的比较多的语言是python,而项目的服务是一个php项目,没错,就是世界上最好的那个语言[Doge]。 其实语言使用上倒是没什么障碍,高级编程语言的语法都大同小异,脚本语言应该都可以快速上手去写代码。 当时对我来说比较陌生的,是nginx+php-fpm组合,以及他们中间交互使用的fastcgi协议。
CGI协议(GatewayInterface)
早期的Web协议(比如Http)其实就是单纯的将服务器上的网页文件传输过去,所有用户看到的网页是一模一样没有任何差别的。 而后时代的Web变得更加丰富,与用户的交互更加频繁了,需要根据用户的一些信息展示个性化定制的内容。 由此CGI协议应运而生:
- 让用户的Web请求能触发一个外部的服务器进程,可以针对用户请求动态返回给客户端各种各样动态变化的信息。
- FastCGI在CGI的基础上做了优化,最大的变化是CGI协议在处理每一个请求时都会fork出一个新进程,而FastCGI协议维护了进程池。
Nginx反向代理服务器
Nginx在我看来是一个非常有意思的东西,原则上所有的http服务前面都建议用Nginx做一层代理。 这样做的好处有:
- 灵活的水平拓展,如果流量压力增大可以添加逻辑服务器的节点提高吞吐性能。
- 安全的服务热更新,利用
nginx -s reload
可以很安全的对服务进行替换。
php-fpm(php-FastCGI Process Manager)
php-fpm是fastcgi协议的一种php实现,已经集成到php官方发布版中。
- php-fpm分为master和worker进程。
- master进程负责与Web服务器进行通信,将HTTP请求转发给worker进程进行处理,并将结果返回给Web服务器。
- php-fpm通过参数
pm.max_requests = 10240
设置了每个worker进程处理请求数的生命周期,当worker进程处理了指定数量的请求后会被kill掉重新创建。
Golang
作为游戏服务器,php有一个很大的问题就是没有守护进程,这在很多方面对业务的发展造成了局限:
- 由于没有守护进程,用户的状态只能依赖redis记录,但是好的游戏体验往往伴随的更频繁的Client-Server数据交互,而redis的吞吐量则限制了这个交互。
- PHP目前市场上很难找到合适的人才,这对我们组人员扩充也是一个不小的阻碍。
- 服务器无法主动的向客户端进行
Push
。
所以我们决定用Golang重新设计一套服务框架,着重实现消息推送的功能。
WebSocket服务
游戏自然需要维护Client-Server的TCP长连接,其实实现这个的选择并不多。WebSocket几乎是我们唯一的选择,否则的话就只能自己从TCP上设计一套协议,这显然不合理。 WebSocket协议复用了Http的握手信道,即通过HTTP协议进行握手,当连接建立完成将协议升级后,双方后续就通过WebSocket协议进行交互。
gRPC无状态服务
在设计Go服务的时候,我们觉得Php服务起码有一个好处,就是可以做到服务’无缝’热更。但是在TCP有状态长连接的服务上,显然要做到这个就比较困难了。 (跑个题,之前在一次技术分享上,了解到可以通过某些手段去改变一个TCP连接底层fd的所属进程,这样就可以做到连接的转让。这个点我还没来得及去深挖,觉得很有意思)
所以我们将一些可以做到无状态的周边逻辑拆成了单独的服务,通过gRPC与网关服务器交互,这样可以降低我们需要重启网关的概率。
DB
Redis
这一年中接触到最多的数据库就属Redis了。 作为当今如日中天的内存数据库,Redis有他很多的优点:
- 部署简单,使用简单。
- 访问速度极快。
也记得Redis给我带来的困扰:
- 扩充节点麻烦,目前我们采用扩充节点的办法是搭建新的集群,然后利用订阅同步将旧集群中的数据迁移到新集群中。当新旧集群的数据保持同步旧可以切换服务的DB地址了。
个人成长方面
心态
19-20这一年多的时间发生了很多事,我心态也伴随着起起伏伏。刚毕业选择gap一年申PhD的时候,内心是自信且对未来充满希望。 特别是在深研院实验室认识了很多志同道合的朋友,有和我一样是选择gap做paper的,有工作了几年想继续深造的。刚毕业那半年,也算是热情满满的做了半年的research。 变化也就是在新冠爆发后,刚和老师谈好的意向名额,因为疫情变得不确定了。
其实现在回想那段时间,实验室里很多朋友后来也都顺利的PhD入学了,也有的和我一样作了别的选择。 虽然说当时我还有其他的一些压力和因素,但是我还是非常佩服和祝福那些坚持下来并顺利入学的朋友。很多时候,成功就在于你比别人多坚持了那么一会。
之后这一年,我需要让自己的心态更沉稳下来,更加坚持和专注的去实现既定的目标。
Docker
其实在实验室做RA的期间,使用Linux服务器跑深度学习任务时就意识到Linux系统有多重要。 docker其实就是依赖linux的namespace,cgroup实现的一个特殊进程。 今年的目标之一是深入理解docker的实现,借此也能了解更多linux内核功能。
Object: 熟练掌握docker
kr1: 阅读并实现《自己动手写docker》
kr2: 全年分享docker实现及原理系列文章
Kubernetes(k8s)
这一年业余时间自己鼓捣最多的还是Kubernetes,作为当今容器编排的事实标准,kubernetes有着无与伦比的吸引力。 我的个人博客其实就是在我自己的k8s集群上搭建的,而深入了解kubernetes也被我作为2022年很重要的一个成长目标。 用OKR规范这个这个挑战:
Object: 深入理解和使用kubernetes
KR1: 手动部署kubernetes各个组件搭建集群
KR2: 深入理解kubernetes架构,并分享1~2篇技术博客
KR3: 可以调试kubernetes代码并梳理清代码的组织和结构,创建系列博客
2022展望 - 不卑不亢的继续前进
2021年对我来说是不平凡的一年,2020年底我选择放弃PhD选择工作时更多的是一种无奈,但是现在而言我挺喜欢这个现状。 不论是在学校还是工作,最重要的还是自己要不断的学习和成长。业界有句话说: “每隔一年你回来审视一年前的自己,你都要为一年前自己的无知和差劲感叹,否则你这一年就什么都没做。” 虽然这句话多少有点戏谑,但是工作生涯前期的成长节奏确实挺重要的,以之自勉吧。