把手绑在背后没有说明书拼高达

解决问题的第一步,也是最困难的一步就是发现问题。

所以这一篇,就是一个发现问题,然后确认,再尝试去解决的一个记录。

有一句话,叫做“我懂得很多道理,却依然过不好这一生。”

最近讲了太多关于“讲道理”这个事情,几乎快要替代“做正确的事情”在我心中的标杆地位了。但是最近的一些事情和思考,另外重新开始考虑,然后很显然的发现,“讲道理”并不一定是“正确的事”,所以“讲道理”其实只是一个解决问题的方法,并不能成为一个信条和根本驱动,在这个事情上,“讲道理”这件事情,太具体了。

想一想为什么会发生这样的事情呢,也许和这段时间的管理培训相关吧,因为在思考这样的问题,急于想找出一种方式向他人表达自己的观念,并且自认为找到了(“所谓讲道理的事情”),而且还觉得很有效,于是不由自主,不知道什么时候开始,自己不讲到底得开始强调“讲道理”了,在向别人一遍遍重复的时候,也对自己一遍遍重复。然后发生的事情,戈培尔已经告诉大家了。

现实世界中,发现了“万物理论”这种好事情是不会发生的,如果感觉发生了,可能就是错误的开始。

当然,一个你自己都解释不清楚的“万物理论”倒是没问题,比如我的“做正确的事情”。一个混沌的理论也就无所谓“遵守”。

说回具体的事情。

一直以来的行事方式都很简单粗暴:排除限制,解决问题。大部分的事情的限制其实都来自于自己,所以只要你“不要脸”,很多问题的解决其实都很容易,实际上也不需要什么特别的技能。所谓光脚不怕穿鞋的。

解除限制的过程,也是个拼命思考的过程。现实中的限制,往往都不那么显而易见,打破常规是必须的。这样的拼命思考,又带来一个问题:想太多。这个副作用其实不大,用“小心谨慎,深思熟虑”的说法就可以很轻易的说服自己。“至少想多比没考虑到要好嘛”。而问题的复杂和混沌,实际上也必须要依靠更加复杂和多层的思考,才能所谓“抓住本质”。“抓住本质”,变换问题,寻找真正需要解决的问题,可以说是最简单有效的方法了。

毕竟,只要问题在不断解决,其他的都不是事。甚至战略上的懒惰都能轻易的被战术上的成功掩盖。

并且,按照这个简单的逻辑,实际上也能发挥极大的杠杆率,让你成为环境中最能解决问题的那一个。习惯于自己解决问题。也确实的难以依赖他人。

追求正确性+讲道理带来了一个问题,就是难以让别人接受观点与帮助别人成长。换句话说,不好带人。这是一个我长期发现,但是难以接受以至于有点逃避的问题,事实上说,我其实不擅长带人,或者说是确实没有成功帮助别人成长。之前我也给自己提出了很多理论解释这个问题。从早期的寻找各种方法,到后期的觉得“就是不行”。但是实际上现在看来,原因很简单,“道理”这个东西,就是屁股决定大脑的,每个层级上需要解决的问题不一样,所以讲的道理也实际上是不一样甚至是截然不同的。而我的追求正确性,一个是让我经常没法花时间去讲道理(需要尽快解决问题),二是,讲出来的道理都过于“正确”,可能对特定对象来说就反而不是最佳的了。

这些问题,在早期其实并不会有太大的影响,而且对实际的发展很有帮助。甚至我依然觉得能有今天,之前的方法和方向还是对的。

(此时此刻,想太多的问题依然影响着我,每想到要写一段,就会想写10个背景知识说明为什么会产生这样的情况,以及为什么在当时是正确的,然后再说明10种情况下的变体,然后思绪就飘忽到天国去了)

但是到了现在,我必须要承认,在实际事情上的能力,我已经不如团队了,最基本的事情,原来可能我写的代码是最不会出bug的,最近发现,写的所有的地方,过段时间都能看到被修复的commit。而从团队的立场上,是不能把我考虑进项目流程中的。我对于团队就像是个网上认识的对项目很感兴趣,想要义务帮忙的热心网友。对于这种情况,我的观点一直很明确——再强都不能要,或者必须要有非常明确的隔离,并且不能有任何预期。事实上,团队也是这样对我的(当然正确,当然,也令人沮丧)。这样,对我来说最困难的情况就发生了——

我发现我不能靠参与项目来获取信息,让自己进步,但是我现在所在的职位要求我应该有巨量的信息并且做出决策。突然就死循环了。

意识到这个问题的时候,我的第一反应是这不讲道理。我能力不足我承认,但是我一直都很努力的从项目中学习,希望经历跟进项目的过程,能够成长。但是根据上述逻辑,其实我被禁止了这样做,这相当于要求我成为一个架构师,却不许自己写一行代码?甚至看别人写都得小心谨慎,因为很容易围观的时候一句话,造成未知的影响。

最绝望的问题在于,实际上自己是没有得选的,最终必须要解决问题。

真正意识到这个问题的沮丧感还是挺打击人的,首先从道理推断这些都是对的,那么我之前的行为就是个巨大的用“战术上的勤奋掩盖战略上的懒惰”。而且我过去一直以为自己只是用这种方式来度过一下低谷期,现在想来,其实我根本就没有在自己的职位上做正确的事情,我只是纠正了一些显而易见的错误,然后做了一些显而易见正确的决策,然后就安心的继续把自己当成一个优秀的程序员了。

想清楚道理,认错并不困难,困难的是然后怎么解决。虽然事情依然困难,但是已经知道了问题,至少就有很多方向可以去尝试了,首先我第一反应就是觉得可以去问人,第二天,我去找了Kiro。

Kiro那天很忙,下午开了很长的会后还跟我聊了很久,真的是很感谢,结论上说,我感觉很有帮助。

Kiro的观点是,我的根本目的是为了让事情做成,那么项目只是公司发展的一个部分,并且帮助项目做成的方式不一定是要靠直接影响项目,甚至直接写代码。眼界开阔一些,必须要承认有些事情就是没法控制的,也不可能控制所有事情。就算之前的经历让我有“事情最终都还是要靠自己解决的”,还是依然要再试一次。

我之前的观点在于,我直接影响项目,是最直接有效来确保达成目标的方法,但是事实上却是不一定是最好的方法,影响总会分积极和消极。这段时间的很多纠结都在于,我想确认到底这个影响是积极还是消极,以及没法说服自己,真的不这样做。有可能我回家躺着是对项目最好的方式,但是我又如何才能让自己确认真的是这样呢?

所以到底我还是没有确认这一点,但是Kiro的话让我发现了其实却是我还是有很多可以做的事情,可能过去是我不喜欢的,但是跟现在这种纠结的心情相比,那我就宁愿去做这些事情了。既然我事实上对现在的团队有着确实的信任,那何必想太多,一定要把道理讲到“100%”呢。

虽然我懂得很多道理,但是道理之上还有meta-道理,一件事情在这个角度符合自己的道理,在另一个角度可能就违反了。其实道理对自己来说,更像是一个让自己心安的借口。所以“讲道理”这件事情,还是应该让它去适合自己的地方——用于统一集体的文化价值——上去发展吧,束缚自己,就成了真的“不讲道理”了。

有着细腻的感情描写的真实系动画

这个方向的起因应该还是《春物》吧,也不记得是具体什么时候,我给自己喜欢的番总结了一个特征,叫做

“有细腻的感情描写”

