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%20-p%20进程id    启动成功后会创建worker_num+2个进程。Master进程+Manager进程+serv->worker_num个Worker进程

1.1.2%20进程使用场景    

    clipboard.png

管道:进程和进程间的一个桥梁echo%20"process-start-time:".date("Ymd%20H:i:s"); $workers%20=%20[]; $urls%20=%20[ %20%20%20%20'http://baidu.com', %20%20%20%20'http://sina.com.cn', %20%20%20%20'http://qq.com', %20%20%20%20'http://baidu.com?search=singwa', %20%20%20%20'http://baidu.com?search=singwa2', %20%20%20%20'http://baidu.com?search=imooc', ]; //创建多个子进程分别模拟请求URL的内容 for($i%20=%200;%20$i%20<%206;%20$i++)%20{ %20%20%20%20$process%20=%20new%20swoole_process(function(swoole_process%20$worker)%20use($i,%20$urls)%20{ %20%20%20%20%20%20%20%20//%20curl %20%20%20%20%20%20%20%20$content%20=%20curlData($urls[$i]); %20%20%20%20%20%20%20%20//将内容写入管道 %20%20%20%20//%20%20%20%20echo%20$content.PHP_EOL; %20%20%20%20%20%20%20%20$worker->write($content.PHP_EOL); %20%20%20%20},%20true); %20%20%20%20$pid%20=%20$process->start(); %20%20%20%20$workers[$pid]%20=%20$process; } //获取管道内容 foreach($workers%20as%20$process)%20{ %20%20%20%20echo%20$process->read(); } /** %20*%20模拟请求URL的内容%20%201s %20*%20@param%20$url %20*%20@return%20string %20*/ function%20curlData($url)%20{ %20%20%20%20//%20curl%20file_get_contents %20%20%20%20sleep(1); %20%20%20%20return%20$url%20.%20"success".PHP_EOL; } echo%20"process-end-time:".date("Ymd%20H:i:s");1.2%20Swoole内存-table详解内存操作模块之:Table    swoole_table一个基于共享内存和锁实现的超高性能,并发数据结构    使用场景:用于解决多进程/多线程数据共享和同步加锁问题    进程结束后内存表会自动释放

//%20创建内存表 $table%20=%20new%20swoole_table(1024); //%20内存表增加一列 $table->column('id',%20$table::TYPE_INT,%204); $table->column('name',%20$table::TYPE_STRING,%2064); $table->column('age',%20$table::TYPE_INT,%203); $table->create(); $table->set('singwa_imooc',%20['id'%20=>%201,%20'name'=>%20'singwa',%20'age'%20=>%2030]); //%20另外一种方案 $table['singwa_imooc_2']%20=%20[ %20%20%20%20'id'%20=>%202, %20%20%20%20'name'%20=>%20'singwa2', %20%20%20%20'age'%20=>%2031, ]; $table->decr('singwa_imooc_2',%20'age',%202); print_r($table['singwa_imooc_2']); echo%20"delete%20start:".PHP_EOL; $table->del('singwa_imooc_2'); print_r($table['singwa_imooc_2']);1.3%20协程    线程、进程、协程的区别    进程,线程,协程与并行,并发    并发与并行的区别?

