Swoole入门到实战(二):进程,内存和协程、Swoole完美支持ThinkPHP5、分发Task异步任务机制实现

本文主要是介绍Swoole入门到实战(二):进程,内存和协程、Swoole完美支持ThinkPHP5、分发Task异步任务机制实现,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

一、进程,内存和协程

1.1 进程

1.1.1 进程

进程就是 正在运行的程序一个实例
$process = new swoole_process(function(swoole_process $pro) {// todo
//     php redis.php$pro->exec("/usr/local/php/bin/php", [__DIR__.'/../server/http_server.php']);
}, false);$pid = $process->start();
echo $pid . PHP_EOL;
//回收结束运行的子进程
swoole_process::wait();

    clipboard.png

    以树状图显示进程间的关系:pstree -p 进程id
    启动成功后会创建worker_num+2个进程。Master进程+Manager进程+serv->worker_numWorker进程

1.1.2 进程使用场景

    clipboard.png

    clipboard.png

管道:进程和进程间的一个桥梁
echo "process-start-time:".date("Ymd H:i:s");
$workers = [];
$urls = ['http://baidu.com','http://sina.com.cn','http://qq.com','http://baidu.com?search=singwa','http://baidu.com?search=singwa2','http://baidu.com?search=imooc',
];
//创建多个子进程分别模拟请求URL的内容
for($i = 0; $i < 6; $i++) {$process = new swoole_process(function(swoole_process $worker) use($i, $urls) {// curl$content = curlData($urls[$i]);//将内容写入管道//    echo $content.PHP_EOL;$worker->write($content.PHP_EOL);}, true);$pid = $process->start();$workers[$pid] = $process;
}
//获取管道内容
foreach($workers as $process) {echo $process->read();
}
/*** 模拟请求URL的内容  1s* @param $url* @return string*/
function curlData($url) {// curl file_get_contentssleep(1);return $url . "success".PHP_EOL;
}
echo "process-end-time:".date("Ymd H:i:s");

1.2 Swoole内存-table详解

内存操作模块之: Table

    swoole_table一个基于共享内存和锁实现的超高性能,并发数据结构
    使用场景:用于解决多进程/多线程数据共享和同步加锁问题
    进程结束后内存表会自动释放

// 创建内存表
$table = new swoole_table(1024);// 内存表增加一列
$table->column('id', $table::TYPE_INT, 4);
$table->column('name', $table::TYPE_STRING, 64);
$table->column('age', $table::TYPE_INT, 3);
$table->create();$table->set('singwa_imooc', ['id' => 1, 'name'=> 'singwa', 'age' => 30]);
// 另外一种方案
$table['singwa_imooc_2'] = ['id' => 2,'name' => 'singwa2','age' => 31,
];$table->decr('singwa_imooc_2', 'age', 2);
print_r($table['singwa_imooc_2']);echo "delete start:".PHP_EOL;
$table->del('singwa_imooc_2');
print_r($table['singwa_imooc_2']);

1.3 协程

    线程、进程、协程的区别
    进程,线程,协程与并行,并发
    并发与并行的区别?

$http = new swoole_http_server('0.0.0.0', 9001);$http->on('request', function($request, $response) {// 获取redis 里面 的key的内容, 然后输出浏览器$redis = new Swoole\Coroutine\Redis();$redis->connect('127.0.0.1', 6379);$value = $redis->get($request->get['a']);// mysql.....//执行时间取它们中最大的:time = max(redis,mysql)$response->header("Content-Type", "text/plain");$response->end($value);
});$http->start();    

二、Swoole完美支持ThinkPHP5(重难点

2.1 面向过程方案

2.1.1 面向过程代码实现

$http = new swoole_http_server("0.0.0.0", 9911);$http->set(['enable_static_handler' => true,'document_root' => "/home/wwwroot/swoole/thinkphp/public/static",'worker_num' => 5,]
);
//此事件在Worker进程/Task进程启动时发生,这里创建的对象可以在进程生命周期内使用
$http->on('WorkerStart', function(swoole_server $server,  $worker_id) {// 定义应用目录define('APP_PATH', __DIR__ . '/../../../../application/');// 加载框架里面的文件require __DIR__ . '/../../../../thinkphp/base.php';
});
$http->on('request', function($request, $response) use($http){//如果在每次请求时加载框架文件,则不用修改thinkphp5源码
//    // 定义应用目录
//    define('APP_PATH', __DIR__ . '/../../../../application/');
//    // 加载框架里面的文件
//    require_once __DIR__ . '/../../../../thinkphp/base.php';/*** 解决上一次输入的变量还存在的问题* 方案一:if(!empty($_GET)) {unset($_GET);}* 方案二:$http-close();把之前的进程kill,swoole会重新启一个进程,重启会释放内存,把上一次的资源包括变量等全部清空* 方案三:$_SERVER  =  []*/$_SERVER  =  [];if(isset($request->server)) {foreach($request->server as $k => $v) {$_SERVER[strtoupper($k)] = $v;}}if(isset($request->header)) {foreach($request->header as $k => $v) {$_SERVER[strtoupper($k)] = $v;}}$_GET = [];if(isset($request->get)) {foreach($request->get as $k => $v) {$_GET[$k] = $v;}}$_POST = [];if(isset($request->post)) {foreach($request->post as $k => $v) {$_POST[$k] = $v;}}//开启缓冲区ob_start();// 执行应用并响应try {think\Container::get('app', [APP_PATH])->run()->send();}catch (\Exception $e) {// todo}//输出TP当前请求的控制方法//echo "-action-".request()->action().PHP_EOL;//获取缓冲区内容$res = ob_get_contents();ob_end_clean();$response->end($res);//把之前的进程kill,swoole会重新启一个进程,重启会释放内存,把上一次的资源包括变量等全部清空//$http->close();
});
$http->start();

    测试:

    clipboard.png

2.1.2 onWorkerStart事件

//此事件在Worker进程/Task进程启动时发生,这里创建的对象可以在进程生命周期内使用
$http->on('WorkerStart', function(swoole_server $server,  $worker_id) {// 定义应用目录define('APP_PATH', __DIR__ . '/../../../../application/');// 加载框架里面的文件require __DIR__ . '/../../../../thinkphp/base.php';
});
Tips:如果修改了加载框架文件,需要重启: php php_server.php

    onWorkerStart:

    此事件在Worker进程/Task进程启动时发生,这里创建的对象可以在进程生命周期内使用
    在onWorkerStart中加载框架的核心文件后:

  1. 不用每次请求都加载框架核心文件,提高性能
  2. 可以在后续的回调事件中继续使用框架的核心文件或者类库

2.1.3 关于再次请求进程缓存解决方案

    当前worker进程没有结束,所以会保存上一次的资源等。解决上一次输入的变量还存在的问题:

  1. 方案一:if(!empty($_SERVER)) { unset($_SERVER); }
  2. 方案二:$http-close();把之前的进程killswoole会重新启一个进程,重启会释放内存,把上一次的资源包括变量等全部清空(php-cli控制台会提示错误)
  3. 方案三:$_SERVER = []推荐方案

2.1.3 关于ThinkPHP5路由解决方案

当第一次请求后下一次再请求不同的模块或者方法不生效,都是‘第一次’请求 模块/控制器/方法。如下图:

    clipboard.png

    clipboard.png

    clipboard.png

修改 ThinkPHP5框架 Request.php源码位置: /thinkphp/library/think/Request.php

    修改如下:

  1. function path() { }
    //注销判断,不再复用类成员变量$this->path
  2. function pathinfo() { }
    //注销判断,不再复用类成员变量$this->pathinfo
  3. 使其支持pathinfo路由,添加如下代码在function pathinfo() { }
if (isset($_SERVER['PATH_INFO']) && $_SERVER['PATH_INFO'] != '/') {return ltrim($_SERVER['PATH_INFO'], '/');}

    修改后完整Request.php文件:

    /*** 获取当前请求URL的pathinfo信息(含URL后缀)* @access public* @return string*/public function pathinfo(){if (isset($_SERVER['PATH_INFO']) && $_SERVER['PATH_INFO'] != '/') {return ltrim($_SERVER['PATH_INFO'], '/');}
//        if (is_null($this->pathinfo)) {if (isset($_GET[$this->config->get('var_pathinfo')])) {// 判断URL里面是否有兼容模式参数$_SERVER['PATH_INFO'] = $_GET[$this->config->get('var_pathinfo')];unset($_GET[$this->config->get('var_pathinfo')]);} elseif ($this->isCli()) {// CLI模式下 index.php module/controller/action/params/...$_SERVER['PATH_INFO'] = isset($_SERVER['argv'][1]) ? $_SERVER['argv'][1] : '';}// 分析PATHINFO信息if (!isset($_SERVER['PATH_INFO'])) {foreach ($this->config->get('pathinfo_fetch') as $type) {if (!empty($_SERVER[$type])) {$_SERVER['PATH_INFO'] = (0 === strpos($_SERVER[$type], $_SERVER['SCRIPT_NAME'])) ?substr($_SERVER[$type], strlen($_SERVER['SCRIPT_NAME'])) : $_SERVER[$type];break;}}}$this->pathinfo = empty($_SERVER['PATH_INFO']) ? '/' : ltrim($_SERVER['PATH_INFO'], '/');
//        }return $this->pathinfo;}/*** 获取当前请求URL的pathinfo信息(不含URL后缀)* @access public* @return string*/public function path(){//注销判断,不再复用类成员变量$this->path
//        if (is_null($this->path)) {$suffix   = $this->config->get('url_html_suffix');$pathinfo = $this->pathinfo();if (false === $suffix) {// 禁止伪静态访问$this->path = $pathinfo;} elseif ($suffix) {// 去除正常的URL后缀$this->path = preg_replace('/\.(' . ltrim($suffix, '.') . ')$/i', '', $pathinfo);} else {// 允许任何后缀访问$this->path = preg_replace('/\.' . $this->ext() . '$/i', '', $pathinfo);}
//        }return $this->path;}

2.2 面向对象方案

class Http {CONST HOST = "0.0.0.0";CONST PORT = 9911;public $http = null;public function __construct() {$this->http = new swoole_http_server(self::HOST, self::PORT);$this->http->set(['enable_static_handler' => true,'document_root' => "/home/wwwroot/swoole/thinkphp/public/static",'worker_num' => 4,]);$this->http->on("workerstart", [$this, 'onWorkerStart']);$this->http->on("request", [$this, 'onRequest']);$this->http->on("close", [$this, 'onClose']);$this->http->start();}/*** 此事件在Worker进程/Task进程启动时发生,这里创建的对象可以在进程生命周期内使用* 在onWorkerStart中加载框架的核心文件后* 1.不用每次请求都加载框架核心文件,提高性能* 2.可以在后续的回调中继续使用框架的核心文件或者类库** @param $server* @param $worker_id*/public function onWorkerStart($server,  $worker_id) {// 定义应用目录define('APP_PATH', __DIR__ . '/../../../../application/');// 加载框架里面的文件require __DIR__ . '/../../../../thinkphp/base.php';}/*** request回调* 解决上一次输入的变量还存在的问题例:$_SERVER  =  []* @param $request* @param $response*/public function onRequest($request, $response) {$_SERVER  =  [];if(isset($request->server)) {foreach($request->server as $k => $v) {$_SERVER[strtoupper($k)] = $v;}}if(isset($request->header)) {foreach($request->header as $k => $v) {$_SERVER[strtoupper($k)] = $v;}}$_GET = [];if(isset($request->get)) {foreach($request->get as $k => $v) {$_GET[$k] = $v;}}$_POST = [];if(isset($request->post)) {foreach($request->post as $k => $v) {$_POST[$k] = $v;}}$_POST['http_server'] = $this->http;ob_start();// 执行应用并响应try {think\Container::get('app', [APP_PATH])->run()->send();}catch (\Exception $e) {// todo}$res = ob_get_contents();ob_end_clean();$response->end($res);}/*** close* @param $ws* @param $fd*/public function onClose($ws, $fd) {echo "clientid:{$fd}\n";}
}
new Http();

三、分发Task异步任务机制实现

    示例演示:发送验证码

1、优化,将对接第三方的接口放入异步任务中
$_POST['http_server']->task($taskData);
    /*** 发送验证码*/public function index() {$phoneNum = intval($_GET['phone_num']);// tp  inputif(empty($phoneNum)) {return Util::show(config('code.error'), 'error');}// 生成一个随机数$code = rand(1000, 9999);$taskData = ['method' => 'sendSms','data' => ['phone' => $phoneNum,'code' => $code,]];//优化,将对接第三方的接口放入异步任务中$_POST['http_server']->task($taskData);return Util::show(config('code.success'), 'ok');}
} 
2、将 http对象放入预定义 $_POST中,传给调用者
$_POST['http_server'] = $this->http;
    /*** request回调*/public function onRequest($request, $response) {......//将http对象放入预定义$_POST中,传给调用者$_POST['http_server'] = $this->http;ob_start();// 执行应用并响应try {think\Container::get('app', [APP_PATH])->run()->send();}catch (\Exception $e) {// todo}......}
3、Task任务分发
    /*** Task任务分发*/public function onTask($serv, $taskId, $workerId, $data) {// 分发 task 任务机制,让不同的任务 走不同的逻辑$obj = new app\common\lib\task\Task;$method = $data['method'];$flag = $obj->$method($data['data']);return $flag; // 告诉worker}
4、代表的是 swoole里面后续所有 task异步任务都放这里来
class Task {/*** 异步发送 验证码*/public function sendSms($data, $serv) {try {$response = Sms::sendSms($data['phone'], $data['code']);}catch (\Exception $e) {return false;}// 如果发送成功 把验证码记录到redis里面if($response->Code === "OK") {Predis::getInstance()->set(Redis::smsKey($data['phone']), $data['code'], config('redis.out_time'));}else {return false;}return true;}
}

这篇关于Swoole入门到实战(二):进程,内存和协程、Swoole完美支持ThinkPHP5、分发Task异步任务机制实现的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/993659

相关文章

C++中unordered_set哈希集合的实现

《C++中unordered_set哈希集合的实现》std::unordered_set是C++标准库中的无序关联容器,基于哈希表实现,具有元素唯一性和无序性特点,本文就来详细的介绍一下unorder... 目录一、概述二、头文件与命名空间三、常用方法与示例1. 构造与析构2. 迭代器与遍历3. 容量相关4

C++中悬垂引用(Dangling Reference) 的实现

《C++中悬垂引用(DanglingReference)的实现》C++中的悬垂引用指引用绑定的对象被销毁后引用仍存在的情况,会导致访问无效内存,下面就来详细的介绍一下产生的原因以及如何避免,感兴趣... 目录悬垂引用的产生原因1. 引用绑定到局部变量,变量超出作用域后销毁2. 引用绑定到动态分配的对象,对象

Linux kill正在执行的后台任务 kill进程组使用详解

《Linuxkill正在执行的后台任务kill进程组使用详解》文章介绍了两个脚本的功能和区别,以及执行这些脚本时遇到的进程管理问题,通过查看进程树、使用`kill`命令和`lsof`命令,分析了子... 目录零. 用到的命令一. 待执行的脚本二. 执行含子进程的脚本,并kill2.1 进程查看2.2 遇到的

SpringBoot基于注解实现数据库字段回填的完整方案

《SpringBoot基于注解实现数据库字段回填的完整方案》这篇文章主要为大家详细介绍了SpringBoot如何基于注解实现数据库字段回填的相关方法,文中的示例代码讲解详细,感兴趣的小伙伴可以了解... 目录数据库表pom.XMLRelationFieldRelationFieldMapping基础的一些代

Java HashMap的底层实现原理深度解析

《JavaHashMap的底层实现原理深度解析》HashMap基于数组+链表+红黑树结构,通过哈希算法和扩容机制优化性能,负载因子与树化阈值平衡效率,是Java开发必备的高效数据结构,本文给大家介绍... 目录一、概述:HashMap的宏观结构二、核心数据结构解析1. 数组(桶数组)2. 链表节点(Node

Java AOP面向切面编程的概念和实现方式

《JavaAOP面向切面编程的概念和实现方式》AOP是面向切面编程,通过动态代理将横切关注点(如日志、事务)与核心业务逻辑分离,提升代码复用性和可维护性,本文给大家介绍JavaAOP面向切面编程的概... 目录一、AOP 是什么?二、AOP 的核心概念与实现方式核心概念实现方式三、Spring AOP 的关

Python版本信息获取方法详解与实战

《Python版本信息获取方法详解与实战》在Python开发中,获取Python版本号是调试、兼容性检查和版本控制的重要基础操作,本文详细介绍了如何使用sys和platform模块获取Python的主... 目录1. python版本号获取基础2. 使用sys模块获取版本信息2.1 sys模块概述2.1.1

Python实现字典转字符串的五种方法

《Python实现字典转字符串的五种方法》本文介绍了在Python中如何将字典数据结构转换为字符串格式的多种方法,首先可以通过内置的str()函数进行简单转换;其次利用ison.dumps()函数能够... 目录1、使用json模块的dumps方法:2、使用str方法:3、使用循环和字符串拼接:4、使用字符

Linux下利用select实现串口数据读取过程

《Linux下利用select实现串口数据读取过程》文章介绍Linux中使用select、poll或epoll实现串口数据读取,通过I/O多路复用机制在数据到达时触发读取,避免持续轮询,示例代码展示设... 目录示例代码(使用select实现)代码解释总结在 linux 系统里,我们可以借助 select、

Linux挂载linux/Windows共享目录实现方式

《Linux挂载linux/Windows共享目录实现方式》:本文主要介绍Linux挂载linux/Windows共享目录实现方式,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地... 目录文件共享协议linux环境作为服务端(NFS)在服务器端安装 NFS创建要共享的目录修改 NFS 配