go语言游戏服务端开发(一)——架构

五邑隐侠,本名关健昌,12年游戏生涯。 本教程以Go语言为例。
 
网络游戏程序分为客户端和服务端。客户端负责图形渲染、交互和一些简单校验处理,服务端负责业务逻辑处理、数据存储。
我们开发一个游戏demo,服务端程序可以是一个单线程的服务进程。它包含网络通信、业务逻辑处理、数据存储。服务端打开网络端口监听,客户端通过网络连接到服务端,服务端接入连接。客户端发包给服务端,服务端接收到包后进行解析,调用对应的处理程序进行处理,处理程序处理成功后,修改数据并保存下来,再把响应包封包发送给客户端。
0
简单的数据存储可以保存在文件里。当用户量逐渐增加,数据存储的性能、完整和安全要求逐渐增大,数据存储改为存储到数据库。这样服务端就分为服务进程、数据库进程两个进程。服务进程跟数据库进程通信,进行数据读写。由于游戏的实时要求比较高,如果每个请求都要读写数据库,当大量用户同时访问时,数据库读写成了服务性能的瓶颈。因此,一般游戏都做缓存,简单的缓存可以缓存在服务进程的内存里,业务处理直接操作缓存数据,每隔一段时间(例如10分钟),用个独立线程把被修改的缓存数据保存到数据库。
0
这样会有一个问题,就是一旦服务进程宕机,在保存间隔时间里的游戏数据就会丢失掉。特别是游戏服务进程有更新上线时,稳定性还没有被线上并发验证,宕机的几率会增加,数据丢失的风险也会增加。为了减轻风险,可以考虑把数据缓存跟服务进程分离。使用共享内存,或者使用第三方的专业缓存程序(memcached、redis)作缓存。这样数据丢失的风险就大大降低了。
0
随着游戏业务开发,游戏业务不再是简单的请求、返回,它还有一系列推送需求,如聊天、邮件系统,这类需求可能是全员推送,这会占用服务进程的CPU时间,导致请求响应变慢。而且这种推送时效性要求没有游戏其他业务高,因此可以把这种广播类型的推送抽离出来放到一个单独的广播进程,服务进程通过向广播进程发送广播消息到广播队列,广播进程从广播队列获取消息进行广播。这样又产生了新的问题,客户端需要连接到两个服务端的进程。为了解决这个问题,可以增加网关进程,客户端只需要连接到网关进程,由网关进程代替客户端将请求发送到服务进程进行处理,服务进程、广播进程的响应/推送进入网关,由网关确定转发给哪个客户端连接。
0
这样服务端就变成了5个独立进程的进程组,接下来对数据库和数据缓存做下技术选型,可以选择业界用得比较多的mysql数据库、redis缓存。利用redis的list结构做消息队列建立起服务进程和广播进程的通信。
0
这些进程都部署在一台机器上,所以目前服务端的处理能力受限于一台机器的硬件性能。当然我们也可以将这5个进程分散到多台机器上,如果玩家人数再增加,可以把网关进程、服务进程和广播进程集群,redis、mysql改成分布式缓存和分布式数据库,这样也能扩容。更通用的做法是对游戏分服,每个分服的数据互相隔离。这是目前市面上大多数游戏的做法,滚服运营也已经是比较成熟的运营手段。由这5个进程组成的进程组,1个分服对应一个进程组,一个进程组部署在同一台机器上。这样通过分服就可以横向支持更多的玩家并发访问。
这样又出现了新的问题,客户端怎么知道该连接哪个分服。而且现在的游戏,同一个玩家可以在不同的分服进行游戏,以达到好友同玩一个服。一般的,在玩家进入分服前,让玩家先进行全局登录,然后根据游戏类型,要么下发分服列表由玩家自己选择分服,要么给玩家指定分服(例如类似COC的所谓不分服游戏,这个分服可以理解为一个节点)。增加一个全局的账号进程,负责游戏的全局登录、下发分服信息。账号进程还负责支付回调,进行订单确认和发货。
0
随着游戏运营推广,分服数量越来越多,游戏迭代也需要对服务进行维护,靠人工维护分服列表显得越来越笨拙。应该建立一个自动化的机制,当开新分服,或者分服进入维护的时候,自动更新分服列表。在这里可以通过服务注册发现的机制来实现自动化。利用一个全局的redis作为注册中心,所有分服的信息通过服务编号作为字段,都保存在redis的一个hash结构里,key为gamesrv。当开新分服,或者切换分服状态时,分服的服务进程向redis更新自己所在分服的信息,然后通过订阅发布机制,发布gamesrv通道的消息,参数为更新的服务编号。账号进程在玩家全局登录时,只需要把redis里key为gamesrv的hash返回给客户端就可以。
0
还有一种情况是,分服宕机了,这个时候需要修改分服状态为维护中,避免玩家进入这个分服。而且redis里分服的信息也应该落地固化,这样就算redis重启了也不丢失。这里可以利用redis的固化机制保存数据。增加一个管理进程,redis数据有变更就调用一次redis的bgsave命令。当然也可以搭建一个注册进程,提供类似redis的订阅发布机制,以及zookeeper的连接监听、树形数据保存、落地固化。管理进程与所有分服的服务进程保持连接,当分服宕机时,管理进程知道对应分服的连接断开,则修改redis里对应分服的信息状态为维护中,这样管理进程也起到监控作用。管理进程除了参与服务的注册发现,还提供对服务和玩家进行管理和操作的服务。基于redis建立消息队列(list结构),账号进程支付确认后向消息队列发消息,管理进程获得消息后,通知具体的分服进行虚拟道具发货。
 
0
账号进程处理全局登录,那就要有一个账号的数据库用来保存玩家的账号信息,分服可以通过管理进程把分服创角、角色更新信息保存到账号数据库,以便玩家选择分服时知道各分服角色的信息。玩家进行全局登录是短暂的一次请求,所以用http短连接来增加访问量。账号进程支持集群,这样需要有对外统一的访问地址。管理进程也提供一些http请求给管理后台网页对服务和玩家进行管理和操作。增加一个http网关提供统一的地址访问,可以用业界普遍使用的nginx作为http网关。
0
对于轻中度游戏,游戏的通信量不会很多,没必要每个分服都有一个长连接socket网关。假设一个分服同时连接服务器的客户端有5k,一台机器的socket网关能支持5w个玩家。这样可以把网关独立出来,一台机器部署两个socket网关,每个socket网关都可以进入任意一个分服。这样子可以通过加减机器横向伸缩socket网关,把网络流量成本集中在几台网关机器上降低成本。这样子账号进程就需要知道网关列表和负载情况,以通过负载均衡给玩家返回合适的连接网关。每个网关也要监听各个分服的状态变化,确定是否连接对应的分服的服务进程、广播进程。因此网关需要参与服务的注册发现。
0
这样一个服务端的架构就基本出来了。从图中可以看出,最大单点风险是管理进程。可以通过守护进程让管理进程崩溃自动重启(守护进程作为父进程创建、等待作为子进程的管理进程退出后,如果没有维护标记则重新创建子进程),提高整个服务的健壮性。
产品上线后还要有数据统计后台,数据来源可以通过账号进程、服务进程运行时生成日志到日志文件,由脚本分析日志上报给统计数据库。统计后台可以作为一个单独的项目去做建表、上报、生成报表。
服务端架构就介绍到这里,接下来聊一下网络通信。
posted @ 2021-09-16 17:26  五邑隐侠  阅读(682)  评论(2编辑  收藏  举报