专栏首页PHP饭米粒【swoole4.0】实现Actor并发模型

【swoole4.0】实现Actor并发模型

什么是Actor?

Actor概念来源于Erlang, 对于PHPer来说,可能会比较陌生,写过Java的同学会比较熟悉,Java一直都有线程的概念(虽然PHP有Pthread,但不普及),它是一种非共享内存的并发模型,每个Actor内的数据独立存在,Actor之间通过消息传递的形式进行交互调度,且Actor是一种高度抽象化的编程模型,非常适合于游戏、硬件行业。

Swoole协程与信箱

得益于Swoole4.x,我们可以基于Swoole的协程与Channel快速实现一个信箱模式调度。模拟代码如下:

use Swoole\Coroutine\Channel;
go(function (){
    //创建十个信箱通道
    $mailBoxes = [];
    for ($i = 1;$i <= 10;$i++){
        $mailBoxes[$i] = new Channel(16);
    }
    //模拟master 邮局调度,随机像一个信箱投递消息
    go(function ()use($mailBoxes){
        while (1){
            \co::sleep(2);
            $key = rand(1,10);
            ($mailBoxes[$key])->push(time());
        }
    });
    //模拟actor 实体消费
    for ($i = 1;$i <= 10;$i++){
        go(function ()use($mailBoxes,$i){
            while (1){
                $msg = ($mailBoxes[$i])->pop();
                echo "Actor {$i} recv msg : {$msg} \n";
            }
        });
    }
});

以上代码执行输出:

php test.php 
Actor 8 recv msg : 1559622691 
Actor 10 recv msg : 1559622693 
Actor 1 recv msg : 1559622695 
Actor 5 recv msg : 1559622697 

协程通道每次在POP遇到无数据的时候,都会自动让出执行权(具体可以去看Swoole协程调度)

Actor库

基于上面的原理,我们实行了一个多进程分布的协程Actor库

composer require easyswoole/actor=2.x-dev

我们依赖dev库进行测试,生产可以自己依赖stable版本

进程关系

Easyswoole的Actor模型中,存在两组进程,一组是proxy进程,用来实现Actor对外服务,一组是worker进程,proxy进程与worker进程之间通过unixsock进行通讯,而Actor实例就均匀的分布worker之中。

样例代码

比如在一个聊天室中,我们可以定义一个房间模型。

namespace EasySwoole\Actor\Test;


use EasySwoole\Actor\AbstractActor;
use EasySwoole\Actor\ActorConfig;

class RoomActor extends AbstractActor
{
    public static function configure(ActorConfig $actorConfig)
    {
        $actorConfig->setActorName('Room');
    }
    public function onStart()
    {
        //每当一个RoomActor实体被创建的时候,都会执行该回调
        var_dump('room actor '.$this->actorId().' start');
    }
    public function onMessage($msg)
    {
        //每当一个RoomActor实体收到外部消息的时候,都会执行该回调当
        var_dump('room actor '.$this->actorId().' onmessage: '.$msg);
        return 'reply at '.time();
    }
    public function onExit($arg)
    { 
        //每当一个RoomActor实体退出的时候,都会执行该回调
        var_dump('room actor '.$this->actorId().' exit at arg: '.$arg);
        return 'exit at '.time();
    }
    protected function onException(\Throwable $throwable)
    {
        //每当一个RoomActor出现异常的时候,都会执行该回调
        var_dump($throwable->getMessage());
    }
}

在cli模式下创建一个Actor服务

use EasySwoole\Actor\Actor;
use EasySwoole\Actor\Test\RoomActor;
use EasySwoole\Actor\ProxyProcess;

Actor::getInstance()->register(RoomActor::class);
$list = Actor::getInstance()->generateProcess();

foreach ($list['proxy'] as  $proxy){
    /** @var ProxyProcess $proxy */
    $proxy->getProcess()->start();
}
foreach ($list['worker'] as $actors){
    foreach ($actors as $actorProcess){
        /** @var ProxyProcess $actorProcess */
        $actorProcess->getProcess()->start();
    }
}
while($ret = \Swoole\Process::wait()) {
    echo "PID={$ret['pid']}\n";
}

创建一个cli测试脚本

use EasySwoole\Actor\Actor;
use EasySwoole\Actor\Test\RoomActor;
Actor::getInstance()->register(RoomActor::class);

