提要
本文介绍网络游戏服务器中常用的组件:网关。主要讨论网关的作用,与网关相关的服务器架构设计,及我们项目中的具体实现(Go 语言 HTTP)。
网关的作用
1. 维持连接
我第一次接触网关的时候做的是 MMORPG 游戏,当时的服务器架构是分场景的,即每个 game 维护一个或多个游戏场景。而且 MMORPG 一般来说用的是 TCP 长连接做消息通信,于是当玩家从一个场景切进另一个场景时需要断开与前一个服务器的连接,再去连接后一个服务器,这时候如果网络不通畅,常常就掉线了。
引入网关做代理可以很好的解决了这个问题。客户端启动后连接的是网关,网关再去连接场景服务器并转发所有消息包。切换场景时客户端与网关的连接是不断的,由网关负责去连接新的场景,因为网关和游戏服务器通常在同一局域网内,所以掉线的问题大大改善了。
2. 串并转换
网关可以更进一步增加串并转换的功能。网关和每个 game 只维护单一连接,将多个客户端的并发连接转为串行方式发往 game。这样做的好处是客户端切换场景时网关不用重新建立 TCP 连接了,可靠性更高。
另外 game 处理消息的逻辑可以更加简洁,不用处理 I/O 复用,因为所有的消息来自单个 TCP 连接(单网关)或者固定数量的几个 TCP 连接(多网关的情况)。
3. 权限控制
在多进程的网络游戏架构中,game 除了要处理客户端的消息,同时还会处理其他进程的消息,比如账号、GM 工具等。出于安全原因,一定需要把客户端的消息隔离开来加以限制。
一种方法是考虑进程开启后不同的服务监听不同的 ip 和端口,内部使用的服务只绑定内网 ip,从而起到隔离的效果。
我们采用的另一种做法是使用网关来做权限控制,直接将客户端的请求限制在 HTTP 的某个路径下,这样一来所有的服务器进程都只监听单一 ip 和端口,简化了架构设计。
4. 运维方便
游戏服务器经常需要开服、合服,还有硬件故障等原因导致不得不做服务器迁移。如果 game 直接对客户端提供服务,服务发生变更时往往需要客户端退出重新走登录流程。如果使用网关,在服务器内部发生地址变更时只需要保证网关得到更新并发起重连,客户端甚至根本觉察不到。
部署时可以把所有网关绑定至同一个域名,于是客户端连接服务器时通过统一的域名即可,不用关心具体的 game 服务器地址。同时网关可以很轻松地动态增减,也有了简单的网络负载均衡功能。
5. 省钱
增加新的网关进程意味着我们需要购买更多的服务器,怎么会省钱呢?
因为将外网带宽这种比较昂贵的资源集中管理可以使其利用率更高,自然就达到省钱的效果了。
以我们使用的阿里云 ECS 为例,外网带宽作为一种资源单独收费,目前(2015 年 3 月)价格列举如下,其中带主机费用指的是如果使用独立主机(选择最低配置)运行网关来提供网络接入功能对应的价格:
带宽 | 带宽费用(元/月) | 单价(元/M/月) | 带主机费用(元/月) | 带主机单价(元/M/月) |
---|---|---|---|---|
1M | 23 | 23 | 55 | 55 |
2M | 46 | 23 | 78 | 39 |
3M | 71 | 23.67 | 103 | 34.33 |
4M | 96 | 24 | 128 | 32 |
5M | 125 | 25 | 157 | 31.4 |
6M | 205 | 34.17 | 237 | 39.5 |
7M | 285 | 40.71 | 317 | 45.29 |
… | … | … | … | … |
注意两点:1. 带宽的单价不是固定不变的,单台主机需要的带宽越大,单价越高。2. 如果使用独立主机接入外网,选择 5M 带宽单价最便宜。
假设我们每台 game 服务器需要 0.5M 带宽,共计 20 台。如果不使用网关,那么需要给 20 台 game 配置 1M 带宽,总费用是 23*20=460 元/月;而使用网关的情形,我们配置 2 台 5M 的独立服务器,总费用是 157*2=314元/月。
再假设随着游戏功能增加,用户量活跃度增大,每台 game 需要的带宽升至 1.2M。如果不使用网关,那么只能将每台 game 的带宽升为 2M,总费用是 46*20=920元/月;使用网关的情形下,我们需要增加 2 台 5M、1 台 4M 的独立服务器,总费用是 157*4+128=756元/月,而且扩容的过程只做添加操作,更加简单。
设计与实现
我们的游戏项目使用的是 HTTP 短连接,引入网关的主要作用是做权限控制和方便运维。
如图所示,我们定义了两种不同的网关,gate 用来提供客户端连接服务,admin-gate 是仅限内部使用的,用来做服务器的维护监控等,例如运营人员做 GM 操作。
所有的 gate 都绑定在同一域名下,根据客户端提供的用户 id 把消息转发到对应的 game 服务器上,并对客户端能调用的接口做好权限控制。
admin-gate 不做权限控制,能访问内网的所有服务,但是需要输入用户名和动态密码才允许使用,我们引入了 TOTP 来保证安全。
其他进程如 game、GM 等都运行在内网环境中。我们还运行了 etcd 来做服务发现,当服务迁移等操作时,gate 能立刻感知变化并把消息转发到新的 ip/端口。
Go 语言的 httputil 包里已经有了一份比较完善的 http proxy 实现,我把它稍稍封装了一下,使其可以更方便地在运行时更新 host->addr 映射,代码放在 Github:huangml/gate。监听 etcd 并更新配置的部分因为和我们游戏的具体配置相关,所以现在没有开源。