[toc] >[info] 用于替代PHP-FPM同步阻塞的模式。实现异步非阻塞高性能的web服务 ## 优势 * 性能的数十倍的提升; * 可以在`Apache`/`Nginx`等传统WEB服务器和`Swoole`之间切换部署; * **在此模式下,还可以直接在控制器中调用`swoole/server`的方法,比如定时,任务异步投递,协程等等** > 本例使用的是thinkphp5.1的扩展包, 本文只做实操记录与源码分析,详细内容查看 [ThinkPHP 5.1 Swoole 快速上手指南](https://www.kancloud.cn/thinkphp/think-swoole/722895)。 ~~~ composer require topthink/think-swoole # 启动 php think swoole ~~~ ## 理解性能提升 原生的php-fpm是同步阻塞模式的,PHP-FPM 会创建一个主进程,控制何时以及如何将HTTP请求转发给一个或多个子进程处理。PHP-FPM主进程还控制着什么时候创建(处理Web应用更多的流量)和销毁(子进程运行时间太久或不再需要了)。 <br /> 而swoole-httpserver是异步非阻塞模式的。它常驻于内存,并基于事件回调。 **我们先通过ab压测分别压测不同模式下的同一PHP程序** `这里压的是开了debug的thinkphp5.1默认页面` ~~~ // -n 2000表示总请求数为2000 // -c 100 表示并发用户数为100 // -k 使用HTTP的KeepAlive特性 ab -c 100 -n 2000 -k http://127.0.0.1:9501/ ~~~ 关于ab压测工具的安装与使用:[https://cloud.tencent.com/developer/article/1333772](https://cloud.tencent.com/developer/article/1333772) **在php-fpm模式下** ![](https://i.loli.net/2019/04/12/5cb055468f405.png) **在swoole-httpserver模式下** ![](https://i.loli.net/2019/04/12/5cb056319e8c7.png) >[info] 如图所见,几十倍近百倍的性能提升。**注意:**这可不是压 `hello world` 哦,而是压加载了93个文件的框架默认页。 >[danger] 注意,异步或协程模式下的httpserver中不能有同步阻塞的代码,否则就会变成同步,如果变成同步以后,性能还远远不如**PHP-fpm** > [PHP中哪些函数是同步阻塞的](https://wiki.swoole.com/wiki/page/474.html)。 **目前TP-swoole2.0版本对底层数据库的协程是还不支持的!到3.0版本才支持** ### 异步的优势 * 高并发,同步阻塞IO模型的并发能力依赖于进程/线程数量,例如`php-fpm`开启了200个进程,理论上最大支持的并发能力为200。如果每个请求平均需要100ms,那么应用程序就可以提供2000qps。异步非阻塞的并发能力几乎是无限的,可以发起或维持大量并发TCP连接 * 无IO等待,同步模型无法解决`IOWait`很高的场景,如上述例子每个请求平均要10s,那么应用程序就只能提供20qps了。而异步程序不存在IO等待,所以无论请求要花费多长时间,对整个程序的处理能力没有任何影响 ### 同步的优势 * 编码简单,同步模式编写/调试程序更轻松 * 可控性好,同步模式的程序具有良好的过载保护机制,如在下面的情况异步程序就会出问题 * Accept保护,同步模式下一个TCP服务器最大能接受`进程数+Backlog`个TCP连接。一旦超过此数量,Server将无法再接受连接,客户端会连接失败。避免服务器Accept太多连接,导致请求堆积 ## 配置问题 如果应用目录`application`因为使命名空间更具语义化而更改为`app`的话,要手动指定配置项`file_monitor_path` ~~~ 'file_monitor_path' => ['/home/wwwroot/XGservice/app/','/home/wwwroot/XGservice/config/'], // 文件监控目录 默认监控application和config目录 ~~~ >[warning] 切换成swoole模式后,应用就不是从入口文件index.php启动了。而是在onWorkerStart回调中启动,启动的是think/swoole/application(继承自think/app) ## 原生实现 [Http\\Server](https://wiki.swoole.com/wiki/page/327.html) ~~~ use Swoole\Http\Server; $http = new Server("0.0.0.0", 9501); $http->on('request', function ($request, $response) { $response->end("<h1>Hello Swoole. #".rand(1000, 9999)."</h1>"); }); $http->start(); ~~~ `Http\Server`继承自`Server`,是一个的`Http`服务器实现。`Http\Server`支持同步和异步2种模式。无论是同步模式还是异步模式,`Http\Server`都可以维持大量`TCP`客户端连接。同步/异步仅仅体现在对请求的处理方式上。 >[info] `Http/WebSocket`服务器都是继承自`Server`,所以`Server`提供的`API`,如`task/finish/tick`等都可以使用 ## 扩展源码分析 `/vendor/topthink/think-swoole/src/command/Swoole.php` 1. 服务启动前,有钩子监听后续可增加钩子扩展业务 ~~~ $hook = Container::get('hook'); $hook->listen("swoole_server_start", $swoole); ~~~ 2. **在Worker进程启动时也预留了钩子,同时调用了config里面配置的闭包或者类文件(run方法)** ~~~ // vendor/topthink/think-swoole/src/Http.php:142 // 此步就是原生中on回调onWokerstart //onWokerstart预留的钩子 $hook = Container::get('hook'); $hook->listen('swoole_worker_start', ['server' => $server, 'worker_id' => $worker_id]); // 闭包或者类文件 $wokerStart = Config::get('swoole.wokerstart'); ~~~ >[info] swoole是基于事件的。thinkphp-swoole在显式的指定了onWorkerStart(server的回调事件),onRequest(httpServer的回调事件),其它事件可以在 3. 通过 [Process::kill](https://wiki.swoole.com/wiki/page/p-process.html) 实现 `stop`,`reload` 管理 ~~~ # 检测进程是否存在,不会发送信号 Process::kill($pid, 0); # stop Process::kill($pid, SIGTERM); # reload 柔性重启 Process::kill($pid, SIGUSR1); # start /* $swoole = new Swoole\Http\Server("0.0.0.0", 9501,SWOOLE_PROCESS, SWOOLE_SOCK_TCP); */ $swoole->option($this->config); $swoole->start(); # restart # 就是stop之后再start ~~~ 4. 响应事件 这段代码写得可以,值得借鉴 ``` // vendor/topthink/think-swoole/src/Server.php:93 protected $event = ['Start', 'Shutdown', 'WorkerStart', 'WorkerStop', 'WorkerExit', 'Connect', 'Receive', 'Packet', 'Close', 'BufferFull', 'BufferEmpty', 'Task', 'Finish', 'PipeMessage', 'WorkerError', 'ManagerStart', 'ManagerStop', 'Open', 'Message', 'HandShake', 'Request']; // 循环判断,如果有当前回调方法就注册 foreach ($this->event as $event) { if (method_exists($this, 'on' . $event)) { $this->swoole->on($event, [$this, 'on' . $event]); } } ``` ## `Cookie`和`Session` >[danger] 由于`Swoole`的特殊性,扩展本身接管了`Cookie`类和`Session`类的处理,因此不要调用(包括依赖注入)`think\Cookie`和`think\Session`类,而应该改为`think\swoole\Cookie`类和`think\swoole\Session`类。并且所有PHP内置的`session`函数都是无效的,`think-swoole`扩展单独封装了一个`Session`类,和系统的`Session`机制无关。 扩展虽然提供了基于`swoole_table`和`ThinkPHP`缓存的混合解决方案。但建议使用缓存机制来处理`Session`,如果你的应用比较大,则应该配置使用`redis`之类的缓存机制更加适合,直接在你的`cache.php`中定义相关缓存配置即可。 >[info] 其实在实际的项目开发中,更多的是开发api接口,而且都是基于JWT的身份验证,cookie和session反而用得很少。