五邑隐侠,本名关健昌,12年游戏生涯。 本教程以Go语言为例。
网络游戏程序分为客户端和服务端。客户端负责图形渲染、交互和一些简单校验处理,服务端负责业务逻辑处理、数据存储。
我们开发
一个游戏demo,服务端程序可以是
一个单线程的服务进程。它包含网络通信、业务逻辑处理、数据存储。服务端打开网络端口监听,客户端通过网络连接到服务端,服务端接入连接。客户端发包给服务端,服务端接收到包后进行解析,
调用对应的处理程序进行处理,处理程序处理成功后,
修改数据并保存下来,再把响应包封包发送给客户端。

简单的数据存储可以保存在
文件里。当
用户量逐渐
增加,数据存储的
性能、完整和安全要求逐渐增大,数据存储改为存储到
数据库。这样服务端就分为服务进程、
数据库进程两个进程。服务进程跟
数据库进程通信,进行
数据读写。由于游戏的实时要求比较高,如果每个请求都要读写
数据库,当大量
用户同时访问时,
数据库读写成了服务
性能的瓶颈。因此,一般游戏都做缓存,简单的缓存可以缓存在服务进程的内存里,业务处理直接操作缓存数据,每隔一段时间(例如10分钟),用个独立线程把被
修改的缓存数据保存到
数据库。

这样会有
一个问题,就是一旦服务进程宕机,在保存间隔时间里的游戏数据就会丢失掉。特别是游戏服务进程有更新上线时,稳定性还没有被线上并发验证,宕机的几率会
增加,数据丢失的风险也会
增加。为了减轻风险,可以考虑把数据缓存跟服务进程分离。使用共享内存,或者使用第三方的专业缓存程序(memcached、re
dis)作缓存。这样数据丢失的风险就大大降低了。

随着游戏业务开发,游戏业务不再是简单的请求、返回,它还有一系列推送需求,如聊天、
邮件系统,这类需求可能是全员推送,这会占用服务进程的
cpu时间,导致请求响应变慢。而且这种推送时效性要求没有游戏其他业务高,因此可以把这种广播类型的推送抽离出来放到
一个单独的广播进程,服务进程通过向广播进程发送广播消息到广播队列,广播进程从广播队列
获取消息进行广播。这样又产生了新的问题,客户端需要连接到两个服务端的进程。为了
解决这个问题,可以
增加网关进程,客户端只需要连接到网关进程,由网关进程代替客户端将请求发送到服务进程进行处理,服务进程、广播进程的响应/推送进入网关,由网关确定转发给哪个客户端连接。

这样服务端就变成了5个独立进程的进程组,接下来对
数据库和数据缓存做下技术选型,可以选择业界用得比较多的
MysqL数据库、re
dis缓存。利用re
dis的list结构做消息队列建立起服务进程和广播进程的通信。

这些进程都部署在一台机器上,所以目前服务端的处理能力受限于一台机器的硬件
性能。当然我们也可以将这5个进程分散到多台机器上,如果玩家人数再
增加,可以把网关进程、服务进程和广播进程集群,re
dis、
MysqL改成分布式缓存和分布式
数据库,这样也能扩容。更通用的做法是对游戏分服,每个分服的数据互相隔离。这是目前市面上大多数游戏的做法,滚服运营也已经是比较成熟的运营手段。由这5个进程组成的进程组,1个分服对应
一个进程组,
一个进程组部署在同一台机器上。这样通过分服就可以横向
支持更多的玩家并发访问。
这样又出现了新的问题,客户端怎么知道该连接哪个分服。而且现在的游戏,同
一个玩家可以在不同的分服进行游戏,以达到好友同玩
一个服。一般的,在玩家进入分服前,让玩家先进行全局
登录,然后根据游戏类型,要么下发分服列表由玩家自己选择分服,要么给玩家指定分服(例如类似COC的所谓不分服游戏,这个分服可以理解为
一个节点)。
增加一个全局的账号进程,负责游戏的全局
登录、下发分服信息。账号进程还负责支付回调,进行订单确认和发货。

随着游戏运营推广,分服
数量越来越多,游戏迭代也需要对服务进行维护,靠人工维护分服列表显得越来越笨拙。应该建立
一个自动化的机制,当开新分服,或者分服进入维护的时候,
自动更新分服列表。
在这里可以通过服务
注册发现的机制来实现
自动化。利用
一个全局的re
dis作为
注册中心,所有分服的信息通过服务编号作为字段,都保存在re
dis的
一个hash结构里,key为gamesrv。当开新分服,或者切换分服状态时,分服的服务进程向re
dis更新自己所在分服的信息,然后通过
订阅发布机制,发布gamesrv通道的消息,参数为更新的服务编号。账号进程在玩家全局
登录时,只需要把re
dis里key为gamesrv的hash返回给客户端就可以。

还有一种情况是,分服宕机了,这个时候需要
修改分服状态为维护中,避免玩家进入这个分服。而且re
dis里分服的信息也应该落地固化,这样就算re
dis重启了也不丢失。这里可以利用re
dis的固化机制保存数据。
增加一个管理进程,re
dis数据有变更就
调用一次re
dis的bgsave命令。当然也可以搭建
一个注册进程,提供类似re
dis的
订阅发布机制,以及zookeeper的连接监听、树形数据保存、落地固化。管理进程与所有分服的服务进程保持连接,当分服宕机时,管理进程知道对应分服的连接断开,则
修改re
dis里对应分服的信息状态为维护中,这样管理进程也起到监控作用。管理进程除了参与服务的
注册发现,还提供对服务和玩家进行管理和操作的服务,管理进程与账号进程保持连接,账号进程支付确认后可以通过管理进程
通知具体的分服进行虚拟道具发货。

账号进程处理全局
登录,那就要有
一个账号的
数据库用来保存玩家的账号信息,分服可以通过管理进程把分服创角、角色
更新信息保存到账号
数据库,以便玩家选择分服时知道各分服角色的信息。玩家进行全局
登录是短暂的一次请求,所以用http短连接来
增加访问量。账号进程
支持集群,这样需要有对外统一的访问地址。管理进程也提供一些http请求给管理
后台网页对服务和玩家进行管理和操作。
增加一个http网关提供统一的地址访问,可以用业界普遍使用的
Nginx作为http网关。

对于轻中度游戏,游戏的通信量不会很多,没必要每个分服都有
一个长连接socket网关。假设
一个分服同时连接服务器的客户端有5k,一台机器的socket网关能
支持5w个玩家。这样可以把网关独立出来,一台机器部署两个socket网关,每个socket网关都可以进入任意
一个分服。这样子可以通过加减机器横向伸缩socket网关,把网络流量成本集中在几台网关机器上降低成本。这样子账号进程就需要知道网关列表和
负载情况,以通过
负载均衡给玩家返回合适的连接网关。每个网关也要监听各个分服的状态变化,确定是否连接对应的分服的服务进程、广播进程。因此网关需要参与服务的
注册发现。

这样
一个服务端的架构就基本出来了。从图中可以看出,最大单点风险是管理进程。可以通过守护进程让管理进程崩溃
自动重启(守护进程作为父进程创建、等待作为子进程的管理进程
退出后,如果没有维护
标记则重新创建子进程),提高整个服务的健壮性。
产品上线后还要有数据
统计后台,数据来源可以通过账号进程、服务进程运行时
生成日志到日志
文件,由脚本分析日志上报给
统计数据库。
统计后台可以作为
一个单独的项目去做建表、上报、
生成报表。
服务端架构就介绍到这里,接下来聊一下网络通信。
版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 [email protected] 举报,一经查实,本站将立刻删除。