好像些微记得是为了跟诸谦解释?

最近在找此类番的时候,看到一个说法,我一看就知道对了。

“真实系”

没有什么大起大落大喜大悲,就是一天天发生的事情,好像自己能多经历了几种人生一样。这样最好了。

所以想写这么一篇,List 一下我目前看过的这个风格的片子。如果发现了新的,也可以随时补充。

以下还是分几个组吧。主要还是因为有些动画也不能完全说是日常,真实,但是我感觉也很细腻,所以私心想放进去。

顺序以自己的追番时间先后为准。

绝对符合

我的青春恋爱物语果然有问题

多田君不恋爱

川柳少女

月色真美

Just Because

玉子市场(玉子爱情故事)

这几部,就是青春校园,一天又一天,以至于,你要说让我说说这几部讲了个什么故事,就是难为我胖虎了。

掺杂了点奇怪的东西

冰菓(推理)

人渣的本愿(额……其实讲道理确实是青春校园,我只是没脸放在上一组wwwww)

比宇宙更远的地方(倒不是多了什么……而是这番根本没有恋爱啊!但是小南极赛高!)

少女编号(同上)

青春猪头少年不会梦到兔女郎学姐(青春期症候群……这算哪种?)

齐木楠雄的灾难(这就是非日常的日常,相对《日常》其实说的是非日常)

女高中生的虚度日常(校园不恋爱)

喜欢本大爷的就你一个(看看前几集就好)

 

 

钉钉的提示音太TM吵了

做梦也没想到,我也有在公司推行钉钉的一天……

主要的点还是,单纯的软件设计上看,很多点还是做得比企业微信好的……企业微信的槽我自己用了两天都已经吐不完了。

另外,能够有效区分工作和生活,这点上还是很能减轻心智负担的。之前每次想找一个人,都得考虑我们一般是用微信还是QQ,想查找一段记录的时候,也得考虑一下是在哪儿聊的。

最后只有一个问题了

钉钉的提示音实在是太!吵!了!

这个吵,一方面是波形本身的增益估计设得比较高,另一方面是估计是有意设置的令人不适,这样能有效注意。

今天实在是忍不了了,直接开AU给改了,降低了10db的响度,并且把起音删除了。

试了一下,感觉好多了。

音频文件在钉钉的安装目录的这个位置:

DingDing\main\current\uiresources\new\common\sound

message

欢迎下载替换: )

2020

在公开的地方写作,就算没什么人(可能根本就没人)看,和自己想表达一些东西,还是不太一样,有些话,自己想想可以,写出来,就不知道会发生什么了。

总之,在半年多之后,总归还是有了写新的一篇的想法。

首先道歉(如果真的竟然有读者的话),装修的话题我可能是暂时不会写了……在新的办公室里也已经工作了一年多,总的来说还是满意的。

2020,看上去很像是个充满赛博朋克感的未来年份,却被踏踏实实地写在日期的栏位中。总归,时间还是会过去。

在给自己做了多年的思想建设后,突然地,有种人到中年的感觉。也许是因为《春物》最后一卷的发售,令人察觉青春真的结束了吧。本来很担心春物的结局会不会崩,但是看到的那一刻就知道,崩不了了。“果然还是他们呀”,发出这样的感叹,然后开心得看着他们的结局,没有那样的不舍,也没有过多的遗憾,真的很完美啊。一部恋爱喜剧,可能只有最后一本的最后三分之一才算谈了恋爱,甚至连手都没牵过,却让我觉得是最喜欢的作品。也许真的果然有问题吧,我说自己(笑)

这个时候写总结,非常有强行的感觉,明明事情才做了一半,却只是因为时间到了,“被迫”。好吧,还是说一说好了。

如果说18年的主要成果是让每个岗位都有人了,那么19年的主要成果就是让团队不再有我过于纠结的人了吧。现在来说,我是真的对现在的团队非常喜欢和敬佩,他们能做到太多我自己做不到的事情。能实现这件事情,回想一下,简直要哭出来。

这个月开始,应该陆续还会有几个能解决问题的人加入,希望在上半年,可以让基本的开发问题,不再令人纠结。

接近去年底的时候,通过对一位前辈的拜访,让我对项目要做的事情本身有了清晰的认知和计划,转化留存付费,谁都知道,但是真的就是这个。

真的去年底的时候(12月18~23日)去马来西亚首都吉隆坡逛了一圈。马来西亚是我们目前测试阶段的主要买量地区。之前真的是完全没有概念,这一趟下来,大体上还有有了基本的认知。比较没有想到的意外收获,来自于在宾馆看的YouTube。也是非常神奇,虽然说在国内要想看也是可以看到,但是真的不太理解到底看个啥。对于平台的量级也是比较想当然。由此受到的启发,我在考虑做一些不一样的事情,总之多做一些尝试吧。用事实说话,好过纠结和空想。

虽然说目前还是不太能安心做事,纠结的点也有点多。不过感觉2月份,当预期的援助力量全部到齐后,应该会有所缓解。现在的时间点,感觉真的不是个好的总结时间,但是姑且算是个记录吧。

期待下一次打开页面的心情。

关于装修

这一篇突然开启了一个神奇的话题。

当然,不是因为我突然中大奖买了房所以准备装修,只是突然被要求搬离从创业开始就一直待着的心动办公楼,于是就需要自己解决办公场地问题了。

相对于家装,公装接触过的人就少很多了, 但是对于大多数早期创业者来说,又是跨不过去的坎。向我们这种,这么多年都靠蹭别人的完善环境,突然一下需要自己来,对于需求和预算的把控就很容易出现大的偏差。所以突然就很想吧这个过程记录一下,也算给遇到同样问题的小伙伴一些参考。

因为这个问题实在头疼(如果不算项目本身的话,甚至已经可以算这几年来处理的最复杂的综合性问题了)。从4月以来服务器这边就基本没有耗费太多精力,主要就是加入了一下cluster_client的定期断线重连机制,这样也可以解决其他节点依赖cluster_manager节点先启动的问题。

项目管理

