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

相关文章

使用Python实现在Word中添加或删除超链接

《使用Python实现在Word中添加或删除超链接》在Word文档中,超链接是一种将文本或图像连接到其他文档、网页或同一文档中不同部分的功能,本文将为大家介绍一下Python如何实现在Word中添加或... 在Word文档中,超链接是一种将文本或图像连接到其他文档、网页或同一文档中不同部分的功能。通过添加超

windos server2022里的DFS配置的实现

《windosserver2022里的DFS配置的实现》DFS是WindowsServer操作系统提供的一种功能,用于在多台服务器上集中管理共享文件夹和文件的分布式存储解决方案,本文就来介绍一下wi... 目录什么是DFS?优势:应用场景:DFS配置步骤什么是DFS?DFS指的是分布式文件系统(Distr

Golang操作DuckDB实战案例分享

《Golang操作DuckDB实战案例分享》DuckDB是一个嵌入式SQL数据库引擎,它与众所周知的SQLite非常相似,但它是为olap风格的工作负载设计的,DuckDB支持各种数据类型和SQL特性... 目录DuckDB的主要优点环境准备初始化表和数据查询单行或多行错误处理和事务完整代码最后总结Duck

NFS实现多服务器文件的共享的方法步骤

《NFS实现多服务器文件的共享的方法步骤》NFS允许网络中的计算机之间共享资源,客户端可以透明地读写远端NFS服务器上的文件,本文就来介绍一下NFS实现多服务器文件的共享的方法步骤,感兴趣的可以了解一... 目录一、简介二、部署1、准备1、服务端和客户端:安装nfs-utils2、服务端:创建共享目录3、服

Java内存泄漏问题的排查、优化与最佳实践

《Java内存泄漏问题的排查、优化与最佳实践》在Java开发中,内存泄漏是一个常见且令人头疼的问题,内存泄漏指的是程序在运行过程中,已经不再使用的对象没有被及时释放,从而导致内存占用不断增加,最终... 目录引言1. 什么是内存泄漏?常见的内存泄漏情况2. 如何排查 Java 中的内存泄漏?2.1 使用 J

C#使用yield关键字实现提升迭代性能与效率

《C#使用yield关键字实现提升迭代性能与效率》yield关键字在C#中简化了数据迭代的方式,实现了按需生成数据,自动维护迭代状态,本文主要来聊聊如何使用yield关键字实现提升迭代性能与效率,感兴... 目录前言传统迭代和yield迭代方式对比yield延迟加载按需获取数据yield break显式示迭

Python实现高效地读写大型文件

《Python实现高效地读写大型文件》Python如何读写的是大型文件,有没有什么方法来提高效率呢,这篇文章就来和大家聊聊如何在Python中高效地读写大型文件,需要的可以了解下... 目录一、逐行读取大型文件二、分块读取大型文件三、使用 mmap 模块进行内存映射文件操作(适用于大文件)四、使用 pand

python实现pdf转word和excel的示例代码

《python实现pdf转word和excel的示例代码》本文主要介绍了python实现pdf转word和excel的示例代码,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价... 目录一、引言二、python编程1,PDF转Word2,PDF转Excel三、前端页面效果展示总结一

Python xmltodict实现简化XML数据处理

《Pythonxmltodict实现简化XML数据处理》Python社区为提供了xmltodict库,它专为简化XML与Python数据结构的转换而设计,本文主要来为大家介绍一下如何使用xmltod... 目录一、引言二、XMLtodict介绍设计理念适用场景三、功能参数与属性1、parse函数2、unpa

C#实现获得某个枚举的所有名称

《C#实现获得某个枚举的所有名称》这篇文章主要为大家详细介绍了C#如何实现获得某个枚举的所有名称,文中的示例代码讲解详细,具有一定的借鉴价值,有需要的小伙伴可以参考一下... C#中获得某个枚举的所有名称using System;using System.Collections.Generic;usi