天刀在韩国化的过程中,韩方对天刀的游戏内商城功能提出了一系列的适应他们本土运营习惯的商业化改造需求,其中最重要的基础功能修改就是需要商城能够支持不停服修改商品内容或上下架商品,包含新增未事先打进版本配置里的内容。
天刀的商城从设计上原本支持商品的定时上下架,并可通过gm指令动态上下架商品,但是所操作的商品都是事先配置好的商品。天刀的资源配置都是启动时预先加载到共享内存中,运行时上层逻辑模块只能对资源配置数据进行只读操作,如果要操作一个不在版本中的内容就需要提供新增配置并增加资源热加载功能。
全量更新方案
我们有一个主要提供给策划方便调试和测试配置数值用的资源热加载gm指令:通过打gm指令(reloadres 资源id)服务器将读取对应id的资源文件放在一块新申请的内存中。全部读取正确无误后,遍历读取的数据,与老数据key一致的将新数据覆盖回去,老数据里没有的(新增key),追加到该类型资源数据尾部(内存预先已预留一定数量空余)。
这样在debug版本里可以不重启服务器就能快速的应用修改配置内容到游戏逻辑中,满足不停服动态加载修改配置的需求。为了快速实现韩服功能,对这个指令功能做了一些修改:
客户端在线同步热更新
在外网,客户端使用的是打包后的二进制文件(bin格式),因为是全量更新,采用类似在线文件更新的方式。热加载时,给所有在线客户端广播资源类型和对应版本号,客户端比较版本后自行向服务器申请拉取新的打包文件加载后更新,而新登录的客户端可通过tcls下载更新包更新。
增加版本号识别
版本号的作用就是让客户端和服务器知道自己当前使用的资源是不是最新,但之前服务器并没有对每个资源文件单独进行版本号定义,且文件格式(xml)也与客户端(bin)不一致。为了不影响现有的服务器目录组织,我们将要更新的配置文件(包含客户端的bin文件和服务器的xml文件)单独组织到一个新的目录下,并将xml文件和bin文件各自的md5码放到一个配置文件中,通过md5码来辨别版本。
增加服务器间数据同步
在服务器端,要保证所有服务器进程对热加载的资源数据保持一致性。我们在已有的两阶段提交事务功能上衍生出了分布式事务提交:当输入gm指令后,由world向相关进程发送热加载请求,并等待回复。若所有进程都回复成功则再次向所有相关进程提交确认,若有一个进程回复失败或在超时时没有等到所有进程回复则向所有相关进程发送取消请求。
输入gm指令后,服务器到更新目录下加载更新配置,分别获得服务器文件和客户端文件对应的md5。对于服务器文件,直接比较运行目录下xml文件和更新目录下xml文件的md5码是否一致,不一致则重新读取配置,所有服务器加载成功后将客户端的md5广播给所有在线玩家,客户端比较不一致后自行向服务器申请拉取更新文件数据。
这个方案经过一番测试,我们又发现了新的问题:
1. 天刀在外网运营一段时间后进过了数个版本的优化,为了节省内存占用,将资源配置由各个进程独立加载修改为了同一台tc机器上进程共享。在一个进程进行资源的更新时,其他的进程对资源的使用会出现数据错乱。
2. Md5实际并不能标示一个文件的最新版本,客户端bin文件可能包含其他逻辑,因为纯客户端bug而导致对bin文件的更新会使md5码与服务器不一致,再次导致热更新的文件变动情况复杂化。
3. 部署复杂。例如上一条情况,QQ号买号平台且仅仅是配置的更新每次都需要客户端参与打包,给出md5码,并由运维更新配置目录。
因此我们需要一个更轻量的,影响范围更小,操作更便捷的方案。
增量更新方案
再从需求出发,运营想要的更新,主要是为了新增或修改某几条数据,大部分数据没有变化,而客户端希望我们只将变化的配置数据直接通过游戏内协议告知以方便修改,运维则要求更新步骤越简单越好。
综合考虑,我们将全量更新改成了增量更新:将要修改和要增加的配置单独配在新的xml文件中,服务器只用加载和同步这部分变化的数据。资源加载时不直接修改原生配置数据,通过修改配置索引达到增加和修改资源数据的目的。
天刀的资源管理器类似如下格式:
服务器启动时将资源数据加载到data数组中,并根据key值创建索引key->index,运行时线上逻辑根据索引取配置数据(数据地址+index*size)。
增量更新模式下,资源内存组织如下图:
索引被分成了多份的数组形式,并增加了追加数据数组。根据使用索引i来决定使用哪一份索引和追加数据。
热加载时,增量资源被加载到追加数据(索引= (i+1)%n)中,并同原始数据一起参与创建新索引。创建索引时,如果遇到增量数据中的key与原始数据中冲突的,取增量数据的index以达到覆盖原数据的目的:
索引创建好并通过资源检查后,直接切换使用索引i=i+1,使线上逻辑访问到不同的资源索引数组达到可以访问新的追加数据的目的。
最后更新流程修改如下:
增量更新带来的好处是:
加载的数据量小,同步量也小,部署上仅需更新一个内容简单的xml配置。
由于读写分离,无缝切换,共享资源的进程间不需要加锁。
回滚简单,只需把追加索引回滚即可。