一开始要确定这件事情,我的第一反应还挺开心的,本来独立确实是有独立的好处,而且装修也勉强算是个小的建筑工程嘛,本来就是我一直希望尝试一下学习的事情,可以从工程界的鼻祖——建筑工程,推知一些软件工程方面的参考。项目目标明确,也不是很大,干系人也少,项目发起人又是自己,本来的确觉得是个轻松有趣的项目。(后来……

在Teambition上面开了一个项目,名称简单粗暴就叫做“新场地”,只设了一个任务组,分成房源、交房、方案、施工、搬迁五个列表,分布进行。因为这件事情是突然加出来的,打乱了本来的规划(年底项目上线之后再考虑搬迁),所以并不希望(也确实没有多余的精力)在这件事情上面付出过多。总体是按照尽量不改变现有的习惯和尽量让风险最低的原则进行规划的。时间方面,心动可以在现有场地无法使用之后为我们提供1~2个月的过渡场地,也就是说,从项目开始到可以入驻,总共大概是3个月的时间。那么就抓紧开始吧。

找房源

第一反应肯定还是想继续呆在现在的园区里,毕竟很习惯了嘛,换一个园区也不知道换到哪里,范围太广了。面积的话,之前我尝试联系一家共享办公的时候,大概做过一个规划,预期到明年的时候可能要达到40人,需要大约500平米的场地(包括一个会议室和财务室)。在稍早一些的时候,在樊少的帮助下,联系了园区业主看了几个场地,都没有非常满意。所以一开始我想看看其他的场地,找了一个做中介的朋友,开始帮我推了很多场地,一个个去看了。各有各的问题,还是没有看到非常满意的。回来的时候向园区业主又确认了一下,之前看的那几间还有没有了,结果发现最近又有了一间新空出来的,但是面积只有350平米,比预期小很多、虽然觉得可能不太行,但是之前看的场地让我有点觉得500平米是不是过于大了,赵宁也说她之前的公司,500平米的场地是可以排70多个工位的,于是死马当活马医,反正先去看看再说。

一进去第一印象还不错,装修比较新,感觉也挺大,一层感觉都是一些咨询位,还有一个淋浴间(我一直希望的,虽然知道使用频率机会为0),肉眼可见的面积已经不小了。走上二楼,有一个玻璃隔离的空间,里面有一个封闭阳台,以及一大块类似培训室的空间。玻璃房感觉可以作为会议室或者财务室,培训室面积不小,作为工位几乎已经可以安排下我们现在的所有人了。正看着,业主催促我再去三楼看看,这我才注意到玻璃空间里还有一个楼梯间,正是通往三楼的楼梯所在。本来两层的面积已经基本可以接受了,发现还有一层实在令人意外,我不禁向业主确认:这真的是350平米吗……

三楼的结构有些奇怪,两面都是满墙的窗户,可惜并不能打开。整个三楼被中间一堵墙分成了两件,墙的中间有一扇门,奇怪的是,门开在离地大约1米的位置,需要通过5级台阶上下。业主解释说,这个和之前带我看过的一间结构类似,整个三层实际上是阁楼,所以面积是不算在租赁价格中的,我们看到的结构实际上是一道梁,如果想到达另一边,必须要跨越这道近一米高的梁,所以才有了这样奇怪的结构。

回来以后我根据业主给的资料,找了些软件重新绘制了正确比例的图,简单的排了一下座位确定面积是否足够使用。虽然从图上看其实使用面积只有大概200平米(刷新了我对于面积的认知),但是面积确实是够的。总之,最终经过全面考虑,我们确定了想租下这一间,于是就开始准备谈价格、合同等等,同期开始准备起装修的事情。

确定装修承包商

说到装修我真的是完全没有一点概念,需要哪些需要花多少钱都没有任何参考,这种事情很明显在网上也是查不到的,就如同之前开发PS4的时候的烦恼,因为会接触这块的人实在太少了,公开渠道就很难指望有人分享一些经验。比较靠谱的方式是在身边找寻。我对于现在的环境当然还是一直比较满意的,所以第一反应当然就是找我们现在所在场地的装修承包商。通过介绍,联系到装修老板,老板也很快就带队上门来测量讨论方案了。与此同时,租赁的合同也基本确认了,看起来周末就可以开始动工了。

一切的顺利在我第一次收到报价方案的时候戛然而止,看着报价上面完全超乎预期的数字我真的差点没被吓死,单看每一项,价格都非常有槽点,虽然大部分我也不了解,但是总会觉得真的至于这么贵吗。跟老板交流,明确表示这个价格要远超我们之前沟通的时候确定的不超过每平米1000元的平均价,对方给出的解释是,因为我们的装修都是需要在夜间的(噪音施工),所以人工费要翻倍。我当然哪有这么多噪音施工,但是如果要我直接跟他怼,很明显我这边处于信息弱势。并且也过于消耗自己的精力。这时我才意识到,我现在做的事情,可能并不能通过一直以来习惯的基于信任的做事方式解决。这也是我在这件事情上面第一次感到厌烦。

我暂时中断了和这家装修老板具体的方案讨论,也放弃了从周末就开始开工的计划,开始乘着周末找其他的供应商。一家是现在住的地方楼下的公司,开了很多年了,看上去业务还挺繁忙。另一家是尝试联系朋友帮忙介绍了一家。楼下的这家明显是做家装为主的,朋友介绍的这家自称是只做公装。楼下这家这个服务挺不错,感觉设计师也很积极,不过不管说什么,都是回答没问题可以做,令人不免担忧靠谱度。相对来说,朋友介绍的这家,聊得挺开心的,在公装方面的专业度也很明显,主动提出了很多我没直接说出来的需求。对应的方案上对比也很明显,楼下这家的方案各种骚操作,竟然还企图给一楼制造段差,进行所谓功能分区,一听就很家装方案。办公室一定要把桌子斜着放,非常不明所以。之前他说的一些可以做的方案,也直接被在场的空调供应商打脸。朋友介绍的这家的方案就比较办公,但是可惜他对二楼的改动实在太大了,并且我觉得按照这样的方式,可能也不够好用。这两家共同存在的最致命的问题在于——他们都把设计图画错了。被我寄予厚望的朋友介绍的这家错得甚至非常离谱。相对而言,我们一开始找的贵得一塌糊涂的这家,至少图是画得最准的。在这种我个人觉得真是最最最基础的问题上面出错,实在是令人很难接受。不过,经过跟两边的沟通,我对于项目的规模和预算以及方案都有了更准确的认知。一般即使是完全从白胚的公装,也就在800~1000这个价位(不包括空调家具设备,但是包括弱电布线)。我也在Teambition上面整理出了一份基本需求。与此同时,我从公司内自己装修过房子的同事那边获得了不少信息。几天没有回复后,贵得一塌糊涂的这家老板可能也有点急了,又来在找我,意思说是,本来不清楚我们需求只是简单装修一下,所以做了很多的额外设计,并且答应回去重新出一份报价。

时间有限,如果再去找新的供应商,说实话这是一件没有底的事情,毕竟之前没有经历过的话,很难找到一个可以完全信任的合作方。既然怎么搞都无法按照完全信任的方式合作,那就只好被迫完全基于非信任合作了。虽然我实在非常不愿意这样,效率也非常低。但是在初次的外部合作中,也许这也是无法避免的吧。

我开始考虑找一个监理帮忙处理过程验收了,说白了就是有一个全程顾问可以帮我补充所有信息。碰巧了,一个同事的一个同学,之前也一直在向他咨询一些细节问题的,正好就是做监理的。那不废话了,直接约他见面聊聊。我带了报价去见他,他也带了他们这边的设计师过来,我们一项项对了报价,因为是专业做这方面,所以价格是很清楚的。我们把所有不合理的价格都扣除(保留正常的利润),得到了一个我们可以接受的最高价格,在此基础上面再降低一些作为谈判的余地。有了这次的沟通,我心里也就有底了。

回去的路上,刚出地铁站,装修老板就打电话过来,我也直截了当地跟他说:“我报个价格给你,你看能做伐?”经过一番确认,我也是考虑为了避免未来的麻烦和加价可能,稍微放宽了一些底价,最终达成了双方都满意的结果。从结果上看,真的大概是最开始的价格砍一半,放下电话,算是放下心来,这个比较大的问题终于确定好了,但是也不禁很无语,单单是确认装修供应商这个最初的问题,就需要花费这么多精力获取信息,这样基本断绝了我指望自己在不去过多了解装修的专业信息的情况下,解决这一问题的奢望了。果然还是,“自己解决不了的问题,也不可能靠他人解决”。

(目前装修还是在早期的进度,下一篇准备说说空调和弱电)

 

 

游戏服务器的集群问题

(这篇可能希望可以长期更新,记录一下这个问题的解决过程,最终可以产生一个方案)

遗留的两个问题

目前游戏服务器框架是使用的Skynet,之前的所有开发设计都是基于单机情况下的,在配合了Gitlab-CI之后,已经可以非常爽快的使用了。代码合并进master分支之后,会被pipeline自动构建并部署到阿里云的测试服务器上。在年底的时候,因为终于有了一位正式的服务器程序( 雷迪 )入职,在做完框架交接之后,基本就是他直接和产品那边配合开发逻辑了,我也把精力放在了其他方面。

在当时我这边暂时脱离服务器开发的时候,实际上还有两个点不放心,或者说在正式上线前必须解决。一个是数据库,一个是集群。

目前的数据库连接方案是我基于Redis和MySQL,写了一层薄封装,称为Unistore。这层封装本质是是给MySQL实现了一套Redis接口,这样一来,Redis就完全变成了一个缓存,数据实际上都是存在MySQL里面的。做这件事主要还是上一篇日志描述的事情给我带来很大的鸭梨,觉得数据这种东西还是小心点好。不过因为实现得比较粗暴,写入的时候是直接往Redis和MySQL里同时阻塞写入,这样一来,MySQL的写入是大概率撑不住的。这一块我跟雷迪聊了想法,在年前的阶段他尝试了一下改进,目前应该已经是在使用新的方案了,这个下周抽空去找他聊下,作为评审和确定。

至于集群,虽然我很赞同云风的建议,如果靠单机加核数和内存就能解决的问题,是没有必要上集群的。在年前我也对现有的单机部门做了一些压测,情况总体看来还是很不错的,CPU和内存基本压力没有那么大,也很明显只要往上加,性能是可以对应堆上去的。遇到稍微麻烦的问题在于并发连接数,我们的连接用的是TCP,所以用户数和并发连接数成正比,这就是著名的C10K问题了,虽然说,目前应该C10K问题依旧被研究得比较清楚了,但是对于我这样的运维萌新来说还是挺一头雾水的,在研究了半天的Linux kernel参数之后,应该勉强是在两台PC的轰击之下保持了10000+的连接(使用ss -s查看),但是离预期的目标来说还是差挺大的,并且总归是要为未来的拓展做好准备,这样集群方案就是绕不开的问题了。

Docker 与 Kubernetes

最近一段时间,林峰的平台那边因为基本的需求都完成得差不多了,所以开始研究了一下Docker。说来惭愧,我一直觉得Docker应该是很确定的方向,但是几次尝试,都看得云里雾里。Docker本身还是挺好理解的,但是一涉及到实际生产方案,资料就十分匮乏,最终唯一的用途就是在Mac mini上面起了一个Container取代原本原生部署的Unity cache server,这种无状态缓存用Docker还是很爽的。这次林峰的成果大大超出我的预期,在本地使用Minikube进行尝试后,在内网服务器部署了一个IBM cloud private集群,算是对kubernetes的使用有了一个大概的了解,我看差不多可以用了,就开始怂恿他开始往生产环境上面试,于是在阿里云上面尝试了一番容器服务。

阿里云的容器服务Kubernetes版总共有四种,Kubernetes、Kubernetes托管版,多可用区Kubernetes、Serverless Kubernetes。Kubernets和多可用区Kubernetes基本一样,最正常的方案,就是用至少3台ECS作为Master节点,然后加入Worker。托管版就是不需要自己买Master,只需要配置Worker。而Serverless Kubernetes就更厉害啦,连Worker都不用买,直接也被托管了。我们兴趣最大也最先尝试的就是Serverless Kubernetes,完全无视底层虚拟机,完全面向容器,多么令人开心的方案。结果,试了半天,helloworld都跑不通,直接报错无法拉取镜像。无奈只能工单咨询,结果答曰:Serverless Kubernetes的Worker没有公网访问权限,所以无法访问 Docker hub……这我可是头一回听说,我买台云服务器还有不能上网的吗?我又不需要公网访问……仔细研究了一番才明白,VPC还真不能上网,之前我买的几台可以上网是因为我分配了公网IP,如果只是内网机的话,不仅仅是外面访问不进来,里面也访问不出去。这下开心了,Serverless Kubernetes的Worker又不是我的,我也无权分配公网IP,要想上网只能买个NAT服务,价格嘛,12块钱一天……我说就为了拉个镜像至于么,于是开始找寻方法,找了半天还真给我找到了,原来只需要配置一下路由表,把0.0.0.0/0的下一跳指向一台可以联网的ECS,然后在这台ECS上开启ipv4 redirect转发即可,啊,第一次感受到了SNI的强大。配置好之后终于可以拉取镜像部署了。经过一番试用,最终还是放弃了,Serverless Kubernetes的运行本质上还是依赖了弹性容器实例 ECI,但是不知道为什么,阿里云设定每个pod至少分配2核4G内存,这对我们现在的测试实在是太不友好啦(穷),再考虑到方案强依赖云服务商的特定服务也是我一直很不喜欢的,于是出门右转开始尝试Kubernetes托管版。中途解决了各种奇奇怪怪的问题,最终效果非常令人满意,搭配上完整的弹性伸缩服务,现在整个集群都可以根据负载自动伸缩,负载高了,自动加入一台新的ECS作为节点,而ECS我也发现了抢占式实例这个性价比神器,只要不限制最高价格,也不用担心会被回收,平均只需要正常按量计费的1/6价格,目前是用了两台2核8G的ECS测试,每小时只需要2毛多。简直让我产生了在玩异星工厂的爽感。

在这个过程中,我也大概了解了Kubernetes集群和服务架构的一些逻辑,真的是非常方便,怪不得大家都在容器化,这才是真正的DevOps。既然容器这么好,那游戏服务器这边,怎么搞也得上一下吧?于是就产生了这篇日志要讨论的问题。

Web 项目与游戏服务器集群的差异

目前在Web项目中,使用微服务架构已经成为标准做法,简单来说,就是让所有的接口都成为无状态的,那么服务就可以近乎无限的横向拓展,而微服务所依赖的服务发现与负载均衡网关,则为快速拓展带来了非常大的方便。游戏服务器从本质上来说和Web服务器其实并没有非常大的区别,主要的差异大概列一下是以下几点:

  • 游戏不可能做到完全无状态,主要是性能方面考虑。所有数据都只存在数据库或者某个中心节点是几乎不可能的。一个网页,点击一下链接,花费一秒钟打开你可能还觉得挺快的,而游戏你滑动一下摇杆,过一秒角色才动,可能你就想砸电脑了。
  • 游戏的通讯协议一般是直接走TCP或者封装一下TCP on UDP,不太会(也不是没有)走HTTP协议,在频繁通讯的情况下每个HTTP请求都要重新建立TCP连接,每个HTTP包还要带上那么一大坨额外的数据,时间和带宽开销有相对较大,就算考虑到keep-alive可以复用连接,二者的性能还是有明显差距的。(最近看到从Google的QUIC项目升级而来的HTTP/3,是基于UDP的,非常有意思)
  • 游戏服务器没有类似Spring Cloud这样的成熟方案……

这两(三)个问题合并在一起,就让我犯难了。理论上说,我当然希望所有的服务是无状态的,那么TCP也就没关系了,我随便连谁都行。如果有状态的话,我就必须保证同一个用户每次链接的服务都是同一个,这样势必导致需要给每个服务都开放公网连接端口,这些端口还必须有办法让用户知道。如果考虑Kubernetes集群就更完了,每个Container都需要对外暴露端口,这简直没法做了。

Frontd

想了两天(开始写文章的时候是第一天晚上),偶然看到了很久以前其实就看到过的东西……结果豁然开朗

https://github.com/xindong/frontd

感谢沈老师,感谢达达,没想到竟然是这么这么熟悉的大佬们帮我解惑了,解决方案也很容易,只是在出口架设一个网关,每次连接上去以后,先跟他说你要找谁,网关就帮你转发了……理论上和HTTP是一个逻辑。

有些时候,解决方案不是你想不到,是不敢想。

于是周一很开心的写了一天的Go……把这个三年前的项目重新整理了一下,替换了一些依赖,并基于Alpine重新build了镜像,让镜像的体积从257M减小到了7M(多阶段构建大法好!!!)

今天呢,则是build了一天的项目镜像……前面花了一半的时间企图继续使用Alpine打包,结果最后发现Jemalloc不兼容……GG

然后又转而使用Ubuntu,也遇到很多问题,一一解决。镜像传参问题也想到了一个比较好的办法,算是搞定了基本的镜像问题,standalone模式也可以顺利运行了。

明天开始,来解决服务发现问题,并且需要重新改写客户端那边基于frontd网关的连接。

服务发现

服务发现问题解决的比较顺利,除了中途差点操作失误丢失了全部的工作成果,最终竟然是从之前打好的docker image里面把脚本都找回来了……简直万幸,这个故事告诉我们,找回 git 中 staged 的变更只存在理论可能,实际操作中实在太麻烦了……

服务发现本质上要解决的问题是在所有节点共享一份地址簿,当有节点加入或退出的时候,需要根据实际情况实时更新。所有无状态节点都是走Kubernetes服务的,直接通过服务名称即可访问,也是保证高可用的,这部分节点的伸缩,对于Pod外都是完全透明的,不需要动态注册与反注册,只需要订阅地址簿的更新即可。而有状态的节点(比如游戏服务器),在集群中应该是在无头服务的后面,必须要指定到具体的Pod才可以访问,所以这类节点不仅需要订阅更新,也需要动态注册自己。

具体的实现方式是这样的,首先有一个单独的节点作为服务发现的管理节点,称为cluster_manager(暂时这就是一个节点,如果做成高可用就需要解决多节点的数据一致性问题,比较麻烦,这个节点的功能也比较单一,应该不会出现严重的问题,后续有机会可以加强这里的健壮性),其他每个节点都有一个对应的服务发现服务,称为cluster_client。所有节点在启动的时候,都必须先启动cluster_client服务,这个服务会自动向cluster_manager订阅更新。需要注册的节点在订阅之后可以调用接口进行注册,注册成功,则会获得一份地址簿用于更新。后续的更新都会通过之前的订阅自动完成。

成功注册之后那就有个节点下线的时候的反注册问题。正常的下线反注册很简单,就是一个接口调用的问题,但是异常状态下,比如崩溃或者网络连接中断的情况,就必须及时的把这个节点下线。要解决这个问题,就需要一个健康检查的功能,一般的HTTP服务需要维持心跳来刷“存在感”,Skynet使用的是TCP,云风大佬在一个Issue里面给出了一个很好的办法,套用过来就是说,cluster_manager服务在接收到注册后,就会给cluster_client发送一个请求,正常情况下cluster_client不需要回应,这样,当这个节点中断的时候,cluster_manager就会收到一个error,从而得知节点状态异常。顺带着,我把login service到game service的健康检查也顺便做了一下,这样当game service断开的时候,login service就可以及时了解,从而防止推荐不存在的节点给用户。

网关模式的实现

frontd网关模式的实现……就是一把辛酸泪了,理论上是很简单清晰的,泪点都在具体实现上。首先,我研究了很久都没想通为什么frontd连接成功之后不会回应一个状态码,这样的话错误码就无从接收了呀,你接收4个字节,一看不是错误码,哦,那说明连上了,那收到的4个字节怎么办!你还能还给socket不成……或者万一正确的消息恰好就是状态码的一种呢?最后实在无解了,就还是给网关改写了这块功能,也修改了一下相应的协议。

然后呢,frontd的加密使用的是AES算法,具体的应用标准则是基于openssl,这里并没有详细的文档说明到底是怎样得到的最后的base64编码的结果,我所能依赖的只有1、源码中引用的一个叫做go-oepnssl的库;2、文档中给出的一个在线加密网站;3、openssl的官方实现。第一个库基本就不用看了,整个一挂羊头卖狗肉,就单单实现了一个加密也好意思叫go-openssl?而在线加密网站完全只说用了Crypto-js,其他的一概不知,最终我能依靠的就只有openssl了。以下省略N个小时(包括一通宵),之后,终于搞出一个Lua绑定的C库用来生成加密结果了。后续的工作主要就是客户端的对接,顺便解决了一下最近发现的,shell被强制结束后,Skynet进程会成为孤儿进程的问题。一开始我假设了一堆原因,顺便吧UNIX的signal机制好好学习了一番,最后怎么看怎么不对,这为什么会结束不掉呢?苦思许久后灵光一闪,日志服务的logrotate信号好像就是SIGHUP,看了一下果然是的。为了不改Skynet库代码,我在自己实现的日志模块中把Skynet注册的SIGHUP信号恢复为默认了,并重新注册了SIGUSR1信号作为代替。

至此,集群方案的开发部分就已经差不多了,后续的工作主要是尝试在Kubernetes中部署,并合并代码,将现有的客户端通讯替换为集群方案。

想想觉得很有意思,不懂的时候遇到觉得无比巨难的问题,在解决了以后就好像是理所当然的了。人的进步不正式基于此吗:)

