Swoole在2.0開始內(nèi)置協(xié)程(Coroutine)的能力,提供了具備協(xié)程能力IO接口(統(tǒng)一在名空間Swoole\Coroutine\*
)。
2.0.2或更高版本已支持PHP7
協(xié)程可以理解為純用戶態(tài)的線程,其通過協(xié)作而不是搶占來進行切換。相對于進程或者線程,協(xié)程所有的操作都可以在用戶態(tài)完成,創(chuàng)建和切換的消耗更低。Swoole可以為每一個請求創(chuàng)建對應的協(xié)程,根據(jù)IO的狀態(tài)來合理的調(diào)度協(xié)程,這會帶來了以下優(yōu)勢:
開發(fā)者可以無感知的用同步的代碼編寫方式達到異步IO的效果和性能,避免了傳統(tǒng)異步回調(diào)所帶來的離散的代碼邏輯和陷入多層回調(diào)中導致代碼無法維護。
同時由于swoole是在底層封裝了協(xié)程,所以對比傳統(tǒng)的php層協(xié)程框架,開發(fā)者不需要使用yield關鍵詞來標識一個協(xié)程IO操作,所以不再需要對yield的語義進行深入理解以及對每一級的調(diào)用都修改為yield,這極大的提高了開發(fā)效率。
協(xié)程API目前針對了TCP,UDP等主流協(xié)議client的封裝,包括:
可以滿足大部分開發(fā)者的需求。對于私有協(xié)議,開發(fā)者可以使用協(xié)程的TCP或者UDP接口去方便的封裝。
swoole_server
或者swoole_http_server
進行開發(fā),目前只支持在onRequet
, onReceive
, onConnect
事件回調(diào)函數(shù)中使用協(xié)程。swoole2.0需要通過添加--enable-coroutine
編譯參數(shù)啟用協(xié)程能力,示例如下:
phpize
./configure --with-php-config={path-to-php-config} --enable-coroutine
make
make install
添加編譯參數(shù),swoole server將切換到協(xié)程模式。
開啟協(xié)程模式后,swoole_server
和swoole_http_server
將以為每一個請求創(chuàng)建對應的協(xié)程,開發(fā)者可以在onRequet
、onReceive
、onConnect
3個事件回調(diào)中使用協(xié)程客戶端。
在Swoole\Server
的set方法中增加了一個配置參數(shù)max_coro_num
,用于配置一個worker進程最多同時處理的協(xié)程數(shù)目。因為隨著worker進程處理的協(xié)程數(shù)目的增加,其占用的內(nèi)存也會增加,為了避免超出php的memory_limit
限制,請根據(jù)實際業(yè)務的壓測結(jié)果設置該值,默認為3000。
當代碼執(zhí)行到connect()和recv()
函數(shù)時,swoole會觸發(fā)進行協(xié)程切換,此時swoole可以去處理其他的事件或者接受新的請求。當此client連接
成功或者后端服務回包
后,swoole server會恢復協(xié)程上下文,代碼邏輯繼續(xù)從切換點開始恢復執(zhí)行。開發(fā)者整個過程不需要關心整個切換過程。具體使用可以參考client的文檔。
__call()
dereferencing pointer ‘v.327’ does break strict-aliasing rules
、dereferencing type-punned pointer will break strict-aliasing rules
請手動編輯Makefile,將CFLAGS = -Wall -pthread -g -O2
替換為CFLAGS = -Wall -pthread -g -O2 -fno-strict-aliasing
,然后重新編譯make clean;make;make install
bool getDefer();
bool setDefer([bool $is_defer = true]);
mixed recv();
在協(xié)程版本的Client中,實現(xiàn)了多個客戶端并發(fā)的發(fā)包功能。
通常,如果一個業(yè)務請求中需要做一次redis請求和一次mysql請求,那么網(wǎng)絡IO會是這樣子:
redis發(fā)包->redis收包->mysql發(fā)包->mysql收包
以上流程網(wǎng)絡IO的時間就等于 redis網(wǎng)絡IO時間 + mysql網(wǎng)絡IO時間。
而對于協(xié)程版本的Client,網(wǎng)絡IO可以是這樣子:
redis發(fā)包->mysql發(fā)包->redis收包->mysql收包
以上流程網(wǎng)絡IO的時間就接近于 MAX(redis網(wǎng)絡IO時間, mysql網(wǎng)絡IO時間)。
現(xiàn)在支持并發(fā)請求的Client有:
除了Swoole\Coroutine\Client,其他Client都實現(xiàn)了defer特性,用于聲明延遲收包。
因為Swoole\Coroutine\Client的發(fā)包和收包方法是分開的,所以就不需要實現(xiàn)defer特性了,而其他Client的發(fā)包和收包都是在一個方法中,所以需要一個setDefer()方法聲明延遲收包,然后通過recv()方法收包。
協(xié)程版本Client并發(fā)請求示例代碼:
<?php
$server = new Swoole\Http\Server("127.0.0.1", 9502, SWOOLE_BASE);
$server->set([
'worker_num' => 1,
]);
$server->on('Request', function ($request, $response) {
$tcpclient = new Swoole\Coroutine\Client(SWOOLE_SOCK_TCP);
$tcpclient->connect('127.0.0.1', 9501,0.5)
$tcpclient->send("hello world\n");
$redis = new Swoole\Coroutine\Redis();
$redis->connect('127.0.0.1', 6379);
$redis->setDefer();
$redis->get('key');
$mysql = new Swoole\Coroutine\MySQL();
$mysql->connect([
'host' => '127.0.0.1',
'user' => 'user',
'password' => 'pass',
'database' => 'test',
]);
$mysql->setDefer();
$mysql->query('select sleep(1)');
$httpclient = new Swoole\Coroutine\Http\Client('0.0.0.0', 9599);
$httpclient->setHeaders(['Host' => "api.mp.qq.com"]);
$httpclient->set([ 'timeout' => 1]);
$httpclient->setDefer();
$httpclient->get('/');
$tcp_res = $tcpclient->recv();
$redis_res = $redis->recv();
$mysql_res = $mysql->recv();
$http_res = $httpclient->recv();
$response->end('Test End');
});
$server->start();
Swoole2.0基于setjmp
、longjmp
實現(xiàn),在進行協(xié)程切換時會自動保存Zend VM的內(nèi)存狀態(tài)(主要是EG全局內(nèi)存和vm stack)。
$server = new Swoole\Http\Server('127.0.0.1', 9501, SWOOLE_BASE);
#1
$server->on('Request', function($request, $response) {
$mysql = new Swoole\Coroutine\MySQL();
#2
$res = $mysql->connect([
'host' => '127.0.0.1',
'user' => 'root',
'password' => 'root',
'database' => 'test',
]);
#3
if ($res == false) {
$response->end("MySQL connect fail!");
return;
}
$ret = $mysql->query('show tables', 2);
$response->end("swoole response is ok, result=".var_export($ret, true));
});
$server->start();
onRequest
事件回調(diào)函數(shù)時,底層會調(diào)用C函數(shù)coro_create
創(chuàng)建一個協(xié)程(#1位置),同時保存這個時間點的CPU寄存器狀態(tài)和ZendVM stack信息。mysql->connect
時發(fā)生IO操作,底層會調(diào)用C函數(shù)coro_save
保存當前協(xié)程的狀態(tài),包括Zend VM上下文以及協(xié)程描述信息,并調(diào)用coro_yield
讓出程序控制權(quán),當前的請求會掛起(#2位置)core_resume
恢復對應的協(xié)程,恢復ZendVM上下文,繼續(xù)向下執(zhí)行PHP代碼(#3位置)mysql->query
的執(zhí)行過程與mysql->connect
一致,也會進行一次協(xié)程切換調(diào)度end
方法返回結(jié)果,并銷毀此協(xié)程相比普通的異步回調(diào)程序,協(xié)程多增加額外的內(nèi)存占用。
Ubuntu16.04 + Core I5 4核 + 8G內(nèi)存 PHP7.0.10
ab -c 100 -n 10000 http://127.0.0.1:9501/
測試結(jié)果:
Server Software: swoole-http-server
Server Hostname: 127.0.0.1
Server Port: 9501
Document Path: /
Document Length: 348 bytes
Concurrency Level: 100
Time taken for tests: 0.883 seconds
Complete requests: 10000
Failed requests: 168
(Connect: 0, Receive: 0, Length: 168, Exceptions: 0)
Total transferred: 4914560 bytes
HTML transferred: 3424728 bytes
Requests per second: 11323.69 [#/sec] (mean)
Time per request: 8.831 [ms] (mean)
Time per request: 0.088 [ms] (mean, across all concurrent requests)
Transfer rate: 5434.67 [Kbytes/sec] received
Connection Times (ms)
min mean[+/-sd] median max
Connect: 0 0 0.2 0 2
Processing: 0 9 9.6 6 96
Waiting: 0 9 9.6 6 96
Total: 0 9 9.6 6 96
Percentage of the requests served within a certain time (ms)
50% 6
66% 9
75% 11
80% 12
90% 19
95% 27
98% 43
99% 51
100% 96 (longest request)
更多建議: