[toc]
>[info] 用于替代PHP-FPM同步阻塞的模式。實現異步非阻塞高性能的web服務
## 優勢
* 性能的數十倍的提升;
* 可以在`Apache`/`Nginx`等傳統WEB服務器和`Swoole`之間切換部署;
* **在此模式下,還可以直接在控制器中調用`swoole/server`的方法,比如定時,任務異步投遞,協程等等**
> 本例使用的是thinkphp5.1的擴展包, 本文只做實操記錄與源碼分析,詳細內容查看 [ThinkPHP 5.1 Swoole 快速上手指南](http://www.hmoore.net/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模式下**

**在swoole-httpserver模式下**

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