最后

提两个小点:

  1. 开始没发现网关方法的时候,为了对外暴露的接口有限,我尝试找寻了一些方法,其中一个叫做LO_REUSEPORT的套接字选项成功吸引了我的注意,网络上所有的文章都把我说的云里雾里,实际尝试确实可以让端口被重复监听,但是会导致收到的消息完全混乱。最终,我还是老老实实去翻了《UNIX 网络编程》,解释得清清楚楚,希望后面有对这个选项感兴趣的同学可以直接去看书……简单来说就是这个选项只有UDP通讯的时候需要启用,TCP绝对不要开就对了。
  2. 看书的时候顺便吧IPv4协议部分又复习了一遍,发现了一个有趣的点,原来127.0.0.1/24(即 127.0.0.0 到 127.255.255.255 )全都是本地环回接口,我实际试了一下,在Windows上,除了127.255.255.255(广播地址)以外,确实都是可以ping通的。这个小知识在当天就派上了用场,我在本地单机尝试的时候,希望和集群保持一致的端口,但是端口不能重复监听,怎么办呢?好办,通常我们都是会开启LO_REUSEADDR的,这样我们在本地只要监听不同的本地环回地址,就可以使用相同的端口的,我们还能在hosts中配置对应的hostname,这样就非常开心了,本地和集群配置完全一致。

