• Swoole之php高性能框架


    Swoole介绍

      swoole是由c语言开发的异步网络通信引擎,被编译为so文件(swoole.so)作为php的extesion扩展。

      与其他普通扩展不同:

        与普通的扩展不同的是普通的扩展只是提供一个库函数。而swoole扩展在运行后会接管PHP的控制权,进入事件循环。当IO事件发生后,swoole会自动回调指定的PHP函数。  

       高性能:

        swoole使用原生c语言编写,swoole相比apache/fpm,可以常驻内存,主要是节省PHP框架和全局对象每次请求创建销毁带来的性能损耗,提升性能。

      官网 :https://www.swoole.com/

      官网手册https://wiki.swoole.com/wiki/page/1.html

      php官方手册 https://www.php.net/manual/zh/book.swoole.php

        swoole源码:https://github.com/swoole/swoole-src

      来自官网的介绍

      

      可以看出Swoole可以支持高并发。在看看Swoole的特性

      

    Swoole架构

      

                        swoole架构图

       架构说明:

        1. Master进程:是Swoole的主进程, Master里面包含了很多Reactor线程,主要基于Reactor模式用于处理客户端的请求,swoole对于事件的监听都是在这些线程中实现,也包括对信号的处理。

         Master进程里包含一些线程,比如主线程和Reactor线程组等。

          1)主线程(MainReactor)负责监听等待客户端的socket连接,如果有新的连接accept,主线程会根据Reactor线程连接的数量来评估,将新的连接分配给连接数量最少的reactor线程。

          2)Reactor线程组负责维持与客户端TCP连接,处理网络IO,异步非阻塞的去收发数据等。Reactor线程在socket可读的时候进行协议解析,将请求投递到worker进程,在socket可写时候将数据发送给客户端。

          3)心跳包检测线程(HeartbeatCheck)

          4)UDP收包线程(UDPRecv)。

          
        2.Manger进程:管理进程。位于Worker进程和Task进程的上层,负责对工作进程的创建及管理,当工作进程由于程序原因异常退出或者被误杀,容易产生僵尸进程,为了保证服务的稳定性,Manger进程会监视工作进程的事件,必要的时候Manger进程重新创建工作进程或者回收。  
        3.Worker进程:工作进程,负责业务处理。接受Reactor线程的投递请求数据包,并执行PHP回调函数处理数据,worker处理完毕后,通过进程间的通信(比如管道,共享内存,消息队列)生成响应数据返回给Reactor线程。
        4.Task进程:异步耗时任务工作进程。

      一个请求经历的生命周期如下:

        1. Swoole服务器主线程(Master)等待客户端连接请求。
        2. Reactor线程处理接连socket,读取socket上的请求数据(Receive),将请求封装好后投递给Work进程。
        3. Work进程就是逻辑单元,处理业务数据。
        4. Work进程结果返回给Reactor线程。
        5. Reactor线程将结果返回socket,然后在返回给客户端。

    Swoole运行模式

      Swoole最大特点是在于多线程Reactor模式处理网络请求,使其能够面对高并发的大量连接请求。进程常驻内存,程序启动运行只加载一次PHP文件,避免重复加载。全异步非阻塞,占用资源小,使用协程程序执行效率高。解决了传统PHP开发模式的痛点。  

    Swoole热重启

      swoole启动之后只需要加载初始化一次php文件,但是这个也导致一些问题,当修改php文件后不能像传统那样马上就能获取修改后的效果,而是需要先关闭swoole服务然后在重新启动,这样才能重新加载修改后的php代码,但是这样显然是不能接受的,当然swoole已经考虑到这点并提供了解决这样问题的功能。

      在swoole中可以向主进程发送各种不同信号,主进程根据接收到的信号作出不同的处理。

    传统php-fpm模式和Swoole的区别

      1.  php-fpm模式是一个进程池架构的Fastcgi服务,Master 主进程 / Worker 多进程工作模式,每次每个Worker 进程只能接受一个请求,当PHP代码执行完毕,会释放所有的资源(包括初始化一系列的对象,环境设置),下一次请求需要重新再进行初始化等各种繁琐的操作,消耗很多cpu等资源。

      2. Swoole也是采用的也是 Master/Worker 模式,不同的是 Master 进程有多个 Reactor 线程,Master 只是一个事件发生器,负责监听 Socket 句柄的事件变化。Worker 以多进程的方式运行,接收来自 Reactor 线程的请求,并执行回调函数(PHP 编写)。

      3. 高并发情况下fpm模式容易502,服务压力大时,worker不够用就会不可响应或响应不过来。如果通过开启多个worker进程会占用大量内存,对服务器压力也很大。

     

    Swoole生命周期

      

    Swoole使用注意事项

       1. 相比传统PHP,更难于上手,需要一点学习成本。

       2. 因为是常驻内存,更容易内存泄漏,在处理全局变量,静态变量的时候需要注意,这些是不会被GC清理的变量会存在整个生命周期里,如果不稍加注意这些变量会很容易消耗完内存。修改代码不会马上起作用,需要热更新或者重启。

     

     Swoole安装

      安装环境要求 

      安装前必须保证系统已经安装了下列软件
      php-7.1 或更高版本
      gcc-4.8 或更高版本
       make
       autoconf

      1.swoole源码获取并安装

    songguojundeMBP:src songguojun$ git clone https://gitee.com/swoole/swoole.git
    
    songguojundeMBP:src songguojun$ ll
    total 35488
    drwxr-xr-x  10 songguojun  wheel       320  8  4 00:38 ./
    drwxr-xr-x  18 root        wheel       576  8  3 21:02 ../
    drwxr-xr-x  74 songguojun  wheel      2368  8  4 00:39 swoole/
    
    没有configure文件 那么就需要通过phpize文件去生成configure文件

     执行phpize命令

    songguojundeMBP:swoole songguojun$ /usr/local/src/php7/bin/phpize
    Configuring for:
    PHP Api Version:         20170718
    Zend Module Api No:      20170718
    Zend Extension Api No:   320170718
    songguojundeMBP:swoole songguojun$ ls config*      #执行完毕后目录下吗多了很多文件 比如configure
    config.guess    config.h.in    config.m4    config.sub    configure    configure.ac

     注意上面phpize命令要在swoole目录下执行,不然会报错

     查看configure增加了swoole选项,可以根据你的需要来决定是否开启

    songguojundeMBP:swoole songguojun$ ./configure --help | grep swoole
      --enable-debug-log        Enable swoole debug log
      --enable-trace-log        Enable swoole trace log
      --enable-swoole           Enable swoole support

    2.执行configure

    songguojundeMBP:swoole songguojun$ ./configure --with-php-config=/usr/local/src/php7/bin/php-config  #这里指定具体的php配置目录,因为我本地环境有多个php版本

    3.执行make

    songguojundeMBP:swoole songguojun$ make && make install          
    
    ........
    
    Build complete.
    Don't forget to run 'make test'.
    
    Installing shared extensions:     /usr/local/src/php7/lib/php/extensions/no-debug-non-zts-20170718/      #这里提示swoole扩展安装到这个目录下了
    Installing header files:          /usr/local/src/php7/include/php/
    songguojundeMBP:swoole songguojun$ cd /usr/local/src/php7/lib/php/extensions/no-debug-non-zts-20170718/  
    songguojundeMBP:no-debug-non-zts-20170718 songguojun$ ll
    total 8272
    drwxr-xr-x  5 songguojun  wheel      160  8  4 10:53 ./
    drwxr-xr-x  3 songguojun  wheel       96  8  3 18:26 ../
    -rwxr-xr-x  1 songguojun  wheel  2019064  8  3 18:26 opcache.a*
    -rwxr-xr-x  1 songguojun  wheel   441892  8  3 18:26 opcache.so*
    -rwxr-xr-x  1 songguojun  wheel  1770380  8  4 10:53 swoole.so*                <-  进入扩展目录发现swoole.so文件已经生成

     examples目录下提供了很多demo文件,我们进入examples/server目录

    songguojundeMBP:server songguojun$ pwd
    /usr/local/src/swoole/examples/server
    songguojundeMBP:server songguojun$ ls
    dispatch_func.php    getReceivedTime.php    pipe_message.php    uid_dispatch.php
    dispatch_stream.php    ip_dispatch.php        reload_aysnc.php    unix_stream.php
    echo.php        listen_1k_port.php    reload_force.php    zmq.php
    exist.php        local_listener.php    reload_force2.php
    fixed_header_client.php    manager_timer.php    single.php
    fixed_header_server.php    multi_instance.php    trace.php

     查看其中echo.php文件

    $serv = new swoole_server("0.0.0.0", 9501);
    $serv->set(array(
        'worker_num' => 1,
    ));
    $serv->on('receive', function (swoole_server $serv, $fd, $reactor_id, $data) {
        echo "[#".$serv->worker_id."]	Client[$fd] receive data: $data
    ";
        if ($serv->send($fd, "hello {$data}
    ") == false)
        {
            echo "error
    ";
        }
    
    });
    $serv->start();

     我们执行下,发现会报错,提示缺少swoole_server类,这是因为目前php还没办法使用到swoole类库。要是用swoole的类库必须要在php.ini配置文件中打开

     查看下php.ini文件路径

    songguojundeMBP:server songguojun$ php -i | grep php.ini
    Configuration File (php.ini) Path => /usr/local/src/php7/lib

    由于我是Mac系统,php.ini文件在/private/etc/目录下,/private/etc/php.ini 如果没有这个文件,将这个目录下的php.ini.default复制一份重命名为php.ini,然后在该文件中添加swoole扩展。

    添加完毕之后在将 /private/etc/php.ini文件复制到/usr/local/src/php7/lib目录下

    cp /private/etc/php.ini /usr/local/src/php7/lib/php.ini

    然后在查看swoole扩展是否存在

     然后在执行刚刚上面echo.php文件看是否成执行成功。

     发现没有报错了,而且通过命令发现正在监听9501端口

    songguojundeMBP:server songguojun$ lsof -i :9501
    COMMAND  PID       USER   FD   TYPE            DEVICE SIZE/OFF NODE NAME
    php     3142 songguojun    3u  IPv4 0xd405c2bd609771f      0t0  TCP *:9501 (LISTEN)
    telnet  3187 songguojun    3u  IPv4 0xd405c2bdf381d9f      0t0  TCP localhost:64452->localhost:9501 (ESTABLISHED)
    songguojundeMBP:server songguojun$ kill -9 3142   #杀掉进程
    songguojundeMBP:server songguojun$ lsof -i :9501

     查看Swoole版本

     Swoole TCP服务

      tcp.php

    //创建Server对象,监听 127.0.0.1:9501端口
    $serv = new SwooleServer("127.0.0.1", 9501); 
    //设置基本参数
    $serv->set([
        'worker_num' => 8, //worker进程数 建议设置cpu核数1-4倍
        'max_request' => 10000,
    ]);
    /*
     * 监听连接进入事件
     * $fd是客户端连接的唯一标识
     * $reactor_id 线程id
     */
    $serv->on('Connect', function ($serv, $fd, $reactor_id) {  
        echo "Client: {$fd} Connect.
    ";
    });
    //监听客户端数据接收事件
    $serv->on('Receive', function ($serv, $fd, $reactor_id, $data) {
        //向客户端发送数据
        $serv->send($fd, "Server: ".$data);
    });
    //监听连接关闭事件
    $serv->on('Close', function ($serv, $fd) {
        echo "Client: Close.
    ";
    });
    //启动服务器
    $serv->start(); 

     运行tcp.php来启动 tcp服务。

    我们可以先查看tcp.php开启的子进程情况。

     下面使用telnet这个工具去连接9501端口

     上面用telnet去充当客户端连接tcp服务,我们也可以使用swoole提供的客户端来实现。

    tcp_client.php

    <?php
        //实例化tcp client
        $client = new swoole_client(SWOOLE_SOCK_TCP);
        if (!$client->connect("127.0.0.1", 9501)) {
            echo "connect failed";
            die;
        }
    
        //输入消息 STDOUT STDIN属于php cli模式下的常量
        fwrite(STDOUT, "请输入内容
    ");
        $message = trim(fgets(STDIN));
        //向tcp服务端发送消息
        if ($message) {
            $client->send($message);
            //接受来自服务端的消息
            $result = $client->recv();
            echo $result."
    ";
        }
    ?>

     运行

     Swoole Httpserver

       Swoole中的http服务是基于swool_server,swool_server中所有的方法httpserver都是可以使用的。

       文档地址:https://wiki.swoole.com/wiki/page/14.html

       下面实现一个http服务器。

      http_server.php

    //0.0.0.0监听全部地址
        $http = new swoole_http_server("0.0.0.0", 8811);
        $http->on("request", function($request, $response){
            //输出响应内容
            $response->end("<h1>http server response</h1>"); //end方法将数据输出到浏览器中,只能输出字符串
        });
        //启动http服务
        $http->start();
    

      在命令行执行http_server.php,然后使用curl命令访问。

      

       也可以使用浏览器访问,输入http://127.0.0.1:8811/

      

      如果希望在url上传递参数,php-fpm模式下我们可以使用$_GET/$_POST这样的方式来获取,但是swoole中这样是获取不到的,swoole是通过$request对象来获取,$request对象可以获取及设置get/post/cookie等信息。

      修改上面http_server.php文件。

    //0.0.0.0监听全部地址
        $http = new swoole_http_server("0.0.0.0", 8811);
        $http->on("request", function($request, $response){
            //输出响应内容
            print_r($request->get); //$request->get获取get数据
            $response->end("<h1>http server response</h1>");
        });
        //启动http服务
        $http->start();

       修改完毕之后记得要重启http_server.php,不然不生效。

      除了动态资源还可以通过swoole获取静态资源,比如访问一个html文件。

    //0.0.0.0监听全部地址
        $http = new swoole_http_server("0.0.0.0", 8811);
        //设置开启静态资源
        $http->set([
            'enable_static_handler' => true,
            'document_root' => "/Users/songguojun/code/php/swoole/data",
        ]);
        $http->on("request", function($request, $response){
            //输出响应内容
            $response->end("<h1>http server response</h1>".$request->get);
        });
        //启动http服务
        $http->start();

    index.html

    <html>
    <head></head>
    <body>
     <p>
        我是静态资源index.html
    </p>
    </body>
    </html>

    Swoole WebSocket服务

       Swoole中Websocket的实现是swoole_websocket_server类,这个类继承swoole_http_server。

       Websocket实现

    $server = new swoole_websocket_server("0.0.0.0",8812);
        //监听websocket打开事件
        $server->on("open", function(swoole_websocket_server $server, $request){
            echo "server: handshake success with fd{$request->fd}
    ";
        });
        //监听websocket消息事件
        $server->on("message", function(swoole_websocket_server $server, $frame){
            echo "receive from {$frame->fd}: {$frame->data},opcode:{$frame->opcode},fin:{$frame->finish}
    ";
            //将数据发送到客户端
            $server->push($frame->fd, "this is server");
        });
    
        $server->on("close", function($server, $fd){
            echo "client {$fd} close
    ";
        });
        $server->start();

     Swoole Task服务

       Swoole提供了一个task服务,用于执行比较耗时的操作,比如发送邮件,广播信息等等。它是将这些耗时的任务放到一个swoole task woker任务进程池中。

       使用Task

        1. 设置两个回调函数,onTask和onFlish。

        2.设置task_worker_num参数。

     Swoole进程管理

       Swoole 提供的进程管理模块,用来替代 PHP的pcntl扩展。

        因为PHP原生自带的pcntl扩展存在很多不足,如下:

        1. 没有提供进程间通信的功能。

        2. 不支持重定向标准输入和输出。

        3. 只提供了fork这样原始的接口,容易使用错误。

       Swoole就提供了Process这个类来实现进程方面的功能。

       Swoole进程wiki地址:https://wiki.swoole.com/#/process  (旧)  https://wiki.swoole.com/#/process (新)

       Swoole里进程与进程之间的通信是通过管道来进行的。

       在Swoole里使用多进程比原生php多进程更加容易。我们可以使用多进程来处理许多耗时的场景,比如curl获取多个页面内容。

       进程使用案例1:

      echo "脚本开始".date("Y-m-d H:i:s");
      $urlsArr = [
        "http://www.baidu.com",
        "http://www.qq.com",
        "http://www.sina.com.cn",
        "http://www.sohu.com",
        "http://www.hao123.com",
        "http://www.cnblogs.com"
      ];
      $workers = [];
      for ($i = 0; $i < 6; $i++) {
        //创建子进程 分别有三个参数 1.子进程创建成功后要执行的函数 2.重定向子进程的标准输入和输出 3.是否启用管道(用于进程通信)
        $childProcess = new swoole_process(function(swoole_process $worker) use ($i, $urlsArr) {
            $content = curlData($urlsArr[$i]);
            $worker->write($content);  // 将内容写入到管道中
            }, true); //true表示不打印输出的内容
        $pid = $childProcess->start();
        $worker[$pid] = $childProcess;
      }
    
      //模拟请求
      function curlData($url){
        sleep(2);
        return "handle ".$url." finished";
      }
      //输出进程管道中数据结果
      foreach ($worker as $process){
        echo $process->read().PHP_EOL;
      }
      echo "脚本结束".date("Y-m-d H:i:s");    

    执行结果 总耗时也就1秒多点。

    songguojundeMacBook-Pro:swoole songguojun$ php process.php 
    脚本开始2020-03-26 09:33:19
    handle http://www.baidu.com finished handle http://www.qq.com finished handle http://www.sina.com.cn finished handle http://www.sohu.com finished handle http://www.hao123.com finished handle http://www.cnblogs.com finished 脚本结束2020-03-26 09:33:21

      Swoole内存模块

       swoole提供了几个内存操作模块

        1. Lock。

        2. Buffer。

        3. Table。

        4. Atomic。

        5. mmap。

        6. channel。

        7. serialize。

      我们看下swoole里的table。

       swoole_table是一个基于共享内存和锁实现的高性能,高并发数据结构。可以利用swoole_table在内存中申请一块内存空间。

    Swoole协程

      swoole另一个特性就是提供了协程,协程可以认为是用户态的线程,是通过协作而非抢占式来进行切换,所有的调度都是在用户态中进行,创建和消耗非常低。

    Swoole异步非堵塞IO场景使用

    Swoole系统监控及性能优化

    参考资料:

      https://www.w3cschool.cn/swoole/npai1qc9.html

  • 相关阅读:
    c++ list_iterator demo
    模板元编程例子
    !a && !b 和 !(a || b) 的故事
    简明解释算法中的大O符号
    重构oceanbase的一个函数
    正则表达式识别汉字
    编写易于理解代码的六种方式
    Linux下的tar压缩解压缩命令详解
    2013 年 —— Facebook 在开源方面的工作介绍
    Kent Beck揭秘Facebook开发部署流程
  • 原文地址:https://www.cnblogs.com/songgj/p/9497061.html
Copyright © 2020-2023  润新知