五邑隐侠,本名关健昌,12年游戏生涯。 本教程以Go语言为例。
网络游戏程序分为客户端和服务端。客户端负责图形渲染、交互和一些简单校验处理,服务端负责业务逻辑处理、数据存储。
我们开发一个游戏demo,服务端程序可以是一个单线程的服务进程。它包含网络通信、业务逻辑处理、数据存储。服务端打开网络端口监听,客户端通过网络连接到服务端,服务端接入连接。客户端发包给服务端,服务端接收到包后进行解析,调用对应的处理程序进行处理,处理程序处理成功后,修改数据并保存下来,再把响应包封包发送给客户端。
简单的数据存储可以保存在文件里。当用户量逐渐增加,数据存储的性能、完整和安全要求逐渐增大,数据存储改为存储到数据库。这样服务端就分为服务进程、数据库进程两个进程。服务进程跟数据库进程通信,进行数据读写。由于游戏的实时要求比较高,如果每个请求都要读写数据库,当大量用户同时访问时,数据库读写成了服务性能的瓶颈。因此,一般游戏都做缓存,简单的缓存可以缓存在服务进程的内存里,业务处理直接操作缓存数据,每隔一段时间(例如10分钟),用个独立线程把被修改的缓存数据保存到数据库。
这样会有一个问题,就是一旦服务进程宕机,在保存间隔时间里的游戏数据就会丢失掉。特别是游戏服务进程有更新上线时,稳定性还没有被线上并发验证,宕机的几率会增加,数据丢失的风险也会增加。为了减轻风险,可以考虑把数据缓存跟服务进程分离。使用共享内存,或者使用第三方的专业缓存程序(memcached、redis)作缓存。这样数据丢失的风险就大大降低了。
随着游戏业务开发,游戏业务不再是简单的请求、返回,它还有一系列推送需求,如聊天、邮件系统,这类需求可能是全员推送,这会占用服务进程的CPU时间,导致请求响应变慢。而且这种推送时效性要求没有游戏其他业务高,因此可以把这种广播类型的推送抽离出来放到一个单独的广播进程,服务进程通过向广播进程发送广播消息到广播队列,广播进程从广播队列获取消息进行广播。这样又产生了新的问题,客户端需要连接到两个服务端的进程。为了解决这个问题,可以增加网关进程,客户端只需要连接到网关进程,由网关进程代替客户端将请求发送到服务进程进行处理,服务进程、广播进程的响应/推送进入网关,由网关确定转发给哪个客户端连接。
这样服务端就变成了5个独立进程的进程组,接下来对数据库和数据缓存做下技术选型,可以选择业界用得比较多的mysql数据库、redis缓存。利用redis的list结构做消息队列建立起服务进程和广播进程的通信。
这些进程都部署在一台机器上,所以目前服务端的处理能力受限于一台机器的硬件性能。当然我们也可以将这5个进程分散到多台机器上,如果玩家人数再增加,可以把网关进程、服务进程和广播进程集群,redis、mysql改成分布式缓存和分布式数据库,这样也能扩容。更通用的做法是对游戏分服,每个分服的数据互相隔离。这是目前市面上大多数游戏的做法,滚服运营也已经是比较成熟的运营手段。由这5个进程组成的进程组,1个分服对应一个进程组,一个进程组部署在同一台机器上。这样通过分服就可以横向支持更多的玩家并发访问。
这样又出现了新的问题,客户端怎么知道该连接哪个分服。而且现在的游戏,同一个玩家可以在不同的分服进行游戏,以达到好友同玩一个服。一般的,在玩家进入分服前,让玩家先进行全局登录,然后根据游戏类型,要么下发分服列表由玩家自己选择分服,要么给玩家指定分服(例如类似COC的所谓不分服游戏,这个分服可以理解为一个节点)。增加一个全局的账号进程,负责游戏的全局登录、下发分服信息。账号进程还负责支付回调,进行订单确认和发货。
随着游戏运营推广,分服数量越来越多,游戏迭代也需要对服务进行维护,靠人工维护分服列表显得越来越笨拙。应该建立一个自动化的机制,当开新分服,或者分服进入维护的时候,自动更新分服列表。在这里可以通过服务注册发现的机制来实现自动化。利用一个全局的redis作为注册中心,所有分服的信息通过服务编号作为字段,都保存在redis的一个hash结构里,key为gamesrv。当开新分服,或者切换分服状态时,分服的服务进程向redis更新自己所在分服的信息,然后通过订阅发布机制,发布gamesrv通道的消息,参数为更新的服务编号。账号进程在玩家全局登录时,只需要把redis里key为gamesrv的hash返回给客户端就可以。
还有一种情况是,分服宕机了,这个时候需要修改分服状态为维护中,避免玩家进入这个分服。而且redis里分服的信息也应该落地固化,这样就算redis重启了也不丢失。这里可以利用redis的固化机制保存数据。增加一个管理进程,redis数据有变更就调用一次redis的bgsave命令。当然也可以搭建一个注册进程,提供类似redis的订阅发布机制,以及zookeeper的连接监听、树形数据保存、落地固化。管理进程与所有分服的服务进程保持连接,当分服宕机时,管理进程知道对应分服的连接断开,则修改redis里对应分服的信息状态为维护中,这样管理进程也起到监控作用。管理进程除了参与服务的注册发现,还提供对服务和玩家进行管理和操作的服务,管理进程与账号进程保持连接,账号进程支付确认后可以通过管理进程通知具体的分服进行虚拟道具发货。
账号进程处理全局登录,那就要有一个账号的数据库用来保存玩家的账号信息,分服可以通过管理进程把分服创角、角色更新信息保存到账号数据库,以便玩家选择分服时知道各分服角色的信息。玩家进行全局登录是短暂的一次请求,所以用http短连接来增加访问量。账号进程支持集群,这样需要有对外统一的访问地址。管理进程也提供一些http请求给管理后台网页对服务和玩家进行管理和操作。增加一个http网关提供统一的地址访问,可以用业界普遍使用的nginx作为http网关。
对于轻中度游戏,游戏的通信量不会很多,没必要每个分服都有一个长连接socket网关。假设一个分服同时连接服务器的客户端有5k,一台机器的socket网关能支持5w个玩家。这样可以把网关独立出来,一台机器部署两个socket网关,每个socket网关都可以进入任意一个分服。这样子可以通过加减机器横向伸缩socket网关,把网络流量成本集中在几台网关机器上降低成本。这样子账号进程就需要知道网关列表和负载情况,以通过负载均衡给玩家返回合适的连接网关。每个网关也要监听各个分服的状态变化,确定是否连接对应的分服的服务进程、广播进程。因此网关需要参与服务的注册发现。
这样一个服务端的架构就基本出来了。从图中可以看出,最大单点风险是管理进程。可以通过守护进程让管理进程崩溃自动重启(守护进程作为父进程创建、等待作为子进程的管理进程退出后,如果没有维护标记则重新创建子进程),提高整个服务的健壮性。
产品上线后还要有数据统计后台,数据来源可以通过账号进程、服务进程运行时生成日志到日志文件,由脚本分析日志上报给统计数据库。统计后台可以作为一个单独的项目去做建表、上报、生成报表。
服务端架构就介绍到这里,接下来聊一下网络通信。
- go语言游戏服务端开发(三)——服务机制
五邑隐侠,本名关健昌,12年游戏生涯. 本教程以Go语言为例. P2P网络为服务进程间.服务进程与客户端间通信提供了便利,在这个基础上可以搭建服务. 在服务层,通信包可以通过定义协议号来确定该包怎 ...
- go语言游戏服务端开发(二)——网络通信
一.网络层 网络游戏客户端除了全局登录使用http请求外,一般通过socket长连接与服务端保持连接.go语言的net包提供网络socket长连接相关操作. 对于服务端,一般经历 Listen.Acc ...
- go语言游戏服务端开发(四)——RPC机制
五邑隐侠,本名关健昌,12年游戏生涯. 本教程以Go语言为例. RPC指远程方法调用,游戏里引入RPC目的是降低跨进程交互的复杂度. 游戏业务设计为多go routine,一个玩家一个go routi ...
- 转: 基于netty+ protobuf +spring + hibernate + jgroups开发的游戏服务端
from: http://ybak.iteye.com/blog/1853335 基于netty+ protobuf +spring + hibernate + jgroups开发的游戏服务端 游戏服 ...
- 从架构师视角看是否该用Kotlin做服务端开发?
前言 自从Oracle收购Sun之后,对Java收费或加强控制的尝试从未间断,谷歌与Oracle围绕Java API的官司也跌宕起伏.虽然Oracle只是针对Oracle JDK8的升级收费,并释放了 ...
- 为什么多数游戏服务端是用 C++ 来写
早年开发游戏必须用C++,这没得说,2000-2004年,java还没有nio,其他动态语言不抗重负,只能C/C++能开发出完整可用的游戏服务端.直到2005年,韩国的游戏很多都还是纯C++写服务端, ...
- 俯瞰 Java 服务端开发
原文首发于 github ,欢迎 star . Java 服务端开发是一个非常宽广的领域,要概括其全貌,即使是几本书也讲不完,该文将会提到许多的技术及工具,但不会深入去讲解,旨在以一个俯瞰的视角去探寻 ...
- 游戏服务端中使用Servlet和Java注解的一个好设计
SNS类游戏基本都是使用HTTP短连接,用Java来开发服务端时能够使用Servlet+Tomcat非常轻松的架构起服务端来.在这里介绍一种使用Servlet比較好的一种设计,我也见过非常多基于HTT ...
- Day01_搭建环境&CMS服务端开发
学成在线 第1天 讲义-项目概述 CMS接口开发 1 项目的功能构架 1.1 项目背景 受互联网+概念的催化,当今中国在线教育市场的发展可谓是百花齐放.如火如荼. 按照市场领域细分为:学前教育.K12 ...
随机推荐
- Java基础——JavaDoc生成文档
JavaDoc生成文档 package Top1; /** * @author lwt * @version 1.0 * @since 1.8 * */ public class ...
- 重定向">" 及">>"符号的简单运用
1.command > file 将输出重定向(可理解写入)到 file. 2.command >> file 将输出以追加的方式重定向到 file,也就是写入到 file的尾端. ...
- Apache虚拟web主机构建
目录 一.构建虚拟web主机 1.1.虚拟web主机概述 二.搭建虚拟web主机步骤 2.1.基于域名搭建虚拟主机 ①为虚拟主机提供域名解析 ②为虚拟主机准备网页文档 ③添加虚拟主机配置 ④设置访问路 ...
- STM32—驱动RFID-RC522模块
文章目录 一.S50(M1)卡介绍 1.S50(M1)卡基础知识 2.内部信息 3.存取控制 4.数据块的存取控制 5.控制块的存取控 6.工作原理 7.M1与读卡器的通信 二.RC522工程代码详解 ...
- 使用msp432搭建的平衡小车(一)
1.前言 笔者是一名大二学生曾经荒废一年学业,现在不断学习,所以有任何问题都希望讨论提出,你们的支持就是我的动力. 关于硬件搭建的步骤,笔者就不提网上方案太多了,笔者使用编码器电机,驱动采用tb661 ...
- 【AI】Pytorch_LearningRate
From: https://liudongdong1.github.io/ a. 有序调整:等间隔调整(Step),按需调整学习率(MultiStep),指数衰减调整(Exponential)和 余弦 ...
- Android 11(R) Power HAL AIDL简析 -- 基本接口
Android 11(R) Power HAL AIDL将分三篇文章来介绍: Android 11(R) Power HAL AIDL简析 -- 基本接口 Android 11(R) Power HA ...
- idea中导包出现import org.apach.*,提交代码老出现冲突,不想使用.*的设置
打开设置>Editor>Code Style>Java>Scheme Default>Imports ① 将Class count to use import with ...
- centos8安装mysql8.0
官网下载rpm地址 https://dev.mysql.com/downloads/repo/yum/ wget下载 wget https://repo.mysql.com//mysql80-comm ...
- 数据结构--Dijkstra算法最清楚的讲解
迪杰斯特拉(Dijkstra)算法是典型最短路径算法,用于计算一个节点到其他节点的最短路径.它的主要特点是以起始点为中心向外层层扩展(广度优先搜索思想),直到扩展到终点为止 ###基本思想 通过Dij ...