一次Redis数据完全丢失的事故

虽然不是我删库,但是还是想跑路

之前可能说过,虽然现在的项目明确最终应该还是要靠MySQL,但是开发期间为了方便,使用Redis做数据落地。

很长一段时间都没有问题,心中隐隐产生了点好像直接使用Redis也没什么问题的感觉。

于是今天就瞬间炸裂了。

早上机房因为一个插线板烧毁,导致断电,不过当时我并不知道具体情况,以为只是普通断网,网好了以后试了试NAS连接没问题,就没有再管了。下午偶然想要更新一下Skynet,结果发现开发服连不上去,仔细一查发现VM宿主机都连不上了,赶紧联系运维去机房看了看,一聊才知道原来是断电了。奇怪的是NAS为什么没问题呢?隐约记得NAS我是设置了断电恢复后自动开机,现在是体会到这个功能的必要性了……下次再出现这种情况,得去把虚拟机宿主服务器也设置一下。

上来以后连上VM看,所有虚拟机都没开,原来VMware ESXi在默认设置下,开机是不会自动启动虚拟机的,查询了一下,把都设置自动启动了。确认虚拟机都启动起来了,也就没再管了。结果过了一会儿,徐哥问游戏服务器的连接问题,我有些纳闷,一看,果然都挂了。这时候正好要去参加一场面试,粗略看了眼好像是Redis问题,systemctl查看果然是Redis没有启动,restart也无效,只好嘱咐徐哥帮忙看一下,准备好材料先去面试。

