关于MMO游戏服务器的思考

实现一个MMO游戏框架需要考虑的问题

游戏服务器与web服务器的不同之处

游戏服务器说到底,最重要的几点就是保持状态低延迟交互。这几点就让游戏服务器和普通的服务器(比如电商后端)区分开来了

游戏服务器特有的

保持连接:游戏一般来说需要保持一个客户端到服务端的连接,可以对客户端的玩家的行为(移动,攻击,操作,互动,聊天)进行及时的反馈以及主动推送给相关的玩家。所以游戏更多的使用TCP来保持客户端和服务端的连接,少量游戏会使用UDP或者HTTP。
保持状态:服务端会保持一份玩家的实体,当玩家进行操作时,下次通信的数据会依赖之前的通信的数据。状态就存在内存,偶尔会接受redis,但mysql等是绝对不可以的接受的。
主动推送:游戏服务器由于保持连接和状态,任何数据的改动可以通过服务端主动通知客户端的方式,这样就只需要推送修改的数据。不需要客户端频繁去刷新。
低延迟:很多游戏服务器,尤其是 RPG,MOBA, FPS等游戏,对延迟容忍度非常低,网络拥堵情况下tcp协议由于重传机制,拥塞机制导致非常慢,就需要重新设计协议来处理。
写频繁:游戏中的每一个操作都可能是一个数据,移动、攻击、交易、经验增长等等,所以游戏通常来说需要定时写数据,否则可能会有DB性能瓶颈。
互动:很多游戏是多人游戏,需要玩家互动,这个时候需要保持任何玩家之间都能及时的互动或者沟通,对服务器架构上的设计就有一定的要求,互联网的很多业务分离的微服务架构,很难让所有的玩家能及时的沟通。
复杂度高:由于游戏大多数是交互的,所以做游戏后端开发的业务复杂度比互联网是高的,互联网主要是增删改查(高并发下也挺复杂,但是主要是性能复杂度)。但是游戏却是业务复杂度。比如,你做一个战斗系统,回合制(梦幻西游)的战斗系统怎么做,怎么保存交互的状态,Moba(王者荣耀)的战斗系统怎么做,怎么处理每一帧的指令。

WEB服务器特有的

请求响应:互联网应用一般只需要支持请求响应式的通信,最常用的协议是HTTP来做客户端和服务端的通信。互联网应用一般来说只用关心自己的行为,而不太关注其他用户的行为(即使关注,也没有ms级别的响应要求),所以一般来说即使需要读到其他用户的数据,只需要刷新一下页面就好,也不需要服务端实时推送。
无状态:互联网的应用一般是无状态的,也就是每次进行不同的操作,都需要服务端进行完整的数据读取操作,无法利用上次请求的数据。
服务拆分:互联网是比较容易做服务拆分的,因为业务相对独立,交互弱,做成微服务架构,依赖关系用网络请求来请求数据。一个很长的调用链拿到所有数据。
读频繁:互联网一般是一个读频繁的场景,需要大量的读取数据,只有特定的行为才需要写入。要考虑一定的缓存机制,对数据库的设计要求更高。
延迟容忍:可以容忍一定的延迟,100ms和10ms对用户的体验影响并不大。
增删改查:互联网说到底就是增删改查。(当然1000用户的增删改查和10亿用户的增删改查不一样)。我现在处理十亿级数据千万级QPS的服务。主要考虑高并发下的服务的拆分,架构的设计,微服务化,数据拆分,缓存设计,异步存储,热点处理等等一系列高并发下的性能考虑。但是仅仅业务本身的复杂度相对游戏较低低。

有状态与无状态的服务

有状态服务和无状态服务的区别

有状态无状态服务是两种不同的服务架构,两者的不同之处在于对于服务状态的处理。服务状态是服务请求所需的数据,它可以是一个变量或者一个数据结构。无状态服务不会记录服务状态,不同请求之间也是没有任何关系;而有状态服务则反之。对服务器程序来说,究竟是有状态服务,还是无状态服务,其判断依据——两个来自相同发起者的请求在服务器端是否具备上下文关系

