学习游戏服务器编程进阶篇之全球同服技术架构

2017年04月07日 15:24 0 点赞 0 评论 更新于 2025-11-21 21:20

服务器架构技术一直是技术热点,像流行的游戏服务器、各种数据平台系统等都离不开服务器的架构设计。服务器架构设计的优劣直接影响用户对产品的使用体验。在互联网时代,全球用户或玩家之间的距离越来越近,各类平台设计需满足全球用户的使用需求已成为现实,数据共享也成为当前亟待解决的问题。

游戏服务器在这方面的技术已取得突破,例如《COC海盗奇兵》《皇室战争》等游戏,它们采用的就是全球同服架构设计。许多开发者认为搭建全球同服服务器颇具难度,实际上,利用现有的开源技术,我们也能自行搭建一个可满足数万到数十万实时在线玩家的全球同服服务器。下面将为读者逐一介绍搭建全球同服服务器涉及的技术。

全球同服面临的首要问题——时间问题

地球上不同国家存在时差,要实现全球同服,需在服务器端妥善处理这一问题。解决方案是在不同地区部署一组服务器,这些服务器的数据最终要统一到一个数据库中。例如,中国大陆玩家可通过香港服务器登录到美国服务器,这就要求中心服务器架设在国外,所有玩家数据最终在美国服务器实现统一。

在选择布置于世界各地的服务器时,我们选用开源框架 HAProxy 框架服务器,其代码开源,代码地址。下面介绍一下 HAProxy 的功能:

HAProxy 通常应用于 Web 端,即平台架构中。使用 HAProxy、PHP、Redis 和 MySQL 能够支撑每周 10 亿次请求,且扩展性极佳。平台中的架构设计如下:

架构细分

将 Application Layer 层进一步细分,可得到如下架构图。图中使用了 Symfony2、Varnish、Keepalived、Redis。其中,Varnish 是一款高性能且开源的反向代理服务器和 HTTP 加速器,它是开源框架,网上可获取源代码;Keepalived 主要功能是实现真实机器的故障隔离及负载均衡器间的失败切换;Redis 主要用于数据存储。

在全球同服服务器架构中,Varnish 暂不使用,Keepalived 可用于服务器故障切换。若一台服务器发生故障,它会迅速切换到另一台服务器。这里会用到 xinetd 服务来检测端口,HAProxy 用 HTTP 协议检测该端口是否正常。Haproxy 服务器之间的切换图示如下:

数据层技术

我们使用 Redis 和 MySQL 存储所有数据,MySQL 更多作为三级缓存层,而 Redis 是系统的主要数据存储。选用 Redis 的优点如下:

  • 在存储大量数据(约 2.5 亿记录)时不会影响性能。
  • 通常多是基于特定资源的简单 GET 请求,无查找及复杂的 SELECT 操作。
  • 在单请求时尽可能多获取资源以降低延时。

上述技术大多应用于 Web 架构设计,鉴于其优点,Haproxy 主要作为游戏的节点服务器使用,其布局如下:

不同地区的客户端通过这些服务器登录,例如中国大陆玩家可通过香港的 Haproxy 服务器登录,最终通过 gate 服务器在美国汇总。我们的数据库位于美国,中心服务器在国外,图中的服务器作为地区服务器节点使用。

Haproxy 是用 C 语言编写的开源服务器,只能在 Linux 系统运行,感兴趣的读者可深入学习。接下来,我们要通过 gate 服务器将世界各地登录的玩家联系起来。gate 服务器选用 Erlang 语言实现,需注意各个 gate 服务器之间相互连通,这样才能满足全球玩家同服需求。

Erlang 应用广泛,其最大优点是使用方便,很多基础功能已集成到 Erlang 语言中。此前用 C++ 编写服务器时,管理 TCP 连接繁琐,需编写大量代码,底层框架也需编写众多代码,既耗时又易产生 BUG。而使用 Erlang 则方便得多,无需考虑底层细节,只需关注服务器架构和业务逻辑,能让人彻底摆脱底层开发的困扰。可直接使用 Erlang 语言搭建游戏服务器,如下:

使用 Erlang 作为 gate 服务器可实现负载均衡,且 Erlang 语言易于上手。这样,Haproxy 服务器可直接与 Erlang 实现的 gate 服务器连接,gate 服务器会进行负载均衡处理,同时解决世界各地的接入问题。对 Erlang 语言不了解的读者可查阅Erlang 官方网站

游戏服务器底层和游戏逻辑实现

全球同服服务器的网关问题解决后,接下来是游戏服务器底层和游戏逻辑的实现。我们选择了 Skynet 框架,该框架由云风开发维护,实现了一个多线程高并发的在线游戏后台服务框架,提供定时器、并发调度、服务扩展框架、异步消息队列、命名服务等基础能力,支持 Lua 脚本,单服务器支持 10K+ 客户端接入和处理。Skynet 框架如下:

每个在线客户端在 skynet server 上都有一个对应的 socket 与其连接。一个 socket 在 skynet 内部对应一个 lua 虚拟机和一个“客户特定消息队列”(per client mq)。当客户特定消息队列中有消息时,该队列会挂载到全局队列(global message queue)上,供工作线程(worker threads)进行调度处理。

skynet 的服务处理主流程较为简单:一个 socket 线程轮询所有的 socket,收到客户端请求后将请求打包成一个消息,发送到该 socket 对应的客户特定消息队列中,然后将该消息队列挂到全局队列队尾;N 个工作线程从全局队列头部获取 client 特定的消息队列,从客户特定消息队列中取出一个消息进行处理,处理完后将该消息队列重新挂到全局队列队尾。

感兴趣的读者可自行查看 SkyNet 源代码。每个客户处理消息时,按消息到达顺序进行处理。同一时刻,一个客户的消息仅由一个工作线程调度,因此客户处理逻辑无需考虑多线程并发,基本无需加锁。这些消息从 Haproxy 服务器发出,经 Erlang Gate 服务器分发给 Skynet 处理。下面看看 Skynet 的并发调度任务方式:

lua 支持 non - preemptive 的 coroutine,一个 lua 虚拟机中可支持海量并发的协作任务,但 coroutine 主要问题是不支持多核,无法充分利用当前服务器普遍提供的多核能力。所以目前有很多项目为 lua 添加 OS thread 支持,如 Lua Lanes、LuaProc 等,这些项目都需解决并发任务的组成及调度问题。并发任务可用 coroutine 表示:每个 OS 线程上创建一个 lua 虚拟机(lua_State),虚拟机上可创建海量的 coroutine。

数据存储设计

Sky net 架构实现后开始进行数据存储设计,这里使用 MySQL 和 Redis 存储数据,其存储方式前文已介绍。Redis 支持多 key - value 数据库(表),用 RedisDb 表示一个 key - value 数据库(表),不清楚的读者可上网查询。

整个全球同服服务器架构的简易设计如下图所示:

以上实现了全球同服服务器技术架构,可实现全球玩家同服。关于细节,如使用的通信协议、开发逻辑使用 Lua 开发等不再详述。其实,其主要原理是利用 Web 架构设计。若要搭建全球同服服务器,读者需重点掌握 Haproxy 服务器、Erlang 语言、SkyNet 服务器架构和 Lua 脚本的编写,以及利用 Redis、MySql 实现数据存储。需提醒读者,市面上很多开源资源若善加利用,能帮助我们实现诸多目标,国外能做到的,我们同样可以。

作者信息

孟子菇凉

孟子菇凉

共发布了 3994 篇文章