回来以后问题还没解决,这个问题非常奇怪,系统日志里看Redis只是单纯的启动不了,徐哥查看了Redis日志说是没有权限读取dump.rdb,查看权限竟然是600,所有者和群组都是root。权限设为755后,读取报错,确认应该是文件损毁,改名之后,Redis顺利启动。BGSAVE生成新的dump文件,对比发现,这次生成的文件所有者是正确的Redis,大小也远远超过损毁的文件,看来原来的资料是没救了。损毁的原因猜测应该是断电的时候正好在dump,不过怎么会这么不靠谱呢?仔细去看了一遍Redis有关持久化的文档,以及conf文件,终于搞清楚了,原来是自己的用法不对。

Redis 有两种持久化方案,RDB (Redis DataBase)和 AOF (Append Only File)默认开启的只有RDB,定期全量备份全库,这个从文件名dump也能清楚的看出来,平时非常好用,但是致命的问题就在于……RDB并不保证完整性和一致性。万一在dump的时候断电,那么就是直接的结果……文件损毁。

而AOF就是为了弥补这个缺点而存在的,直接的理解就是记录日志,根据配置可以对每一条记录写一次,或者每秒写一次,在超过指定大小后,还会重写整个文件,防持续追加记录导致文件过大。

找到设置,开启AOF,以后就不怕再断电了。正高兴呢,结果查询了一下,发现Redis竟然数据又消失了……仔细考虑了一下,开启AOF之后,Redis启动时就不再读取RDB,而是根据AOF日志恢复,而现在AOF是空的,那么当然恢复不出任何数据了。这个问题的解决方法也很简单,要么从一开始就开启AOF,要么配置文件中先不开启AOF,而是通过RDB启动起来以后,使用client调用命令手动开启AOF。我这样操作以后发现依然没有数据,这可就奇怪了。再仔细思索一下,心中一凉,完了……又把数据搞丢了。我在上一次发现数据库是空的,然后stop数据库准备不开AOF启动那一次,直接就把空数据库dump了出来,覆盖了有数据的那份……唉……太傻了。

删库有一个好处,我趁机把之前想改的一个key结构给改了,反正都全部重来了,也算苦中作乐吧。好在反正就我们这几个程序策划在里面,给每人发点金币补偿一下吧,哈哈哈。

这一段时间

每次因为什么想要写点什么的时候,总会在脑子中过一圈,然后觉得这也不想写那也写不了,聊聊公司团队的事情吧,顾虑太多;聊聊技术问题吧,自己都觉得写出来也是见笑。什么都不管,先打开网站登个后台顺手点个升级,然后发现,连接错误无法访问——好家伙,阿里云这是把Wordpress整个域名墙了么,升级的小提示点不掉真是浑身蓝瘦。

看看上一篇,3月份,这之后可以说是开始了从项目角度说最好的一段时间了吧,策划和技术都在快速推进。倒是我这边,开始陷入了一点战略战术上都不确切想得清楚自己到底该做什么的境地,开始渐渐有些焦虑。

3月底的时候徐哥过来,客户端开始正式推进,写了个B站爬虫,看MMD分类视频的播放数据,看了《Unnatural》,去参加了出版局的培训,买了打印机,搞定了公司网站ICP备案的迁移,4月的时候思聪突然提出要离开,叽里呱啦,面了一堆美术,找了杨大哥,把事情敲定,20号战神发售,沉迷了一段时间,真不错。

以上……是靠Google历史记录翻出来的……互联网隐私真可怕233

5月开始就是正式开始搞服务器和平台,期间去参加了核聚变,感谢机核大佬,和吹哥吃了个饭,后面继续搞服务器,基本就是安心用战术上的勤奋掩盖战略上的懒惰,当个快乐的码农了,大概在6月底的时候感觉略微有些入门,大体上是理解意思了。

服务器招人是从来没停过,一开始是觉得两个方向,1. 找一个可以全部搞定的人把这个搞定;2. 找一个搞Skynet有点经验的人,按照当前的方向进行下去。结果来说,至今来没找到合适的(残念在于有两个职位其实已经谈好了,结果候选人自己放弃了……)到目前为止,基本上当前项目来说感觉基本上其实已经差不多了,今天我甚至已经在考虑要不要直接招一个搞Java web服务器的来忽悠他转行233。真的无奈啊,游戏服务器怎么会这么难找……神了

写服务器的经历还有值得一说的也许就是确实打破了我对动态语言的偏见吧。刚开始确实是觉得心智压力极大,写到后面也就觉得还好了,不过对打字速度的要求确实是比过去高了不少,哈哈。Lua比我过去想象中的感觉要好很多,已经有些爱上这门语言了,第一次尝试不使用OO范式写项目,感觉也很新奇。总之在编程领域也算是开启新世界的大门了吧。

 

为什么我不用单例

突然发现,其实自己倒是很少写技术相关的内容,仔细想想,还是有些内容是希望聊聊,记录下来的。这是一些经过长期思考、验证,至今依然觉得有其道理的内容。

今天先从一个简单的问题着手吧。

编程学到一定程度,从大学开始接触的话大概在大二大三的样子,基本都会接触一个新鲜事物——设计模式。回想一下我看的第一本设计模式相关的书好像是《设计模式之禅》。设计模式给我的最大震撼是终于理解了之前看到的很多写法到底为什么这么写,这也是自己从把代码作为一个固定模式使用,转变为开始自行思考写法。换言之,从“编写”代码,到“设计”代码的一个转折吧。

初学设计模式,我也和很多人一样,印象最深的,就是“单例模式”了。对于自己已经熟知的写法进行组合,竟然可以产生这样的效用。而真正写好、写正确一个单例,又没这么简单。我至今都很痴迷这种简单与复杂的交织。

大学后来写Web写App,单例倒是确实没怎么用到过,所以渐渐也没那么在意了,再到后来,幻刃成立之后,开始写游戏项目,这个问题很显然地就出现了。

Unity 是脚本驱动的,每个 GameObject 上都可以挂载不同的脚本,每个脚本负责自己的数据和逻辑,这个思路是很清晰的。那么我们来设想一下,一个初次开发游戏的开发者,会怎样组织代码呢?

一开始,肯定是每个脚本自己管自己的事情,需要相互引用就“拖一下”,逻辑简单的时候这样是不会出现什么大问题的。

然后呢,他就会发现,自己有挺多地方有着差不多的代码,这时候呢,挺有可能他也听说了一个新鲜词,叫做“DRY原则”,自己也深以为然,于是开始思考怎么解决这个问题。

