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

五邑隐侠,本名关健昌,12年游戏生涯。 本教程以Go语言为例。

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

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

上一篇:Android list刷新后仍然定位到原来的位置,解决。


下一篇:App崩溃前上传数据或者操作UI iOS