PHP下用Swoole实现Actor并发模型的方法


Posted in PHP onJune 12, 2019

什么是Actor?

Actor对于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/easyswoole

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持三水点靠木。

PHP 相关文章推荐
php的字符串用法小结
Jun 08 PHP
php下通过curl抓取yahoo boss 搜索结果的实现代码
Jun 10 PHP
PHP 第二节 数据类型之字符串类型
Apr 28 PHP
通过缓存数据库结果提高PHP性能的原理介绍
Sep 05 PHP
解析Win7 XAMPP apache无法启动的问题
Jun 26 PHP
基于preg_match_all采集后数据处理的一点心得笔记(编码转换和正则匹配)
Jan 31 PHP
PHP利用header跳转失效的解决方法
Oct 24 PHP
Yii框架在页面输出执行sql语句以方便调试的实现方法
Dec 24 PHP
php写入数据到CSV文件的方法
Mar 14 PHP
[原创]php集成安装包wampserver修改密码后phpmyadmin无法登陆的解决方法
Nov 23 PHP
PHP中trait使用方法详细介绍
May 21 PHP
PHP使用Redis实现防止大并发下二次写入的方法
Oct 09 PHP
PHP面向对象类型约束用法分析
Jun 12 #PHP
PHP面向对象程序设计__tostring()和__invoke()用法分析
Jun 12 #PHP
php实现微信公众号创建自定义菜单功能的实例代码
Jun 11 #PHP
php转换上传word文件为PDF的方法【基于COM组件】
Jun 10 #PHP
php简单计算权重的方法示例【适合抽奖类应用】
Jun 10 #PHP
PHP7实现和CryptoJS的AES加密方式互通示例【AES-128-ECB加密】
Jun 08 #PHP
php使用scandir()函数扫描指定目录下所有文件示例
Jun 08 #PHP
You might like
dede3.1分页文字采集过滤规则详说(图文教程)续二
2007/04/03 PHP
php array_map array_multisort 高效处理多维数组排序
2009/06/11 PHP
在CentOS上搭建LAMP+vsftpd环境的简单指南
2015/08/01 PHP
学习php设计模式 php实现观察者模式(Observer)
2015/12/09 PHP
Laravel多域名下字段验证的方法
2019/04/04 PHP
php源码的使用方法讲解
2019/09/26 PHP
jQuery.Autocomplete实现自动完成功能(详解)
2010/07/13 Javascript
javascript学习笔记(十九) 节点的操作实现代码
2012/06/20 Javascript
jquery的trigger和triggerHandler的区别示例介绍
2014/04/20 Javascript
JS实现模拟风力的雪花飘落效果
2015/05/13 Javascript
使用AngularJS创建自定义的过滤器的方法
2015/06/18 Javascript
Javascript页面跳转常见实现方式汇总
2015/11/28 Javascript
jQuery基本过滤选择器用法示例
2016/09/09 Javascript
React实现双向绑定示例代码
2016/09/19 Javascript
jQuery复合事件结合toggle()方法的用法示例
2017/06/10 jQuery
jQuery 表单序列化实例代码
2017/06/11 jQuery
微信小程序自定义模态对话框实例详解
2017/08/16 Javascript
JavaScript基础教程之如何实现一个简单的promise
2018/09/11 Javascript
vue 音乐App QQ音乐搜索列表最新接口跨域设置方法
2018/09/25 Javascript
傻瓜式vuex语法糖kiss-vuex整理
2018/12/21 Javascript
Vue中使用matomo进行访问流量统计的实现
2019/11/05 Javascript
vue项目中定义全局变量、函数的几种方法
2019/11/08 Javascript
基于脚手架创建Vue项目实现步骤详解
2020/08/03 Javascript
Python备份Mysql脚本
2008/08/11 Python
Python的Django框架中的Context使用
2015/07/15 Python
Python利用Beautiful Soup模块修改内容方法示例
2017/03/27 Python
Python小进度条显示代码
2019/03/05 Python
NOTINO英国:在线购买美容和香水
2020/02/25 全球购物
JAVA代码查错题
2014/10/10 面试题
扬尘污染防治方案
2014/06/15 职场文书
2014年世界艾滋病日演讲稿
2014/11/28 职场文书
污水处理保证书
2015/05/09 职场文书
七年级作文之雪景
2019/11/18 职场文书
MySQL 全文索引使用指南
2021/05/25 MySQL
Spring boot应用启动后首次访问很慢的解决方案
2021/06/23 Java/Android
详解apache编译安装httpd-2.4.54及三种风格的init程序特点和区别
2022/07/15 Servers