首先,可能最容易想到的代码复用方式就是“工具类”。这个平时调用过一些库的话,很容易就能看到并理解这种方式。为什么不是类继承呢?因为这在设计上实在要求太高了,大概在这个阶段我自己对于继承实在还停留在 Rectangle 可以继承 Shape,甚至刚刚搞明白如果我再想要一个 Square 类那该是怎样的继承关系(这个问题确实还挺有意思)。什么接口呀,抽象类呀,还都没在工程上用过呢。后面我也会具体谈到继承的方式在工程上面使用的一些具体情况。

说回工具类,工具类嘛,在面向对象语言中的实现基本就是一个名称以Tools或者Utils什么的结尾,只包含静态方法的一个特殊类,这样,需要复用的方法,就可以复制粘贴过去,然后在任何位置直接调用了。

这种方式好用吗?太好用了,于是你渐渐的就会有自己的MathTools、LogTools、NetTools、FileTools等等以及更多业务相关的类。

再然后,你会发现这种方式好像可以解决一个新遇到的问题,那就是怎样进行脚本间通讯的问题。

比方说吧,大部分游戏,肯定有个概念是主角嘛,Unity中的话,这就是一个GameObject 了。主角对象上可能还挂了一个脚本,负责一些基本逻辑,比如叫做Player。那么,在其他脚本中,如何获取这个脚本呢?

一个比较Unity的做法是直接 GameObject.FindWithTag(“Player”) 拿到GameObject,然后 GetComponent<Player>(),或者是 Object.FindObjectOfType()。这类方法,即使是初学者也会很快明白是不对的。

  1. 需要遍历查找,性能非常差;
  2. 通过 Tag 的形式依赖字符串,所有需要手动保证不输入错误的地方,我觉得都是有问题的;
  3. 如果有大量目标需要查找时,非常难以区分与管理。

那么,很自然的,解决方法就有了,写一个叫做 PlayerManager 的静态类,声明一个公开的 Player 字段,在 Player 初始化的时候,把自己赋值上去就好了。虽然这种初始化时把自己的引用传递出去的方式感觉稍微有些违和,但是至少问题是解决了的。

这个问题本质上其实是任何时候,获取一个特定的引用,在面向对象语言中,基本需要依靠“静态”这个概念,这点是跑不掉的,那么除了直接使用静态字段,还有没有别的方法呢?

很自然,单例天然的解决了这个问题。更因为C#的CLR级别泛型支持,有一个特别方便的单例写法。(本篇不讨论单例正确写法问题,我们先用最朴素的单例实现方法,不考虑懒加载与线程安全问题)

直接一个继承就搞定了。

然后你就可以在任何地方,使用 SingletonExample.Instance 访问这个对象。

咋一看,这和使用静态类没什么区别啊,只是静态类的字段需要多个static,单例访问的时候需要多写个Instance。

再深入想一层呢?有一个很大的区别就是在单例是可以懒加载的,因为其实本质是是个对象,在需要的时候,还可以解除引用进行GC;还要考虑到序列化方面的问题;再有,比如你写了一个FileSystem的静态类,然后发现其实你需要对不同平台编写不同的实现,使用单例的话只需要写多个子类继承FileSystem然后分别实现,最后使用编译选项控制FileSystem的单例实例化的具体对象就可以了,这些情况静态类就都别想了。

这么说来岂不是使用单例还是挺好的,除了每次需要多写个Instance比较繁琐,但是也是可以依靠IDE代码补全,或者干脆命名为I来省事。

我们回过头来想想,单例本质上是解决什么问题的呢?对了,是解决在同一Runtime下确保只存在一份该类的实例这一问题。只有一份实例之后,可以全局便捷访问到,只是 一个附带的作用而已。所以实际上来说,我们是利用了一个副作用,而忽略了本来使用单例的目的(其实我们确实不管理是不是唯一,甚至在Unity中我们也并不能保证,一个继承了Monobehaviour的单例,因为并不是也不能通过new的方式实例化,所以生成了多个是很容易也常见的错误)。实际上,如果想解决“只有一个实例”和“在全局便捷的访问”其实分别会有更加合适的方法。

再说到全局访问,一个人写小规模代码的时候这是个很方便的做法,但是项目一旦开始扩大,人多了以后,你会发现这几个问题。

  1. 滥用,你可能花了很大心思保证两个系统的解耦合,但是因为他们都可以很便捷的被访问到,那么接下去的事情就不是你能控制的了。此外,如果单例成为了给实例提供方便的访问方法的项目标准做法,那么势必会出现大量单例,虽然很明显,不是每个单例的创建者都希望别人在任何位置使用它,但是你却不能用除了约定以外的方式来确保这一点。
  2. 你无法控制启动顺序,单例的懒加载作为一大优势,同时也可能成为一个难以解决的问题。懒加载单例的加载顺序,是由调用方决定的,大多数情况下,单例们都可以自动根据依赖关系顺利启动,但是这种自动,很多情况下其实都不是你希望的顺序。甚至,很容易造成循环依赖
  3. 别人很难通过代码本身了解到框架的设计者提供了那些功能,文档的编写和维护对于小团队来说成本是挺高的,大家协作开发时,也不可能每写一个功能前都问问大家,是否曾经在框架中已经实现了。我在早起开发中,就经常遇到代码库中,同样功能的函数被不同人实现多次的情况,甚至有时候自己都不记得了,又再写了一遍。

说到这里,我们会发现,对于一个游戏运行时脚本系统来说,单例完全就是个不需要的设计,我们想解决问题,其实很好解决。

所有这些需要便捷访问的模块,我一般称作“服务”,他们全部作为普通的类来实现就可以了,然后写一个静态类,把他们都放进去。服务也可以有子服务,子服务的生成销毁与访问都交给父服务即可,逐级推到最上层,会有一个主服务,负责按顺序实例化所有服务,并注入到这个静态类中。这个静态类,一般被称为“服务定位器”

因为有了“服务”这个概念,就隐含了服务定位器中的对象,一定就是希望可以全局便捷访问的,而服务与服务之间,则是尽量解耦合。当你遇到需要和脚本外部交互的情况,第一反应就应该是敲一个L.(我喜欢把服务定位器类命名为L,表示ServiceLocator),IDE的自动补全即会自动提示我可以使用的服务。即使没有IDE,打开看一眼L类,有哪些功能也都清清楚楚。服务相关的脚本也都可以放在一起。静态工具类也都可以放在一起,并且明确只会包含状态无关的纯函数,比如拓展数学库等。按照这样的方式,单例就可以明确得禁止在项目中使用了,即使可能有人把不应该的模块当做了服务,也非常容易被发现并得到修正。

终究我们会发现,我们大多数使用单例解决的,都不是单例应该解决的问题,反而会带来大量的副作用,所以,用正确的方法解决问题,而不是解决问题。


对于有一些经验的游戏开发初入门者,我一直很推荐三本书:《游戏引擎架构》、《网络游戏核心技术与实战》以及游戏编程模式。最后一本篇幅不长,但是就游戏中使用的设计模式问题进行了大量的有针对性的讨论,关于单例与服务定位器的部分,都有专门的章节描述,比我写的不知道高到哪里去了。

每次看到都想吐槽《网络游戏核心技术与实战(オンラインゲームを支える技術)》这本书为什么翻译了个这么《21天学通C++》的名字,导致我一直以为是本垃圾书没有看……

再次断更了很久

再次断更了很久啊,回想一下,中间发生的事情太多以至于也不知道从何说起了。

