MMO 微服务架构思路推演
传统的 MMO 架构有很多局限性,主要有以下几点:
- 承载量有限
- 基于这种架构上, BigWord 无缝世界实现非常复杂
- 无法融入
微服务
这种更先进架构中
因此,本人提出一种 MMO 微服务架构,并以此架构为基础,借 MMO 典型玩法,来推演这种架构是否符合要求
并解决上述 3 个问题
架构图
+---------------+
+-------->+ Base N +----------------------+
| +-------+-------+ |
| ^ |
| | v
+---------------+ +---------------+ | +-------+-------+ +------+--------+
| Client N +------------>+ Gateway N +-------------->+ Game N +-------------->+ Redis |
+---------------+ +---------------+ | +-------+-------+ +------+--------+
| | ^
| | |
| v |
| +-------+-------+ |
+-------->+ AOI N +----------------------+
+---------------+
各服务主要职能:
服务名 | 职能 | 典型玩法 | 特点 |
---|---|---|---|
Base | 玩家个人玩法 | 角色属性系统 背包系统 装备系统 |
无状态,仅逻辑 使用协程方式,同步操作 Redis 存档数据 |
AOI | 场景 AOI 管理 | AOI 系统 | 无状态,仅逻辑 负责某个世界场景的分片 AOI |
Game | 地图场景中玩法 | 打怪/PK 拾取/丢弃 装备/卸下 |
无状态,仅逻辑 使用协程方式,同步访问 Base、 AOI 使用协程方式,同步操作 Redis 存档数据 有短暂的战斗串行化协程(后续介绍) |
Gateway | 转发消息 | 转发个人玩法消息 转发 AOI 消息 |
内存中维护客户端网络会话 |
Redis | 各玩法存档数据 游戏对象 AOI 数据 ROLEID - GATEWAYID ROLEID - BATTLE_GAMEID |
- | - |
服务定位:
- Base 简单微服务,提供基础玩法服务接口
- AOI 简单微服务,只提供 AOI 相关服务接口
- Game 场景玩法微服务,Base 、 AOI 作为上游服务,供其使用;并向玩家提供相关场景中玩法服务
MMO 典型玩法一: 角色属性系统/背包系统/装备系统
Base 服上实现
思路同 PHP 编程,抽象设计好系统接口。每个接口实现,直接协程同步操作 Redis
同时 Base 可以提供 RPC 接口,供 Game 调用
Base 不需要 ROLEID - GATEWAYID/GAMEID 信息,哪个 Gateway/Game 发来的请求,原路返回即可
MMO 典型玩法二: AOI 系统
AOI 服上实现
AOI 系统,功能包括:
- 游戏对象场景中创建、移动、销毁
- 获取某块区域内游戏对象信息
- 管理游戏对象视野。进入视野、离开视野、在视野中移动
- 场景中射线检测、碰撞检测
传统 MMO 架构中,通常这些功能都是会在称为Cell
服务上
Cell
服主要有个问题: AOI 系统、战斗系统是关联在一起的,并与 Cell
服也捆绑在一起
即 角色战斗数据 、 AOI 数据,在 2 个相邻Cell
服边缘需要特殊处理:
- 比如角色战斗数据需要跨 Cell 同步
- 比如无缝世界还需要主从切换等
因此,需要把战斗数据/战斗逻辑与AOI数据/AOI逻辑分开
AOI 服只处理AOI数据/AOI逻辑;AOI数据不需要跨服同步,只做些冗余即可
主要实现原理如下:
- 将整个世界分片,每片对应一个 AOI 服(或者双备下,开2个),以下简称分片 AOI 服
- 分片 AOI 服 Redis 中维护各自 X 、 Y 、Z 3 个轴的 zset 数据(以及AOI相关的玩家数据如 roleid - hmap(speed/face/state) )
- 分片 AOI 服冗余周围9格分片的部分 X Y Z 信息
- AOI 算法使用十字链表法
具体实现逻辑要点:
功能 | 操作 |
---|---|
游戏对象场景中创建、移动、销毁 | 分片 AOI 服直接对 Redis X 、 Y 、Z 3 个轴的 zset 数据做更新 |
获取某块区域内游戏对象信息 | 十字链表法从 Redis 中获取某块区域内游戏对象信息 |
管理游戏对象视野。进入视野、离开视野、在视野中移动 | Redis 保存角色ID-视野信息 快照创建、移动、销毁请求时,获取某块区域内游戏对象信息 计算事件 保存新 角色ID-视野信息 快照 |
场景中射线检测、碰撞检测 | 获取某块区域内游戏对象信息;再做射线检测、碰撞检测 |
性能分析,主要瓶颈如下:
- 某个分片的上 Redis 上有各自的 3 个 zset , 对这 3 个 zset 的存取是一个分片 AOI 服的 TPS 上限
- redis 对同一个 KEY 的 TPS 范围在: 5w-50w/TPS,一般 10w+/TPS
- 分片 AOI 服 CPU 消耗点是:
管理游戏对象视野。进入视野、离开视野、在视野中移动
与场景中射线检测、碰撞检测。5w-50w/TPS 下 CPU 未到达 100% ,不考虑双备 1 个进程即可;否则按 CPU 满的情况,多开
关于 AOI 服的更新问题
文中把 AOI 服定义为完全无状态,因此移动中的玩家,状态更新,只能依赖于客户端定时发送移动事件来驱动。
这样做好处是 AOI 完全无状态,最简单。
如果希望 AOI 服务自己主动刷新移动中的玩家,则无法做到完全无状态,必须内存中维护玩家列表,以及位置、速度信息;并做定时 Update
另外考虑到 AOI 服务每秒定时更新的次数是有限的,通常 20 - 30 帧左右,那么也可以考虑直接 Redis 中维护一个 hmap,保存玩家列表,以及位置、速度信息
这样依然可以让 AOI 服,完全无状态
MMO 典型玩法三: 打怪/PK、拾取/丢弃、装备/卸下
Game 服上实现
有了上游服务 Base、 AOI 服,打怪/PK、拾取/丢弃、装备/卸下
就是些逻辑组合了
注意:Game 上是无场景概念的,只有各种逻辑;即 某个 Game 服不会只处理某场景上的逻辑,除非刻意为之
一、 打怪/PK
打怪/PK
要点:
- Game 是无状态的,每次战斗逻辑,都会去 Redis 中存取相关数据,类似 PHP 做法
- 从玩家进入战斗状态,到脱离战斗状态,因为会涉及到多个玩家或怪物。因此需要申请ID号,并交给某个 Game 处理
- 玩家进入战斗状态时,先 Redis 中获取ID号(无创建之),然后他的战斗消息都会投递给同个 Game
- 玩家战斗中,主攻攻击/被击游戏对象玩家等都会关联到同个ID号上,直到脱离战斗状态为止
- 本质上就是一个虚房间的概念
二、 拾取/丢弃
- Game RPC AOI 请求创建或销毁游戏对象
- Game RPC Base 背包系统
三、装备/卸下
- Base 装备/卸下
- Redis 中战斗属性数据更新 (看代码实现,假设战斗相关功能都在 game 上,则查看战斗ID号,如果有投递给相应 Game)
动态分片
由于分片只处理 AOI 数据,实现动态分片的难度也简单了。
把世界画成完全二叉树
的正方形2叉分割图。
可以把问题简化为完全二叉树
的节点分割与合并的算法基础上,维护下 AOI 数据
细节:
- 动态分片与物理地图资源分片,需要协调好。 2 者不一定要一致
- 物理地图资源分片必须必动态分片要大