【云+社区年度征文】swoft2与laravel-swoole选型实践

2020-12-17 16:38:24 浏览数 (2)

因项目需要,需要做php框架的后端技术选型,于是开始着手测试基于swoole的框架swoft与laravel的扩展包laravel-swoole进行评估。

刚开始打算是在cygwin中使用laravel-s这个laravel扩展包,然而报出了一个cli_set_process_title() failed异常。

找了半天原因,从swoole的官方文档中看到,在macOS与低版本的linux系统中,是无法使用cli_set_process_title这个函数的。搜索了半天,也没有找到有效的解决方案,于是最后选择了替代方案:laravel-swoole

测试环境:

阿里云服务器4C8G,数据库与服务器使用内网通信,排除网络io的干扰。

测试环境为线下的测试服务器与测试数据库,测试条件是查询根据传过去的用户uid查出一条用户记录,并返回查询结果,没有使用redismemcache等缓存。

测试工具:

  • ab

查询sql:

代码语言:txt复制
select * from  where id = xxxx 

测试过程中会出现以下问题:

代码语言:txt复制
[2020-12-15 10:43:50 *3602.1]	NOTICE	finish (ERRNO 1004): send 5 bytes failed, because session#2 is closed

这个问题是AB工具本身的问题,具体的原因可以参考:https://wiki.swoole.com/wiki/page/1527.html

benchmark:

  • 测试在持续60秒内在不同的并发数下的效果,具体执行条件为:ab -t 60 -c 2000 http://127.0.0.1:1215/api/user-info/2052,其中c为变量,意思为并发数。

测试效果对比:

  • 并发数为10:
swoft并发10swoft并发10
laravel-swoole并发10laravel-swoole并发10
  • 并发数为100:
swoft并发100swoft并发100
laravel-swoole并发100laravel-swoole并发100
  • 并发数200:
swoft并发200swoft并发200
laravel-swoole并发200laravel-swoole并发200
  • 并发数500:
swoft并发500swoft并发500
laravel-swoole并发500laravel-swoole并发500
  • 并发数1000:
swoft并发1000swoft并发1000
laravel-swoole并发1000laravel-swoole并发1000
  • 并发数1500:
swoft并发1500swoft并发1500
laravel-swoole并发1500laravel-swoole并发1500
  • 并发数2000:
swoft并发2000swoft并发2000
laravel-swoole并发2000laravel-swoole并发2000
  • 并发数3000:
swoft并发3000swoft并发3000
laravel-swoole并发3000laravel-swoole并发3000
  • 并发数5000:
swoft并发5000swoft并发5000
laravel-swoole并发5000laravel-swoole并发5000

运行时数据库状况:

swoft压测时数据库状态swoft压测时数据库状态
laravel-swoole压测时db状态laravel-swoole压测时db状态

关键指标:

  • Complete requests:请求完成数
  • Failed requests:请求失败数
  • Connection Times:网络消耗时间。
  • Time per request(mean): 服务器收到请求后,响应页面的平均时间
  • Time per request(mean, across all concurrent requests): 并发的每个请求平均消耗时间
  • Percentage of the requests served within a certain time (ms): 一定的时间内,完成的请求数所花的时间比。

总结:

  • 从并发的对比图中,从请求成功数请求失败数来看,swoftlaravel-swoole相比,成功率较高;从网络消耗时间对比,由于有swoft有连接池的存在,明显可以看出,网络IO的时间要优于laravel-swoole;从响应页面的平均时间并发的每个请求平均消耗时间看,swoft性能还是强于laravel-swoole;从一定的时间内,完成的请求数所花的时间比swoft大部分的情况下,处理完成的平均处理时间是优于laravel-swoole。但是随着并发数的上升,请求的最大处理时间与laravel-swoole对比,即最完成全部请求来需要花费的时间,性能相对来说差,综合性能上来看,swoft有一定的优势。

没覆盖测试到的:laravel-swoole加上数据库连接池中间件之后的效果。

从初步使用体验看,swoft要求更高,约束更强,特别是引入了注解概念,所谓I注解即路由,增强了代码的简洁性,同时牺牲了代码的可读性。

语法上,使用PHP7的强类型语法约束与模型数据字段的映射,好处是增强了代码的稳健性,但是缺点也很明显:降低了php的开发效率

swoft文档比较简单,没有过多的停留在概念性解释上面,结合在搭建测试环境中遇到的问题,坑还是有不少,相关的搜索结果与laravel相比会少很多,有些问题可能会需要从框架源码着手解决,因此对使用者会有一定的要求。

