- 实现原理
- 示例代码
- 运行过程
- 协程开销
- 压力测试
实现原理
Swoole-2.0
基于setjmp
、longjmp
实现,在进行协程切换时会自动保存Zend VM
的内存状态(主要是EG
全局内存和vm stack
)。
setjmp
和longjmp
主要是用于从ZendVM
的C
堆栈跳回Swoole
的C
回调函数- 协程的创建、切换、挂起、销毁全部为内存操作,消耗是非常低的
Swoole-4.0
重构了协程内核,实现了C
栈和PHP
栈同时保存和切换,支持所有PHP
语法。
示例代码
$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();
- 此程序仅启动了一个1个进程,就可以并发处理大量请求。
- 程序的性能基本上与异步回调方式相同,但是代码完全是同步编写的
运行过程
- 调用
onRequest
事件回调函数时,底层会调用C函数coro_create
创建一个协程(#1位置),同时保存这个时间点的CPU寄存器状态和ZendVM stack信息。 - 调用
mysql->connect
时发生IO操作,底层会调用C函数coro_save
保存当前协程的状态,包括Zend VM上下文以及协程描述信息,并调用coro_yield
让出程序控制权,当前的请求会挂起(#2位置) - 协程让出程序控制权后,会继续进入EventLoop处理其他事件,这时Swoole会继续去处理其他客户端发来的Request
- IO事件完成后,MySQL连接成功或失败,底层调用C函数
coro_resume
恢复对应的协程,恢复ZendVM上下文,继续向下执行PHP代码(#3位置) mysql->query
的执行过程与mysql->connect
一致,也会进行一次协程切换调度- 所有操作完成后,调用
end
方法返回结果,并销毁此协程
协程开销
相比普通的异步回调程序,协程多增加额外的内存占用。
Swoole4
协程需要为每个并发保存zend stack
栈内存并维护对应的虚拟机状态。如果程序并发很大可能会占用大量内存,取决于C
函数、PHP
函数调用栈深度- 协程调度会增加额外的一些
CPU
开销,可使用官方提供的 协程切换压测脚本 测试性能
在一台ThinkPad T470p
笔记本电脑上进行测试切换1000万次
,需要耗时1.66s
,平均每次协程切换耗时165ns~175ns
之间。
压力测试
- 环境:
Ubuntu16.04 + Core I5 4核 + 8G内存 PHP7.0.10
- 脚本:
ab -c 100 -n 10000 http://127.0.0.1:9501/
测试结果:
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)