教你从头写游戏服务器框架

由于“越通用的代码,就是越没用的代码”,所以在设计之初,我就认为应该使用分层的模式来构建整个系统 。按照游戏服务器的一般需求划分,最基本的可以分为两层:

  1. 底层基础功能:包括通信、持久化等非常通用的部分,关注的是性能、易用性、扩展性等指标 。
  2. 高层逻辑功能:包括具体的游戏逻辑,针对不同的游戏会有不同的设计 。

教你从头写游戏服务器框架

文章插图
 
我希望能有一个基本完整的“底层基础功能”的框架,可以被复用于多个不同的游戏 。由于目标是开发一个 适合独立游戏开发 的游戏服务器框架 。所以最基本的需求分析为:
功能性需求
  1. 并发:所有的服务器程序,都会碰到这个基本的问题:如何处理并发处理 。一般来说,会有多线程、异步两种技术 。多线程编程在编码上比较符合人类的思维习惯,但带来了“锁”这个问题 。而异步非阻塞的模型,其程序执行的情况是比较简单的,而且也能比较充分的利用硬件性能,但是问题是很多代码需要以“回调”的形式编写,对于复杂的业务逻辑来说,显得非常繁琐,可读性非常差 。虽然这两种方案各有利弊,也有人结合这两种技术希望能各取所长,但是我更倾向于基础是使用异步、单线程、非阻塞的调度方式,因为这个方案是最清晰简单的 。为了解决“回调”的问题,我们可以在其上再添加其他的抽象层,比如协程或者添加线程池之类的技术予以改善 。
  2. 通信:支持 请求响应 模式以及 通知 模式的通信(广播视为一种多目标的通知) 。游戏有很多登录、买卖、打开背包之类的功能,都是明确的有请求和响应的 。而大量的联机游戏中,多个客户端的位置、HP 等东西都需要经过网络同步,其实就是一种“主动通知”的通信方式 。
  3. 持久化:可以存取 对象。游戏存档的格式非常复杂,但其索引的需求往往都是根据玩家 ID 来读写就可以 。在很多游戏主机如 PlayStation 上,以前的存档都是可以以类似“文件”的方式存放在记忆卡里的 。所以游戏持久化最基本的需求,就是一个 key-value 存取模型 。当然,游戏中还会有更复杂的持久化需求,比如排行榜、拍卖行等,这些需求应该额外对待,不适合包含在一个最基本的通用底层中 。
  4. 缓存:支持远程、分布式的对象缓存 。游戏服务基本上都是“带状态”的服务,因为游戏要求响应延迟非常苛刻,基本上都需要利用服务器进程的内存来存放过程数据 。但是游戏的数据,往往是变化越快的,价值越低,比如经验值、金币、HP,而等级、装备等变化比较慢的,价值则越高,这种特征,非常适合用一个缓存模型来处理 。
  5. 协程:可以用 C++ 来编写协程代码,避免大量回调函数分割代码 。这个是对于异步代码非常有用的特性,能大大提高代码的可读性和开发效率 。特别是把很多底层涉及IO的功能,都提供了协程化 API,使用起来就会像同步的 API 一样轻松惬意 。
  6. 脚本:初步设想是支持可以用 Lua 来编写业务逻辑 。游戏需求变化是出了名快的,用脚本语言编写业务逻辑正好能提供这方面的支持 。实际上脚本在游戏行业里的使用非常广泛 。所以支持脚本,也是一个游戏服务器框架很重要的能力 。
  7. 其他功能:包括定时器、服务器端的对象管理等等 。这些功能很常用,所以也需要包含在框架中,但已经有很多成熟方案,所以只要选取常见易懂的模型即可 。比如对象管理,我会采用类似 Unity 的组件模型来实现 。
非功能性需求
  1. 灵活性:支持可替换的通信协议;可替换的持久化设备(如数据库);可替换的缓存设备(如 memcached/redis);以静态库和头文件的方式发布,不对使用者代码做过多的要求 。游戏的运营环境比较复杂,特别是在不同的项目之间,可能会使用不同的数据库、不同的通信协议 。但是游戏本身业务逻辑很多都是基于对象模型去设计的,所以应该有一层能够基于“对象”来抽象所有这些底层功能的模型 。这样才能让多个不同的游戏,都基于一套底层进行开发 。
  2. 部署便利性:支持灵活的配置文件、命令行参数、环境变量的引用;支持单独进程启动,而无须依赖数据库、消息队列中间件等设施 。一般游戏都会有至少三套运行环境,包括一个开发环境、一个内测环境、一个外测或运营环境 。一个游戏的版本更新,往往需要更新多个环境 。所以如何能尽量简化部署就成为一个很重要的问题 。我认为一个好的服务器端框架,应该能让这个服务器端程序,在无配置、无依赖的情况下独立启动,以符合在开发、测试、演示环境下快速部署 。并且能很简单的通过配置文件、或者命令行参数的不同,在集群化下的外部测试或者运营环境下启动 。


    推荐阅读