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

相关文章

SpringBoot3实现Gzip压缩优化的技术指南

《SpringBoot3实现Gzip压缩优化的技术指南》随着Web应用的用户量和数据量增加,网络带宽和页面加载速度逐渐成为瓶颈,为了减少数据传输量,提高用户体验,我们可以使用Gzip压缩HTTP响应,... 目录1、简述2、配置2.1 添加依赖2.2 配置 Gzip 压缩3、服务端应用4、前端应用4.1 N

SpringBoot实现数据库读写分离的3种方法小结

《SpringBoot实现数据库读写分离的3种方法小结》为了提高系统的读写性能和可用性,读写分离是一种经典的数据库架构模式,在SpringBoot应用中,有多种方式可以实现数据库读写分离,本文将介绍三... 目录一、数据库读写分离概述二、方案一:基于AbstractRoutingDataSource实现动态

Python FastAPI+Celery+RabbitMQ实现分布式图片水印处理系统

《PythonFastAPI+Celery+RabbitMQ实现分布式图片水印处理系统》这篇文章主要为大家详细介绍了PythonFastAPI如何结合Celery以及RabbitMQ实现简单的分布式... 实现思路FastAPI 服务器Celery 任务队列RabbitMQ 作为消息代理定时任务处理完整

Java枚举类实现Key-Value映射的多种实现方式

《Java枚举类实现Key-Value映射的多种实现方式》在Java开发中,枚举(Enum)是一种特殊的类,本文将详细介绍Java枚举类实现key-value映射的多种方式,有需要的小伙伴可以根据需要... 目录前言一、基础实现方式1.1 为枚举添加属性和构造方法二、http://www.cppcns.co

使用Python实现快速搭建本地HTTP服务器

《使用Python实现快速搭建本地HTTP服务器》:本文主要介绍如何使用Python快速搭建本地HTTP服务器,轻松实现一键HTTP文件共享,同时结合二维码技术,让访问更简单,感兴趣的小伙伴可以了... 目录1. 概述2. 快速搭建 HTTP 文件共享服务2.1 核心思路2.2 代码实现2.3 代码解读3.

MySQL双主搭建+keepalived高可用的实现

《MySQL双主搭建+keepalived高可用的实现》本文主要介绍了MySQL双主搭建+keepalived高可用的实现,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,... 目录一、测试环境准备二、主从搭建1.创建复制用户2.创建复制关系3.开启复制,确认复制是否成功4.同

Java实现文件图片的预览和下载功能

《Java实现文件图片的预览和下载功能》这篇文章主要为大家详细介绍了如何使用Java实现文件图片的预览和下载功能,文中的示例代码讲解详细,感兴趣的小伙伴可以跟随小编一起学习一下... Java实现文件(图片)的预览和下载 @ApiOperation("访问文件") @GetMapping("

Spring Boot + MyBatis Plus 高效开发实战从入门到进阶优化(推荐)

《SpringBoot+MyBatisPlus高效开发实战从入门到进阶优化(推荐)》本文将详细介绍SpringBoot+MyBatisPlus的完整开发流程,并深入剖析分页查询、批量操作、动... 目录Spring Boot + MyBATis Plus 高效开发实战:从入门到进阶优化1. MyBatis

MyBatis 动态 SQL 优化之标签的实战与技巧(常见用法)

《MyBatis动态SQL优化之标签的实战与技巧(常见用法)》本文通过详细的示例和实际应用场景,介绍了如何有效利用这些标签来优化MyBatis配置,提升开发效率,确保SQL的高效执行和安全性,感... 目录动态SQL详解一、动态SQL的核心概念1.1 什么是动态SQL?1.2 动态SQL的优点1.3 动态S

使用Sentinel自定义返回和实现区分来源方式

《使用Sentinel自定义返回和实现区分来源方式》:本文主要介绍使用Sentinel自定义返回和实现区分来源方式,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录Sentinel自定义返回和实现区分来源1. 自定义错误返回2. 实现区分来源总结Sentinel自定