实习总结(一)
长达五个月的实习告一段落,从三月份到七月份的实习经历,需要写篇比较长的文章来进行系统的总结,方便后续复盘掌握的知识 笔者是在一家小型游戏公司实习的,从事的岗位是游戏服务端的开发,主要开发语言是go。下面我将从几个方面进行这段经历的 总结:
- 认识点
- 技术点
- 解决的线上问题
- 困难点
认识点
这次游戏公司的实习之旅算是打开笔者的游戏开发的大门,游戏服务器开发和传统的互联网服务器开发存在方向上和技术上的不同。游戏后端和web后端一样,都是接受客户端传过来的主动事件和数据,对用户数据的状态进行改变。传统的web后端的用户数据状态相互之间的耦合性不强,通常根据客户端传递的事件就可以一步修改到位,所以web后端的调用可以划分为post、get、delete等类型的调用。web后端对用户状态的强一致性要求比较严格,即一份数据对所有的用户需要保证一致性,持久性,安全性,所以对于数据的修改通常伴随着持久化的进行,状态在服务程序中停留的时间极短。反观游戏服务器,哪怕是一盘休闲游戏,游戏持续时间也会长达几分钟。这几分钟中,玩家的数据状态,场景的状态等等都需要长时间停留在内存中,在内存中进行查询修改。读者可能会问,不能将状态维持在数据库中吗,游戏需要保证响应性,数据的查询和修改需要在极短的时间内完成,并且保证对所有玩家保证一致性。数据库的查询修改性能不可能支持这样的需求,所以状态数据不可避免的停留在内存中,通过程序去保证其一致性。当然,一定是存在重要数据需要被及时持久化的,比如道具信息,背包信息,玩家数据等等,根据需求在特定时间点去完成持久化操作。
长达五个月的实习,我对游戏服务器的开发的比较深刻的理解可以总结为一句话,这句话可以指导我们根据策划需求去设计游戏服务器架构,业务实现架构:状态的更新和同步贯彻始终。
状态的更新和同步
面对一个游戏需求时,我们要习惯性得去分析其如何抽象成为一个保存在内存中的可修改的状态,所有的游戏需求都可以被抽象成为一个个状态,这些状态相互依赖,通过彼此共同工作,组成游戏需求。如何定义状态呢,它可以是一种数据结构,数组或者哈希表,这要根据游戏需求来确定,后续我可能会写篇文章继续讨论这个问题,如何针对不同的状态确定其使用的数据结构。它也可以是个抽象的概念,比如说阶段,过程之类抽象的东西,通过触发某些事件,或者达成某种条件,使得状态得到改变
根据游戏需求定义好状态后,就可以围绕这个状态,明确状态改变的触发函数,编写其状态改变的执行函数,具有一致性的状态查询函数。状态的触发修改函数和修改函数需要根据不同的需求,这个是比较好编写的。极大影响服务器架构和游戏性能的是状态的查询机制,这个可以说是游戏服务器开发的重点中的重点。不同的游戏类型,不同的需求都会影响查询机制的制定。其实这里的查询也可以称为同步,状态信息从服务器到客户端的传输。
客户端画面的渲染会强依赖于这些状态,这些状态是客户端画面渲染的输入参数。状态的正确修改和同步,才能使客户端画面能够按照策划的那般去渲染呈现。一般将状态分为两种类型,共享状态和非共享状态。对于共享状态,需要定义称为快照,快照内包含保证玩家画面渲染需要的状态,由服务端主动推送,客户端接收到快照更新画面渲染。对于共享状态的修改,需要进行广播到所有客户端;对于非共享状态的修改,需要单播到指定用户。
对于状态的界定,会影响性能。状态划分粒度太大,会造成网络传输的压力;状态划分粒度太小,会造成系统的复杂度太大,不利于后续业务需求的迭代。对于状态划分的标准,我在日常的工作中总结了以下规则:
- 存在依赖关系划分为一个状态
- A的修改会造成B的修改划分为一个状态
- 业务上存在从属关系的划分为一个状态
方法职责划分
我们根据业务划分完状态,在编程中,通常以类或结构的形式存在,那么如何构思其方法呢。我认为需要有分层的思想,对于一个系统,我们需要有意识的将其进行分层,类似于Java中的MVC思想,越靠近玩法实现或者画面渲染资源管理的类,位于上层;越靠近底层设施,比如存储层,中间件层(redis、消息对列、任务对列等),其功能通常为为上层提供某种服务,其位于下层。
越靠近上层,其方法需要具有更明显的抽象性,通过使用底层工具实现业务需求,同时需要做好工具的可替换暴露,方便后续的业务的变动导致需要替换不同的工具。这些方法不用担心签名变动到来的影响,因为调用的地方通常比较少。但是对于会被较多地方调用的方法,仍然需要保证签名的泛用性,如何提高签名的泛用性会在后文描述。