数据交换上,swoft提供httprpcwebsocket等支持,不再需要再引入第三方依赖,而laravel-swoole作为laravel的扩展包,主要是支持http;在事件的支持上,swoftlaravel都支持同步与异步的事件驱动,在异步处理方面,swoft是基于swoft的协程,而laravel是基于队列。

数据库驱动上,目前swoft官方的文档上只有mysqlredis的驱动,如果项目中有用到mongoDBPostgreSQLSSDB等其他数据库则需要使用第三方的轮子或自己造。

附测试使用swoft遇到的一个有意思的问题:

  • 开启协程有srunsgo,两者有何不同? sgo:开启新协程。 srun:启动协程并等待执行结束。 文档在这一点没说清楚,对两者的说明,网上搜索也没几个相关内容。

在swoft的命令行测试对比的结果:

代码语言:txt复制
echo 'begin'.PHP_EOL;

sgo(function(){
Co::sleep(2);
echo "middle".PHP_EOL;
});

echo "end".PHP_EOL;

此时输出:

代码语言:txt复制
begin
end
middle

如果换成:

代码语言:txt复制
echo 'begin'.PHP_EOL;
srun(function(){
    Co::sleep(2);
    echo "middle".PHP_EOL;
    return true;
});
echo "end".PHP_EOL

那么此时输出:

代码语言:txt复制
begin
[WARNING] SwoftCo:run(83) Already is in coroutine, not need to use `run`!
middle
end

从上面对比看出,顺序执行了(即已经做了同步),但是会抛出一个警告,已经是协程环境不要使用run方法,这可能就是框架作者反复强调再次强调,框架中只能使用 sgo 函数创建协程。的原因之一。因此,我们只能用sgo方法在框架内开协程,srun方法的应用场景更多的应该是在自定义进程等非框架内使用的。

可是如果我既想做顺序输出又不想抛出这个警告呢?

显然我们会注意到sgo方法会有第二个入参$wait

然而,在框架文档里没有解释的,sgo方法$wait到底是嘛玩意?

既然默认是false,什么情况应该用true呢?既然文档没有,那么只能看源代码了。

跟到代码里面去会发现这么一段:

代码语言:txt复制
return Coroutine::create(function () use ($callable, $tid, $wait) {
            // Current cid
            $id = Coroutine::getCid();
            try {
                // Storage fd
                self::$mapping[$id] = $tid;
                if ($wait) {
                    Context::getWaitGroup()->add();
                }

                PhpHelper::call($callable);
            } catch (Throwable $e) {
                Error::log(
                    "Coroutine internal error: %snAt File %s line %dnTrace:n%s",
                    $e->getMessage(),
                    $e->getFile(),
                    $e->getLine(),
                    $e->getTraceAsString()
                );

                // Trigger co error event
                Swoft::trigger(SwoftEvent::COROUTINE_EXCEPTION, $e);
            }

            if ($wait) {
                // Trigger defer
                Swoft::trigger(SwoftEvent::COROUTINE_DEFER);

                Context::getWaitGroup()->done();
            }

看到了熟悉的waitgroupwaitgroupswoole的文档中就提到了它的作用,用来了做同步的, 一般操作有3个方法adddone,以及用来同步等待的wait。那么同理,它在这里,肯定也是用来做同步的。那么让我们试试,看不能能输出预期的:

代码语言:txt复制
begin
middle
end

代码改成如下:

代码语言:txt复制
echo 'begin'.PHP_EOL;
sgo(function(){
    Co::sleep(2);
    echo "middle".PHP_EOL;
},true);
echo "end".PHP_EOL;

然而,输出结果并没有如预期,实际输出:

代码语言:txt复制
begin
end
middle

等等,回过头去看sgo方法的实现,好像它这里少了点什么?

是的,没有看到哪里调用wait方法做同步。

于是,我们跟到Context::getWaitGroup()里面去看代码:确实里面有个wait方法。我们把这个函数加进去看看效果:

代码语言:txt复制
echo 'begin'.PHP_EOL;
sgo(function(){
    Co::sleep(2);
    echo "middle".PHP_EOL;
},true);
Context::getWaitGroup()->wait();
echo "end".PHP_EOL;

实际输出:

代码语言:txt复制
begin
middle
end

符合我们的预期,也就是意味着,如果我们在sgo方法有同步需求的时候,需要自己手动在业务代码处调用Context::getWaitGroup()->wait();这一个方法使数据同步。

0 人点赞