go(function (){
    $actorId = RoomActor::client()->create('create arg1');
    var_dump($actorId);
    \co::sleep(3);
    var_dump(RoomActor::client()->send($actorId,'this is msg'));
    \co::sleep(3);
    var_dump(RoomActor::client()->exit($actorId,'this is exit arg'));
    \co::sleep(3);
    RoomActor::client()->create('create arg2');
    \co::sleep(3);
    RoomActor::client()->create('create arg3');
    \co::sleep(3);
    var_dump(RoomActor::client()->sendAll('sendAll msg'));
    \co::sleep(3);
    var_dump(RoomActor::client()->status());
    \co::sleep(3);
    var_dump(RoomActor::client()->exitAll('sendAll exit'));
});

以上代码执行结果如下: 服务端

php test.php 
string(40) "room actor 00101000000000000000001 start"
string(57) "room actor 00101000000000000000001 onmessage: this is msg"
string(64) "room actor 00101000000000000000001 exit at arg: this is exit arg"
string(40) "room actor 00101000000000000000002 start"
string(40) "room actor 00103000000000000000001 start"
string(57) "room actor 00101000000000000000002 onmessage: sendAll msg"
string(57) "room actor 00103000000000000000001 onmessage: sendAll msg"
string(60) "room actor 00101000000000000000002 exit at arg: sendAll exit"
string(60) "room actor 00103000000000000000001 exit at arg: sendAll exit"

客户端

php test2.php 
string(23) "00101000000000000000001"
string(19) "reply at 1559623925"
string(18) "exit at 1559623928"
bool(true)
array(3) {
  [1]=>
  int(1)
  [2]=>
  int(0)
  [3]=>
  int(1)
}
bool(true)

更多细节可以在EasySwoole项目官网得到文档支持 http://easyswoole.com/ 喜欢EasySwoole项目的,可以给个star https://github.com/easy-swoole

本文分享自微信公众号 - PHP饭米粒(phpfamily),作者:一丰

原文出处及转载信息见文内详细说明,如有侵权,请联系 yunjia_community@tencent.com 删除。

原始发表时间:2019-06-04

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

我来说两句

0 条评论
登录 后参与评论

相关文章

  • Swoole 2020 :4.5 新版本的规划

    转眼 Swoole 开源项目已经历 8 个年头。这 8 年里,有 116 位开发者为 Swoole 贡献了内核代码。有无数 PHP 开发者为 Swoole 提供...

    桶哥
  • 老李大战PHP之file_put_contents

    本来要发一篇LBS(三)和《浪潮浮生记》,结果有事耽搁了整整两天,想了想拿以前的一篇自认为有价值的文章共享一下

    桶哥
  • 史上最好用,支持最全的PHP性能调优工具来了!

    Swoole Tracker 2.8.3 发布,这个版本开始全面支持 PHP5.4,PHP5.5 和 PHP5.6,做这个兼容之前很多人和我说这是个收益非常低的...

    桶哥
  • yii添加csrf验证

    yii2X版本的请绕行,这里说的是yii 1.1.14版本。某某公司的老程序当中没有考虑csrf攻击,所以不幸中枪了。(具体地址我就不贴了,说下解决方案) 配置...

    苦咖啡
  • 用 Mixin Messenger 机器人接受和发送比特币

    在 上一篇教程中, 我们创建了自动回复消息的机器人,当用户发送消息"Hello,World!"时,机器人会自动回复同一条消息!

    Mixin Network
  • 信息竞赛进阶指南--最小表示法

    一个首位相连的字符串,我们要寻找一个位置,从这个位置向后形成一个新字符串,我们需要使这个字符串字典序最小。

    风骨散人Chiam
  • 做完这些数据可视化,我找出了爆款视频的流行法则

    中国有优酷,美国有“油管”(AKA Youtube)。在这个视频已经成为人们日常网络娱乐消遣的时代,大家一定都很关心这些视频网站都暗藏了哪些秘密?本期数据侠Sh...

    DT数据侠
  • 快速学习Docker和容器技术

    基于浏览器交互式学习Docker和容器 参考:https://www.katacoda.com/courses/docker 部署第一个Docker容器 Do...

    shaonbean
  • 为什么在python中for-range比while运行的要快

    time python test1.py 或者test2.py,得到第一个的时间大概是0m1.189s;第二个的时间是0m0.514s。while循环的时间大概...

    生信编程日常
  • Python模拟登陆 —— 征服验证码 10 知乎(倒立文字验证码)

    知乎的倒立文字验证码 # 登录知乎,通过保存验证图片方式 import urllib.request import urllib.parse import ti...

    SeanCheney

扫码关注云+社区

领取腾讯云代金券