无状态服务

无状态请求,服务器端所能够处理的数据全部来自于请求所携带的信息,无状态服务对于客户端的单次请求的处理,不依赖于其他请求,处理一次请求的信息都包含在该请求里。最典型的就是WEB服务器。每次HTTP请求和以前都没有啥关系,只是获取目标URI。得到目标内容之后,这次连接就被杀死,没有任何痕迹,通过cookie保存token的方式传输请求数据。也可以理解为Cookie是通过客户端保持状态的解决方案。

有状态服务

有状态服务则相反,服务会存储请求上下文相关的数据信息,先后的请求是可以有关联的。例如,在Web 应用中,经常会使用Session 来维系登录用户的上下文信息。虽然http 协议是无状态的,但是借助Session,可以使http服务转换为有状态服务, 另外就是游戏服务器, 这次由10级升到11级了, 明天再登录上来是11级而不是10级, 这个等级就是一个状态。

总结

有状态服务需要维护大量的信息和状态,在性能方面要稍逊于无状态服务器;无状态服务在处理简单服务方面有优势,服务之间没有联系,易于扩展,但处理复杂任务需要额外的组件来协助

并发模型

并发模型, 参考之前的文章关于进程/线程/协程以及一些相关概念的整理中的小章节会有专门说明.

开源游戏服务器框架

  • skynet (c++ lua)
  • ET (c# 双端开发)

游戏服务器中经常使用的库

  • 网络库 Netty系列/各种语言的标准net包
  • 日志库 log4相关/各种语言的标准log包
  • 通讯协议与序列化 protobuf/json, 游戏行业推荐protobuf因为传输的数据少或使用纯二进制
  • 内存计算
  • 数据持久化 redis/mysql/mongodb
  • 消息队列
  • 内存管理,
  • 全局唯一GUID, 方便合服,道具追查
  • 状态机(重要,全称有限状态机), 强化角色的状态, 前置状态的检查校验, 游戏开发过程中,各种游戏状态的切换无处不在。但很多时候,简单粗暴的if else加标志位的方式并不能很地道地解决状态复杂变换的问题
  • 数据包操作, 合并, 同一帧内的数据包进行合并,减少IO操作次数, 校验数据包,防止非法链接, 频率控制, 防止外挂
  • 开关控制, 各个模块的开关控制, 方面开关功能
  • 资源监控, 监控内存/cpu/网络io/包/各种池
  • 热更新
  • 内存缓存
  • http服务提供运营数据支撑

client和server交互/server与server交互

后端服务之间的交互来说就不一样了,后端服务之间的RPC调用可能会传输大量数据,如果全部用纯文本的形式来表示数据那么不管是网络带宽还是性能可能都会差强人意。

在这种场景下,Json并不是最好的选项,主要原因之一就在于性能以及数据的体积。当然如果后端之间的数据交互量实时性不是那种极致的要求, json还是一个非常好的选择.

那么有没有二进制的编码方法吗?答案是肯定的,这就是当前互联网后端中流行的protobuf

例如: 服务端要传递一个id=42, 使用json{"id":43}9字节, 使用protobuf082b2字节, 那么就意味着更少的带宽更快的解析.

protobuf介绍, 一般在游戏服务框架中实现

首先说明一点: protobuf和json其实一样, 都是一种序列化和反序列化工具, json序列化json字符串, protobuf序列化二进制.

从本质上讲,protobuf被编码后形成一系列的key-value,每个key-value对应一个proto中的字段。

讨论游戏逻辑服务器为什么很少微服务化

为什么游戏公司的server不愿意微服务化?

其他资源

游戏服务器架构:游戏服务器架构设计进化
游戏开发-技术图谱
游戏服务器开发技术总结

此处评论已关闭