$http%20=%20new%20swoole_http_server('0.0.0.0',%209001); $http->on('request',%20function($request,%20$response)%20{ %20%20%20%20//%20获取redis%20里面%20的key的内容,%20然后输出浏览器 %20%20%20%20$redis%20=%20new%20Swoole\Coroutine\Redis(); %20%20%20%20$redis->connect('127.0.0.1',%206379); %20%20%20%20$value%20=%20$redis->get($request->get['a']); %20%20%20%20//%20mysql..... %20%20%20%20//执行时间取它们中最大的:time%20=%20max(redis,mysql) %20%20%20%20$response->header("Content-Type",%20"text/plain"); %20%20%20%20$response->end($value); }); $http->start();%20%20%20%20二、Swoole完美支持ThinkPHP5(重难点2.1%20面向过程方案2.1.1%20面向过程代码实现$http%20=%20new%20swoole_http_server("0.0.0.0",%209911); $http->set( %20%20%20%20[ %20%20%20%20%20%20%20%20'enable_static_handler'%20=>%20true, %20%20%20%20%20%20%20%20'document_root'%20=>%20"/home/wwwroot/swoole/thinkphp/public/static", %20%20%20%20%20%20%20%20'worker_num'%20=>%205, %20%20%20%20] ); //此事件在Worker进程/Task进程启动时发生,这里创建的对象可以在进程生命周期内使用 $http->on('WorkerStart',%20function(swoole_server%20$server,%20%20$worker_id)%20{ %20%20%20%20//%20定义应用目录 %20%20%20%20define('APP_PATH',%20__DIR__%20.%20'/../../../../application/'); %20%20%20%20//%20加载框架里面的文件 %20%20%20%20require%20__DIR__%20.%20'/../../../../thinkphp/base.php'; }); $http->on('request',%20function($request,%20$response)%20use($http){ %20%20%20%20%20%20%20%20//如果在每次请求时加载框架文件,则不用修改thinkphp5源码 //%20%20%20%20//%20定义应用目录 //%20%20%20%20define('APP_PATH',%20__DIR__%20.%20'/../../../../application/'); //%20%20%20%20//%20加载框架里面的文件 //%20%20%20%20require_once%20__DIR__%20.%20'/../../../../thinkphp/base.php'; %20%20%20%20/** %20%20%20%20%20*%20解决上一次输入的变量还存在的问题 %20%20%20%20%20*%20方案一:if(!empty($_GET))%20{unset($_GET);} %20%20%20%20%20*%20方案二:$http-close();把之前的进程kill,swoole会重新启一个进程,重启会释放内存,把上一次的资源包括变量等全部清空 %20%20%20%20%20*%20方案三:$_SERVER%20%20=%20%20[] %20%20%20%20%20*/ %20%20%20%20$_SERVER%20%20=%20%20[]; %20%20%20%20if(isset($request->server))%20{ %20%20%20%20%20%20%20%20foreach($request->server%20as%20$k%20=>%20$v)%20{ %20%20%20%20%20%20%20%20%20%20%20%20$_SERVER[strtoupper($k)]%20=%20$v; %20%20%20%20%20%20%20%20} %20%20%20%20} %20%20%20%20if(isset($request->header))%20{ %20%20%20%20%20%20%20%20foreach($request->header%20as%20$k%20=>%20$v)%20{ %20%20%20%20%20%20%20%20%20%20%20%20$_SERVER[strtoupper($k)]%20=%20$v; %20%20%20%20%20%20%20%20} %20%20%20%20} %20%20%20%20$_GET%20=%20[]; %20%20%20%20if(isset($request->get))%20{ %20%20%20%20%20%20%20%20foreach($request->get%20as%20$k%20=>%20$v)%20{ %20%20%20%20%20%20%20%20%20%20%20%20$_GET[$k]%20=%20$v; %20%20%20%20%20%20%20%20} %20%20%20%20} %20%20%20%20$_POST%20=%20[]; %20%20%20%20if(isset($request->post))%20{ %20%20%20%20%20%20%20%20foreach($request->post%20as%20$k%20=>%20$v)%20{ %20%20%20%20%20%20%20%20%20%20%20%20$_POST[$k]%20=%20$v; %20%20%20%20%20%20%20%20} %20%20%20%20} %20%20%20%20//开启缓冲区 %20%20%20%20ob_start(); %20%20%20%20//%20执行应用并响应 %20%20%20%20try%20{ %20%20%20%20%20%20%20%20think\Container::get('app',%20[APP_PATH]) %20%20%20%20%20%20%20%20%20%20%20%20->run() %20%20%20%20%20%20%20%20%20%20%20%20->send(); %20%20%20%20}catch%20(\Exception%20$e)%20{ %20%20%20%20%20%20%20%20//%20todo %20%20%20%20} %20%20%20%20//输出TP当前请求的控制方法 %20%20%20%20//echo%20"-action-".request()->action().PHP_EOL; %20%20%20%20//获取缓冲区内容 %20%20%20%20$res%20=%20ob_get_contents(); %20%20%20%20ob_end_clean(); %20%20%20%20$response->end($res); %20%20%20%20//把之前的进程kill,swoole会重新启一个进程,重启会释放内存,把上一次的资源包括变量等全部清空 %20%20%20%20//$http->close(); }); $http->start();    测试:

    

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  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');
   }
} 
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异步任务机制实现

人吐槽 人点赞

猜你喜欢

发表评论

用户名: 密码:
验证码: 匿名发表

你可以使用这些语言

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