游戏服务器架构
公司有几套游戏服务器,各有优缺点。本文将其中一套服务器架构大概阐述下,算是做一个知识记录吧,尽管它有很多缺点,设计上存在硬伤,但支持公司目前乃至未来几年的业务量还是充分足够了。
总体架构如下图:

服务器内部总共有:网关、登陆、广场、游戏、中心、DB代理、Web代理 这几个进程。
工具类则有守护、监控、管理中心 三个进程。配套系统则是网站后台。
1,网关服
网关作为外网和内网的隔离层,直面玩家(一般情况下,整套服务器除网关外,会部署在同一个局域网内)。客户端保存 url ,访问时通过域名解析到网关。客户端保存了端口范围,自己随机选取一个端口进行连接。此处用到了一个动态DNS的负载均衡处理,同一个域名配置多个IP,DNS服务有自动轮询算法,返回给客户端对于的IP访问。
网关主动连接中心服,获取登陆、广场、游戏节点的ip、端口信息,并连接。当玩家连接网关后,消息包根据功能派发到不同节点中去处理。网关对玩家是有多少玩家就有多少条TCP连接,但是对内部服务器节点则是各自一条连接。
2,登陆服
玩家登陆,网关将登陆消息交给登陆服处理。登陆服务器通过DB代理访问数据库检验玩家登陆,成功则生成一个唯一key,返回给客户端。客户端拿这个key进入游戏大厅。该key的生存周期则是该玩家本次登陆登出的时间段。
若玩家手机号注册,那么登陆服处理时,会将短信命令部分交由中心服转发给Web代理,由Web代理发送给网站后台,网站后台处理短信命令,通过运营商发短信到玩家手机上。
3,广场服
广场服负责整个游戏大厅的业务(弹窗、公告、活动、任务、商城、玩家资料、背包、游戏管理、签到、抽奖······)。此服是整个数据中心,在线玩家所有的数据都保存在这个进程中,其他服若有相关数据变动会先同步到广场,再由广场同步回去。
广场会管理所有的游戏服,当玩家进入游戏房间时,广场服会在同类GameServer中选取一个人数最少让客户端进入。
4,游戏服
游戏服只负责玩家游戏业务处理(房间、桌子、椅子、机器人、游戏模块),当玩家在游戏服中发生金币、物品、钻石变化时,游戏服会将信息通知广场,广场处理完毕,将数据同步到游戏服和客户端。
玩家进入游戏服时也是从广场拿数据过去。
5,中心服
所有的服务节点都会连接中心服,中心服会保存所有节点的ip和端口。根据不同节点的需求,将信息下发,然后给个节点根据各自功能和其他节点连接。
在无直连节点间互相通信则通过中心服转发完成。
整个游戏的配置信息都保存在中心服中,由中心派发给广场、游戏服。
6,DB代理
直连数据库,所有节点的DB数据请求皆通过DB代理处理。DB代理根据配置将数据请求分发到相应的队列中(不同的业务划分、优先级划分,让DB代理通过线程池创建大量的线程,每个线程对应一个队列,处理对应类型的数据请求)。
从数据库拿取数据后,发生给对应的节点。
7,Web代理
Web代理作为一个服务器组和外部网站(网站后台)通信的桥梁,功能单一,为内部服务器服务。
8,网站后台
网站后台主要负责:游戏官网、游戏资源(静态、动态)、客户端支付回调、内部人员操作后台(客服、运维、运营、统计)。
当运营/运维人员通过浏览器修改了游戏配置后,网站后台将配置信息记录在数据库中,然后发消息通知Web代理,Web代理通知中心服有ID为X的配置更新了,中心服则去数据库中拉取对应配置(XML),然后解析(struct),成功后根据配置类型同步给广场服/游戏服。
这套服务的优点
1,内存控制
服务器将内存分为两种,一种固定大小,一种运行过程中动态申请的内存。
整套服务器中几乎95%的内存都是抽象成固定大小的内存,每一个服务模块需要的内存大小都在服务器启动的时候申请好,该服务运行过程中一直维护自己的那块内存。
动态申请的内存(例如变长消息包)服务器会用一个档案管理器将其管理起来,增加引用数量属性,让这块内存可以进行重复利用,而不是申请使用完后就直接释放掉。
还有一些对象泚的控制。
整个服务器中能启动时申请的内存全都在启动时申请分配好。
在整套服务器运行过程中,内存是比较稳定的。
2,广播控制
广播分两种全服广播和组播,在组播的业务上层可分为给符合条件的玩家广播和给不符合条件的玩家广播。
全服广播比较简单,广场只用发一条消息,同时给所有的网关,网关给所有TCP连接发消息。
组播则是服务节点在消息上加上需要广播的玩家的id,组成一个TCP长包,发给所有网关(这里有拆包和组包的操作),每个网关遍历玩家id将消息发给对于玩家。
整个广播的过程,服务器内部都是一条消息,高效且有效的控制内存(有的服务器广播多少人就直接在服务中给玩家发消息,内存直接猛涨)。真正进行广播处理的是网关节点,直面玩家。
3,逻辑和数据分离
进程模型概况大致如下,以广场为例:
每个业务插件都是独立的动态库(DLL),并且内部使用的内存大小固定。插件初始化时向框架持有的内存管理器申请内存空间,这一整块内存则由内核去维护。
所有插件持有的只是指向数据的指针,而不是数据本身。进而卸载插件后,该内存块仍然保留在管理器中,当在加载插件时,插件拿到指向自己数据的指针,获取自己的数据,保证数据不丢失。
从而当某个插件逻辑有BUG的时候,只需要将修复好的插件重新替换老插件即可,轻松实现服务器C++代码的热更新。(注:两个版本数据块大小不能变)
关于数据的管理和序列号反序列化,有空在写。
今天把事情做完了写博客被老板看见,觉得我太闲了,把我拉到办公室谈了一个多小时。。。。。。

浙公网安备 33010602011771号