[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反而用得很少。