ActorModel学习(一)--Gateway网关服设计
本文主要参考Everything you wanted to know about the Actor Model but might have been afraid to ask, 本义是讲解actor模型的基本方面,了解actor模型背后的关键思想及其主要前提
Actor模型是一种计算的数学理论,基于Actors的概念。Actor是计算的基本单元,体现为三件事:
- 信息处理
- 存储
- 通信
系统由多个Actor组成,系统的运作可以表现为Actor之间的通信。Actor之间的相互交互是Actor之间发送messenger(也是一个Actor)。看到这里有同学可能会疑惑,这和OOD有什么区别。其实存在一个区别,Actor之间的便捷是不可变的,也就是Actor不能讲可修改的引用传递给另一个actor,方便actor对其修改。Actor计算模型中唯一的时间就是target(发送目标actor)接收到messenger。
每个actor都有地址 / address。actor有自己的mailbox,表示某条消息的目的地。
注意:OnReceive调用传入的是自己的actor上下文
基于Actor模型的分布式游戏后台架构
如何依托Actor模型架构高并发高可用伸缩性强的游戏分布式后端,这是本文谈论的重点。能够落实实践的模型才是好模型,在实践中相信读者才能够更加深刻的理解Actor的精髓,理解为什么Actor天生适合游戏后台开发。
Gateway 网关服
所有后台服务器都需要网关,这里是客户端向后台通信的起点。为适配多种类型的客户端,网关面向的连接不局限于某种协议。为实现这一特性,需要把连接的功能解耦出来。
这里抽象为Connector接口
1
2
3
4
5
type Connector interface {
func Init(*Config) // 初始化自身结构、网关会在启动时初始化连接体
func OnReceive() // actor
func OnCreateClientAgent(*gin.Context) // 连接到来创建ClientAgent
}
在我的设想中,网关面对客户端大致会处理三种类型的消息,建立连接消息、用户请求消息、断开连接消息。
连接器接受连接后,对这个连接我希望把他的读写抽象成两个Actor,读Actor和写Actor。写Actor是读Actor的子Actor,为什么呢,因为读Actor是一个for循环,能够实时监听连接是否断开,而写Actor无法知道连接是否断开。所以在读Actor断开时,写Actor就会自动跟随读Actor被释放。这样就实现自动管理读写Actor的生命周期。
读写Actor的分离的好处是读写并发,两个actor的消息处理不受相互影响,不会相互阻塞。
这里来设计一下网关的消息路由结构。
这里可以先去看看本系列的第二篇,先了解下消息协议如何设计的。我们的分布式游戏后台不同的节点统一通过Message来通信。Message的类型表示这条Message的目的地,比如某条Message的type是MT_ROOM,说明网关需要把这条消息发送给room节点。对于MT_GATE的消息,说明是其他节点发向网关,网关需要将其通过writer发送给客户端。
网关如何识别消息从哪个writer中发出去呢?每一条用户连接都会产生一个固定的session,这个session是reader actor和writer actor的name,同时这个session会保存在message中。网关解析到session就知道是哪个writer actor。这样也避免了网关记录writer actor ref带来的数据竞争的问题。
Router
Router Actor在设计上是无状态的,所有可以写个Router Group Actor,下面有多个Actor负责转发消息。Router的转发路线仅包括两个方向:
- socket -> system
- system ->socket
也就是系统内部各节点的消息路由不由Router负责,需要在自己的节点负责转发。