服务器热更新的讨论

我们之前的服务器是多进程纯C#架构的服务器。最近游戏上线,遇到有时候需要线上修bug的问题。之前对代码热更新预料不足,导致在线上出了一些bug时非常被动,往往需要重启服务器解决问题,影响前期的体验。我们的游戏类型是一个RPG卡牌游戏,操作偏向于单机向,大量依赖于广播的操作比较少。

服务器热更新的讨论

这周的时间里,也考虑了几种方案,下面对比下思考的结果。

嵌入动态脚本

首先,C#+python或者lua的游戏服务器解决方案没有成功实例。现在最有名的ulua和xlua都是基于unity引擎的,在服务器的性能和承载上没有无法验证。没有成熟框架情况下,脚本和非脚本分层等如何来设定,还是需要比较多的设计和开发时间的。

其次,程序员使用脚本语言进行编程比用C#进行敏捷编程效率会差,vs等工具对于效率、正确性提升的红利会消失。此外,也是公司自己的原因,整体转变带来了不可预估的时间成本,以后的项目使用lua开发正确性能不能保障,会不会出现更多的bug?

其他的一些问题。比如一些自动化的代码(配置表,网络数据发送)往往需要在宿主语言和脚本语言上都实现一份等。这些工作会带来不少额外的工作量。

mono代码热更新

因为最终服务器是运行在linux环境下,用mono运行的,而mono是完全开源的。曾经有一位163的牛逼同事,实现了这么一套热更新的方案。方案原理是可以在运行时让mono把内存中的xxx.exe的某一些代码片段改掉,或是把入口重定向掉。

这种方法可以用来修一些bug,但很难去做一些小型新功能的支持。因为原型仅仅可以支持代码片段IL代码的改写,没法支持对新类型的操作等。

另一个缺点是,部分语法比较难以替换,比如泛型函数,闭包访问hold住的对象等,这些在虚拟机层面实现也是比较复杂的语法,在做mono热更新时就会是一些坑点的。如果我们可以完美支持mono热更新,需要了解很多底层语法的机制,那开发量可能与重写一个mono处于一个量级。

最后一个缺点是,热更新操作不如lua热更新直观。需要先编译出新的xxx.exe,然后通过工具生成出新老exe的il差,并导出成mono可以识别的文本。最后,线上触发mono去加载这个文本进行替换。而这里的步骤在lua热更新中,只需要简单的把错误的xxx.lua替换一次就可以了,我们的操作明显复杂。

实现无状态服务器

这一点主要是因为我们期望的游戏服务器类型是一个比较单机化的类卡牌游戏,才有可能这么去实现一个服务器架构。无状态服务器的最大优点是,逻辑和存储分离。也就是逻辑处理服务器只有处理器,没有状态,有点类似于actor模型;而存储服务器(可以是redis,也可以自己实现)则之提供存储和读取数据等简单稳定接口,保障存储服务器的稳定,就没有对存储服务器进行热更新的需求了。

这种框架之下,逻辑处理服务器就可以比较随意的重启了,以此实现了基于冷更新的服务器更新功能,但是玩家并不会感受到游戏更新或是断开。这种情况下,同时运行的几个服务器进程在某段时间内可能是不同版本的,可以实现一个版本逐步更替的过程。

服务器热更新的讨论

这个方案会带来一些复杂性

  • 需要一个调度服务器(CONTROLSV)调度新的LOGICSV替代旧的服务器的过程,所以这个服务器需要维护已有的服务器组。
  • 需要拆分出一个能够配合存储流程的转有存储节点STORESv
  • LOGICSv的逻辑代码,当需要访问和存储数据时,原本只需要访问内存对象,现在需要异步访问。增加了代码编写的复杂度。
  • LOGICSv节点因为没有数据,如果要发起一些主动的update操作,也要异步去db取数据再改写。更严重的是,在操作大量数据时(比如对所有在线玩家update),会造成内部io和访问延迟比较大,并可能造成和玩家线程存取数据的冲突。