先说项目吧,第二个里程碑结束后,第三个里程碑遇到了巨大的需求变更,因为受到了Slay the spire的启发,最终的需求文案中将游戏流程改为了类似的外围Roguelike。在我的感觉有点Project Love的即视感。当然这个倒不是关键,但是类似的遗物系统的加入,着实让我受到了不小的打击。

原本的数值框架,是根据当前有的需求,在考虑了怎样方便的在表格中配置后,设计出来的。主要思想是把所有可能变化的数据,设计了一个最小单位,称作辅助配件,·这样,所有的数值变化都可以认为是N个辅助配件的叠加。在所有的数值表上,任何变化(包括初始化),只需要在对应的字段填值即可。当然,因为分为叠加和相乘,所以每个变量都会有两个字段。而根据最新的需求,因为遗物的加入,则需要允许几乎所有的数值都可以变化,导致之前的设计就完全不可能使用了。

当然这只是个表面问题,单纯问题的解决倒不是最主要的打击点。主要是我开始发现,在数值架构这个问题上,单纯的由程序根据策划当前版本需求设计数据结构是不可能的。首先,策划会要求使用Excel,这个要求实际上是需要把所有结构化数据全部放入一个二维结构里。在策划对于自己的体系没有一个清晰的了解,我这边也没有相关经验的情况下,如果这次也是按照之前的方法,就很有可能出现后面再加入新需求,还得改数值架构的情况。

于是,我就开始考虑其他人是怎么做的。

这个需求对于国内MMO手游来说肯定是已经解决了的,所以问题就是他们是怎么解决的。

当然……答案其实我也差不多能猜到……所以还是想看看有没有更好的方案。

最佳方向其实是看看Slay the spire……所以……就看了……

打开目录,发现大部分的容量都被一个.jar包占据了……右击打开压缩文件,从库文件看是用的一个叫做LibGDX的框架。Google之,原来是个Java写的框架……那……原谅我怀着罪恶的心态学习一下了。

先看了看LibGDX,下下来构建了一个项目,把解压出的资源放入对应路径,代码用jd看了看,再修复了各种奇葩的问题,尝试运行……好吧,竟然跑起来了。

突然很想帮他们移植一个手机版本……

从架构上来说,首先,他们的数值是全部写死在代码里的,一个遗物一个类。遗物的抽象类中有很多回调函数,可以允许你在切面上进行操作。比如一个遗物是下一张牌提升伤害,他们的写法就是在打出牌的回调里,把这张牌的伤害硬加上去了……而整个项目的几乎所有位置上,都充斥着类似

这样的代码。

回调的方法本身没什么问题,虽然我更希望直接注册全局事件,这样耦合更低。但是这种使得几乎所有模块都和各种具体的遗物强耦合的写法实在是令我无法接受。同样,写死所有参数的方式,对于后面程序与策划的解耦合,也是一场巨大的灾难。

这个问题,不是个纯技术问题,换句话说,不可能由我一个人,或者任何一个程序员一个人来解决,这种无力感在年前的很长一段时间都沉重地压在我身上。

在1月底的时候,我突然接到运营那边的消息,说ICEY的所有版本需要紧急修改,一个是需要加入公示信息(写明版号文网文什么的,国产游戏打开都有),还有个是需要根据送审版本,把犹大的名字改掉,时间呢,是2月之前。

哦,2月之前啊,我看看屏幕左下角。

2018-01-31。

好的,我加油。

另一边,其实策划同学也已经感觉按照现在的构想,数值这边确实是很有压力的。于是也一直在积极招人。很快,我就看到了一份非常令人激动的简历。所以,确实大部分时候,一个人是否合适,光看简历已经能基本确定了,有点玄学。于是年前一周,晓浩入职。

因为制作人年会的关系,我也算是蹭了两次心动年会了。我们之前年前基本上就是大家一起吃一顿,还从来没搞过正经的年会。今年,我觉得还是很有必要可以开始准备了,只要开始了,肯定会越来越好的嘛。

唱歌跳舞就算了,想想就觉得如果准备节目那绝对是大家与我和争争两边的双重地狱。除了节目,年会还能干吗呢?嗯……抽奖……

花了一下午写了个抽奖游戏,拜托冬哥给大家都画了一幅大头照,自己玩玩感觉效果不错。

结果……效果真的不错……谁不想要哪个奖品,就一定会抽到他,哈哈,声控抽奖,节目效果满分。

年会上也宣布了今年的年终奖,大家一起过个愉快的新年吧。

年后回来。

晓浩在几次主动找我的聊天中,其经验与能力很快展现了出来。很快,我就看到了全新设计的数值框架。问题解决。根据他自己的意愿与实际能力,肖哥和我一致希望他直接担任产品经理和项目经理,他本身也很有动力。于是,我们开始具体执行了。

结果,遇到了之前从未考虑过的新问题。

因为之前的经历和经验,我虽然依然在寻找主程,但是已经不作为确定性的条件了,基本是抱着至少这个项目,大概率自己来带的觉悟。

结果,第一次跟晓浩沟通具体需求实现问题上,就发生了问题。

当时我只拿到了一份数值属性说明,晓浩来找我确认这样出是否可以。在我看来,我在完全没有拿到其他文档,甚至连是否有其他文档都不清楚的情况下,单凭这一点信息,是无法确认的。虽然单纯的把这个数据结构实现出来是完全没有问题,但是仅仅是这样做完全没有意义,我理解的确认,是需要确认这部分实现,在整个框架下是否可行,以及是否有充足的可扩展性。不然的话,我永远会担心这块。

就这个问题,以及产生问题的原因,我和晓浩进行了大量的交流,甚至一度状态有些紧张。第二天,我开始渐渐发现问题的根源:理论上,因为晓浩是产品经理和项目经理,所以产品的需求实现与项目的进度保证都应该是他的责任,但是因为我具体的负责了项目,那么,我是一定会自己去保证的,从这个角度来看,我和晓浩的合作是一定会产生冲突的;而交流的问题,因为客观上我和晓浩的实际职位与项目组内职位发生了错位,面对一个同时是自己上级和下级的人,即使晓浩已经理解这个问题,还是不免产生大量的客观冲突。

这个问题想要解决,不外乎两种方法:硬怼和换人(当然换的人是我XD)。

硬怼可以解决问题,但是不解决根本问题,客观上来说,只要我负责项目具体内容,那么我不自己来保证项目是不太可能的,更加严重的,这样做明显会严重的影响晓浩的心情与能力发挥。而换人,当然问题就是怎样找到合适的人了。

因为年后回来以后就和争争商量了重点要放在招人方面,也开始有一些简历收到。

最终,还是依靠晓浩推荐了他原来和合作伙伴,徐哥过来。他们之前已经有五六年的合作经验,也有了不少已经上线的项目,从理论上说,确实是解决当前问题的最好方案了。

因为很早以前晓浩就对服务器这边提出了一些担忧,正好这个阶段我也没法在客户端这边进行继续的工作,于是开始跟诸谦一起推进服务器这边。目前已经把大部分的流程都走通了,就等实际需求过来,设计好协议就可以用了。

目前的计划来看,我后续主要是作为PMO负责人对项目进度进行监督,服务器在没有明确负责人的情况下,需要负责推进以及对各部门进行技术支持——使用各种技术解决大家的问题以及提升效率。

从下个月开始,项目就需要开始进入快速推进了,以完成六月底的CJ版本计划。

结果只说了项目……

Optimized by WPJAM Basic