进程就是
正在运行的程序
的
一个实例
$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();
以树状图显示进程间的关系:pstree -p 进程id
启动成功后会建立worker_num+2
个进程。Master
进程+Manager
进程+serv
->worker_num
个Worker
进程php
管道:进程和进程间的一个桥梁
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_contents sleep(1); return $url . "success".PHP_EOL; } echo "process-end-time:".date("Ymd H:i:s");
内存操做模块之:
Table
swoole_table
一个基于共享内存和锁实现的超高性能,并发数据结构
使用场景:用于解决多进程/多线程
数据共享和同步加锁问题
进程结束后内存表会自动释放html
// 建立内存表 $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']);
线程、进程、协程的区别
进程,线程,协程与并行,并发
并发与并行的区别?mysql
$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();
重难点
)$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();
测试:redis
//此事件在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:sql
此事件在Worker
进程/Task
进程启动时发生,这里建立的对象能够在进程生命周期内使用
在onWorkerStart
中加载框架的核心文件后:thinkphp
当前worker
进程没有结束,因此会保存上一次的资源等。解决上一次输入的变量还存在的问题:segmentfault
if(!empty($_SERVER)) { unset($_SERVER); }
$http-close();
把以前的进程kill
,swoole
会从新启一个进程,重启会释放内存,把上一次的资源包括变量等所有清空(php-cli
控制台会提示错误)$_SERVER = []
(推荐方案)当第一次请求后下一次再请求不一样的模块或者方法不生效,都是‘第一次’请求 模块/控制器/方法。以下图:
修改ThinkPHP5
框架Request.php
源码位置:/thinkphp/library/think/Request.php
修改以下:浏览器
function path() { }
$this->path
function pathinfo() { }
$this->pathinfo
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; }
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();
示例演示:发送验证码性能优化
一、优化,将对接第三方的接口放入异步任务中
$_POST['http_server']->task($taskData);
/** * 发送验证码 */ public function index() { $phoneNum = intval($_GET['phone_num']);// tp input if(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'); } }
二、将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 } ...... }
三、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 }
四、表明的是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; } }