Actor 模型介绍
在高并发环境中,为了保证多个进程同时访问一个对象时的数据安全,我们通常采用两种策略,共享数据和消息传递,
使用共享数据方式的并发编程面临的最大的一个问题就是数据条件竞争(data race)。处理各种锁的问题是让人十分头痛的一件事,锁限制了并发性, 调用者线程阻塞带来的浪费,用的不好,还容易造成死锁。
和共享数据方式相比,消息传递机制最大的优点就是不会产生数据竞争状态(data race),除此之外,还有如下一些优点:
-
事件模型驱动–Actor之间的通信是异步的,即使Actor在发送消息后也无需阻塞或者等待就能够处理其他事情
-
强隔离性–Actor中的方法不能由外部直接调用,所有的一切都通过消息传递进行的,从而避免了Actor之间的数据共享,想要观察到另一个Actor的状态变化只能通过消息传递进行询问
-
位置透明–无论Actor地址是在本地还是在远程机上对于代码来说都是一样的
-
轻量性–Actor是非常轻量的计算单机,单个Actor仅包含一个 actorId 和 channel 对象,只需少量内存就能达到高并发
本代码Actor模型主要基于swoole协程的channel来实现,进程间通过协程版 unix domain socket 进行通信,当然Actor不仅仅局限于单个节点上,也可以作为分布式集群运行。
github地址: https://github.com/caohao-php/ycsocket (已经去掉Actor,改成了全协程方案)
本框架 Actor 模型借鉴自EasySwoole 框架的Actor模块。 github网址: https://github.com/easy-swoole/actor ,做了些修改后融入本框架
基本原理
Actor模型=数据+行为+消息
Actor模型内部的状态由它自己维护即它内部数据只能由它自己修改(通过消息传递来进行状态修改),Actor由状态(state)、行为(Behavior)和邮箱(mailBox)三部分组成
-
状态(state):Actor中的状态指的是Actor对象的变量信息,状态由Actor自己管理,避免了并发环境下的锁和内存原子性等问题
-
行为(Behavior):行为指定的是Actor中计算逻辑,通过Actor接收到消息来改变Actor的状态
-
邮箱(mailBox):邮箱是Actor和Actor之间的通信桥梁,邮箱内部通过FIFO消息队列来存储发送方Actor消息,接受方Actor从邮箱队列中获取消息
Actor的基础就是消息传递
git源码剖析
我们框架中的示例代码,是一个多人竞技的游戏服务器,代码中有3个 Actor : RoomLogic 、 PkLogic 、 GameLogic,分别用于存储所有房间的游戏大厅、单个房间逻辑、房间内每个玩家的游戏逻辑,还有一个非 Actor 类 AiLogic ,用于处理AI玩家逻辑,代码都存在于 application/logic 目录中。
Actor的注册:
//Application.php
function register_actor() {
Actor::getInstance()->register(RoomLogic::class, 1);
Actor::getInstance()->register(PkLogic::class, 1);
Actor::getInstance()->register(GameLogic::class, 1);
}
每一个Actor对于其他的Actor来说都是封闭的,他们拥有自己的变量,依附于一个特殊的进程ActorProcess,不同进程的Actor通过协程版unix domain socket 通讯,访问请求会被写入Actor信箱(channel),也就是说Actor本身是进程安全的。
每个Actor拥有一个唯一的id,所有进程对Actor的访问,都是通过该id来确定Actor的进程位置,然后发送消息来访问Actor,所有Actor在使用之前都需要注册,注册主要是初始化Actor名称、进程数、启动回调函数、销毁回调函数、定时任务等信息。
在注册完成之后,我们将为每个Actor都创建对应的依附进程。并将进程挂到 swoole 服务器下。
$ws = new swoole_server("0.0.0.0", 9508, SWOOLE_PROCESS, SWOOLE_SOCK_TCP | SWOOLE_SSL);
Actor::getInstance()->attachToServer($ws);
Actor的创建与信箱的监听
Actor本质上是一个类,所有Actor都继承自ActorBean,该父类保存每个Actor的唯一编号actorId,和一些操作这些Actor对象的方法,比如创建Actor对象的new静态方法。
//ActorBean.php
class ActorBean {
protected $actorId;
public static function new(...$args);
public static function getBean(string $actorId);
public function exist();
public function bean();
function onDestroy(...$arg);
function getThis();
function setActorId($actorId);
function getActorId();
}
//RoomLogic.php 单例
class RoomLogic extends ActorBean {
private static $instance;
public function __construct() {
}
public static function getInstance() {
if (!isset(self::$instance)) {
global $roomActorId; //通过一个全局变量在共享内存中存储 RoomLogic 的 ActorId
$actorIdArray = $roomActorId->get("RoomActorId");
if