• 实现原理
    • 示例代码
    • 运行过程
    • 协程开销
    • 压力测试

    实现原理

    Swoole-2.0基于setjmplongjmp实现,在进行协程切换时会自动保存Zend VM的内存状态(主要是EG全局内存和vm stack)。

    • setjmplongjmp主要是用于从ZendVMC堆栈跳回SwooleC回调函数
    • 协程的创建、切换、挂起、销毁全部为内存操作,消耗是非常低的
      Swoole-4.0重构了协程内核,实现了C栈和PHP栈同时保存和切换,支持所有PHP语法。

    示例代码

    1. $server = new Swoole\Http\Server('127.0.0.1', 9501, SWOOLE_BASE);
    2. #1
    3. $server->on('Request', function($request, $response) {
    4. $mysql = new Swoole\Coroutine\MySQL();
    5. #2
    6. $res = $mysql->connect([
    7. 'host' => '127.0.0.1',
    8. 'user' => 'root',
    9. 'password' => 'root',
    10. 'database' => 'test',
    11. ]);
    12. #3
    13. if ($res == false) {
    14. $response->end("MySQL connect fail!");
    15. return;
    16. }
    17. $ret = $mysql->query('show tables', 2);
    18. $response->end("swoole response is ok, result=".var_export($ret, true));
    19. });
    20. $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/
      测试结果:
    1. Server Software: swoole-http-server
    2. Server Hostname: 127.0.0.1
    3. Server Port: 9501
    4. Document Path: /
    5. Document Length: 348 bytes
    6. Concurrency Level: 100
    7. Time taken for tests: 0.883 seconds
    8. Complete requests: 10000
    9. Failed requests: 168
    10. (Connect: 0, Receive: 0, Length: 168, Exceptions: 0)
    11. Total transferred: 4914560 bytes
    12. HTML transferred: 3424728 bytes
    13. Requests per second: 11323.69 [#/sec] (mean)
    14. Time per request: 8.831 [ms] (mean)
    15. Time per request: 0.088 [ms] (mean, across all concurrent requests)
    16. Transfer rate: 5434.67 [Kbytes/sec] received
    17. Connection Times (ms)
    18. min mean[+/-sd] median max
    19. Connect: 0 0 0.2 0 2
    20. Processing: 0 9 9.6 6 96
    21. Waiting: 0 9 9.6 6 96
    22. Total: 0 9 9.6 6 96
    23. Percentage of the requests served within a certain time (ms)
    24. 50% 6
    25. 66% 9
    26. 75% 11
    27. 80% 12
    28. 90% 19
    29. 95% 27
    30. 98% 43
    31. 99% 51
    32. 100% 96 (longest request)