最近一年多,我在工作中最大的感悟之一就是:游戏是真的很挣钱。随着业务侧在领域中的不断获利和探索,作为一名工程师来说,也有机会去了解游戏服务架构和技术体系的迷人之处。而对于一个存粹的web服务端工程师来说,进入游戏领域的开发是一种全新的挑战同时也是一段不错的成长经历。
对于web的服务端和游戏的服务端,技术要解决的问题和建设的重点其实是有些不同的。就整体的架构来说,web的服务架构更倾向于一种cp的分布式系统架构,它首要解决的问题是要保证用户数据行为的一致性,因此在web的服务端用户的状态被保存服务集群的外部分布式存储。基本上所有的用户请求都会有一个跨网络分区的调用。在和前端的交互上,使用http这种用户用完即走的协议。同时所有的数据和配置基本是存储在服务端,前端的工作更多是将信息以更加友好的方式展现给用户。前端很少要数据上的处理。可以说是重服务端轻前端。
但对于游戏领域的服务架构来说会有略微的不一样,游戏领域更重要用户的游戏体验,点击反馈的实时性和及时性对游戏的体验更加重要。游戏领域的对战服务房间架构更偏向于一个ac的服务架构:所有的可以数据在机器内存中进行计算,前端和服务端使用websocket来及时的交换数据。同时不再前端不再仅仅是一个信息的展示窗口,而是要承担更多的数据计算的工作,有时不得不借助游戏引擎来加速整个的开发过程。
同时对于游戏而言,游戏中数值与数值的交互是游戏玩法的核心,技术需要保障数值准确无误的导入系统,并灵活快捷的修改系统中的数值。并在此基础之上提供校验和模拟能力。本文就游戏领域中对数值治理的经验进行总结和分享,希望对读者有所帮助。
另外游戏的前端更加吃视觉和美术资源。
游戏服务端面临的挑战
试想一下当前有这样的要给业务场景:
用户在进行闯关,用户有6个装备孔位,每把装备有攻击,防御,暴击,连击等属性。同时具有以下的养成维度
- 升级: 装备可以升级,每一级的加成不同。满级1000级。
- 升星:每升一星可以升级的上限就可以进行提升,升星以后外观将会更加炫酷。
- 淬炼:每一把装备在某一星级后进行淬炼获取额外的属性加成。
对于闯关场景,每一个关都有以下的配置维度。
- 每一关的守护兽,守护兽有血量,防御,攻击等属性信息。
- 每一关的闯关道具不同,精英关需要精英门票,普通关需要普通门票。
- 每一关的闯关奖励不同,掉落装备碎片,淬炼石,经验值等用于用户的等级和装备升级养成。
这是一个非常经典的pve的游戏逻辑,但我们已经可以大概感受到游戏服务端对于开发人员的挑战有哪些:
首先是数值庞大,交互复杂。人工配置和维护复杂的体系是极容易出错。因此基本上不可能像web业务一样直接人工录入配置。需要开发工具来进行数值的解析和配置。
其次是游戏玩法非常的依赖于数值。数值错误或者不平衡会产生质损并破坏游戏平衡,给游戏经济体系造成不可挽回的后果。
同时在游戏的开发过程中,非常考验在协作过程中的信息沟通能力:如何付出最小的代价来确保三方(数值策划,研发,前端)对数值的理解一致,使用的数值版本一致?
针对以上的主要挑战,笔者在工作中进行了多种的尝试,以下是笔者实际开发过程中使用的管理和最佳实践。
基于DB的数值管理
其实就拿数据库来存储配置,在游戏上线之前将数据库的数值提前插入进去,系统直接读取DB的内容。为了降低DB的压力,在实践过程中,通常会在db上套一层分布式缓存,和本地缓存。
优点
- 结构化的数据,可以提高数据配置结构的正确性。
- 可以通过DB后台检查和统计配置数据。
- 可以在db的基础上搭建运营后台,可以脱手进行运营。
缺点
- 增加了DB的压力和缓存系统的压力。
- 灵活性差: 需求和数值变更时,修改DB更加麻烦还需要保证缓存系统一致性,一般需要提前预留好DB数据调整的后门接口。
- 测试环境和线上需要两套配置。修改变更后需要代码先上线。
- 配置变更无历史快照保存
在实际的游戏运营过程中,线上的数值变更是在所难免的,而且变更的频率可能要比我们想象的更加频繁。所以一旦使用这种db+缓存的方式来管理数值,那么我们最好就一开始留出足够灵活的用于更改数据内容+缓存的后门接口。如果在分布式缓存之外还有本地缓存的话。如果不想重新启动服务,就最好能够有手段将数据的变更保证分发到各个服务节点。确保各个服务的数值都进行了更新。(可以通过mq广播,也可以通过配置中心变更推送)
在实践过程中,用数据表管理全局性的数值总是给人一种过于笨重的感觉,而且也不适合管理一些结构复杂的数值体系。所以并不推荐使用数据表的方式进行管理,但也并不是完全不能使用。当游戏玩法积累到一定的程度时,一般我们就需要将数值变更的能力进行平台化(比如一些常规的游戏周期配置),然后交给专门的运营同学进行运营。而这个时候db相当于下面的两种方式,在线上实时更改的方便性上便有了一些优势。
基于分布式配置中心的数值管理
比如使用nacos保存配置,将数值生成为一个json格式的字符串,直接将json保存在配置中心。在服务启动的时候,读取json序列化实体,并保留在内存中。
优点
- 灵活性好:配置新增与修改可以一键推送,不需要代码提前上线。
- 借助配置中心的能力,我们可以获得数值对比,和历史版本回退能力,以及可靠性投递能力。
- 访问快:所有的数值都是本地缓存。
缺点
- 容量有限:配置中心存储的配置内容是有限制的。如果数值深度较大,配置中心将无法进行管理。
- 约束性差: 配置项配置容易出错,结构错误或者数值内容错误。而且这种错误很难察觉。如果出现问题,则会对系统产生较大的影响。
- 可读性差:配置的数值内容多为编号。比如装备id,头像id等各种资源id。配置成本和维护成本极大。
- 配置项目分散:大量数值分散在配置中心,查找起来比较麻烦。当然也可以通过创建标签或者命名空间来进行配置的统一管理。
- 对于部分复杂的配置,通过人工生成json几乎不可能。这个时候就需要开发同学需要单独开发Excel解析工具来生成json,并对excel的内容进行验证,然后再导入配置中心。每当Excel的结构发生变化,对应的代码也需要跟着调整,校验规则也需要调整。
- 每一次的数据变更都需要开发介入。不太好在配置中心的基础上做脱手的运营平台。(当然并不是完全不能搞,有一些配置中心平台支持api来读写配置内容)
通过配置中心的方式来保存数值的方式是在实践中较为常用的手段。因为它方便修改和新增,而且所有的数据都保存在机器的内容中,访问速度比较快。
对于复杂的数值(装备,洗练,闯关)可以与策划约定Excel表格结构。按照约定,数值策划直接填写excel,然后开发通过代码直接生成对应的配置json数值,并顺便通过代码检查约定的结构是否完备,整体的数值是否按照规则呈现于线性变化,从而更好的保证数值的正确性。(也就意味着开发需要更多的开发时间和维护成本)。
而对于简单的配置,则可以直接人工生成json,直接填入配置。注意这种方式虽然方便但也很危险,我想基本上每一家公司都会有过因为改错了配置到导致服务出现异常而产生较坏影响的惨案。所以在修改配置中心的时候,我们需要谨慎再谨慎。
在开发Excel解析工具的时候,其实也有很多的坑,下面列出笔者踩过的坑。
注意1:Excel中的cell里的内容通过office看到的内容和代码读取的内容可能不是同一份,cell里的内容可以通过公式和宏定义转换而来的,所以需要特别注意读取到cell的是否是就是最终值。避免方式如下。1. 建议优先使用csv格式的表格,可以避免Excel中隐藏公式。 2. 建议在解析cell时判断cell内容的格式:是否是函数,小数,字符串等然后分别处理。
注意2:一般来说,配置中心的一项配置并非是无限大的,如果数值深度特深,那么生成的json就很容易超过限制。这个时候就需要将json保存成为一个文件。然后将文件上传到文件图床,配置项目中只配置文件的图床地址。应用读取文件的二进制流进行反序列化。
1. 服务在部署的时候会加载配置内容,此时下载文件时,注意要进行retry,在retry 多次仍然失败以后进行报警并直接宕机,防止影响线上的内容。
基于csv方式直接导入的数值管理
不管是db的方式还是配置中心的方式,都是将数值间接性的导入程序中。这样有两个问题总是避免不了的。
- 测试环境和正式环境的数值分开维护,上线之前需要确保数值先上线。否则服务就会出错。
- 前端想要读取数值,则就必须通过服务端来拉取。性能比较差。
那么能不能直接将整个二进制文件并入代码的文件内,在启动的时候,直接读取代码内的文件呢?前端存储一份,服务端也存储一份。一方面上线的时候数值可以随着服务之间上线。另一方面前端也有同样一份数据,相当于是做了个端侧缓存,这样就可以节省网络成本从而大大的提高性能。
顺着这个思路。我们将所有的数值excel文件单独保存在一个仓库中并用git进行管理。并在仓库中同时纳入服务端的maven管理工具和前端的打包工具。所有的excel使用纯文本格式的csv文件。三方共同维护这一个git工程目录。
- git+csv能够追踪到行级别的编号。同时可以使用git的钩子,在每一次push之前执行一段脚本代码,对变更的csv文件进行完备性的验证。
- 使用打包管理工具,在每一次上线发布的时候都打一个正式包,确保了线上数值的稳定性。
- 前后端包括策划共同维护一个仓库,降低了信息同步失真的风险。
另外,不管是db的方式还是配置中心的方式,配置的内容可能大量的都是一些资源的编号,比如礼物id,装备id,装扮id等等这些数据库的主键id。由于线上测试环境的数据库隔离,所以这些的id线上和线下大概率不会相同,也就意味着同样的数值要求,测试环境和线上就必须配置分开的两套。也就意味着会有一些线上场景无法在测试环境进行充分的验证。而且人也很难读懂这些编号背后代表的含义。那么为了解决这个问题,我们对游戏系统内的所有的资源进行按照规则编号。excel内所有的配置直接配置编号,不再使用资源的id。从而可以避免线上测试两套配置和配置不可读性的问题。
[!物料编码规则]
- 所有物料命名使用三段式,三段式缺一不可。
- 物料唯一编号由技术分配需要保证全局唯一且符合三段式结构。三段式结构如下
<分类>:<子类>:<编号>
正则表达式:(?\w+):(?\w+):(?\w+)
- 分类:整个游戏世界里的大类:字符串类型:如:道具(prop),账号(account),英雄(hero)等。对应着系统底层的不同的存储数据表。
- 子类:某一个大类下的子类型。字符串类型。如道具的碎片,礼包等。注:若大类下只有一个子类,则将该大类做为子类。比如英雄 hero 则其子类也是hero(hero:hero:101)
- 编号:子类下的物品的编号。非负整数,从0开始。若子类下只有一个编号,则编号为0。 例如: account:coin:0 hero:hero:101 prop:fragment:101
优点
- 三端(策划,前端,服务端)内容同步,git结合csv能做到行级别的版本控制。
- 可支持大量的数值配置体系,同时还能保证可读性。
- 技术成本低,前端和服务端均可通过插件快速将csv配置导入到系统。
- 线上线下可以共用一套数值,无感配置上线。
缺点
- 灵活性差,文件在服务启动时加载。在配置变更时需要重启服务,前端需要停服。
- 学习成本:需要推策划学习git工具。(跟策划讲:技多不压身)
由于csv是文本内容,所以不会出现像xlsx格式的文件那些cell的内容不是最终值。但有得就有失。csv是不支持多sheet的能力的,不过这也恰好满足了单一性的原则:一个配置文件仅描述一个维度的配置。
在实际的开发过程中,并非有一种一劳永逸的办法能够满足所有的场景。上面的三种方式基本是搭配使用的。
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。