项目切换到Swoole实践
项目背景
用PHP封装的一套接口系统。主要实现调用第三方API接口并按自定义协议解析后返回给上层。通常情况PHP实现的项目效率问题不那么明显,但这种接口调用场景下性能问题变得很突出。由于PHP采用的php-fpm的方式执行,系统一般会启动N个进程解析PHP脚本,但当某个请求延时严重的时候,处理请求的php-fpm只能阻塞不能处理其他请求。所以,
- 如果第三方API出现大批量超时,大量的php-fpm进程会被阻塞
- 由于php-fpm阻塞,系统启动的php-fpm进程很容易耗尽,处理不了新的请求
- 这时CPU资源也在不断进行进程切换,导致CPU异常高
服务的可用性大大折扣,可用性也直接跟对方系统强关联。而此系统又是核心业务,通过增加机器的方式资源利用率极低,线上已有10多台机器用于此服务。为了达到服务的可用性指标,做了一些尝试:
- 优化PHP参数。调整PHP的启动方式,静态的、动态的。
- 熔断处理。对响应时长进行监控,当发现对方异常时,直接熔断请求不发送到第三方。此策略效果比较明显,对稳定性有较大提升,但吞吐量还是很有效。
- 对系统里某些请求、业务逻辑进行优化。
除了熔断之外,其他点对服务稳定性的帮助很微弱,问题跟PHP的进程模型有关系。所以考虑在一定程度上尝试项目重构。
- 已经对接很多第三方接口,重写的成本很大。请求量主要集中在个别接口,即便对此类接口进行重写,规模也不小。所以GO、Python这些语言对系统并不是最好的方案。
- 在项目早期,有一版是基本swoole实现,swoole还是1.8左右的版本。但上线后会出现窜请求的情况,回滚了。所以对swoole的选择是有顾虑的。
徘徊一圈后还是尝试在试一下swoole。
Swoole重构
Swoole已经是4.3.x的版本,在使用之前做了个初步的压测,相比前一次Swoole的评估好了很多,没有出现明显问题。重构主要是框架层面,尽量不对业务代码的实现做调整,前期的开发过程相对来说还好,需要注意的是:
- Swoole是常驻进程,协程之间的数据共享需要注意并发读写的问题,尽量不使用全局变量。
- 有些第三方是用SOAP请求,但SOAP里貌似有个潜在的BUG,某些情况下捕获不到异常,这个时候进程就退出了。
总体上,对照文档,了解下服务端编程的注意事项,问题不大。但接下来需要做测试,确保服务的稳定系和功能的准确性。所以尝试做压测。
- 写压测脚本,持续请求,看长时间运行下的CPU、内存消耗情况
- HTTP、HTTPS的压测,看HTTP、HTTPS的资源使用情况
压力测试
前面做评估的时候业务代码较少,嵌入业务代码后压测还真出问题了,4.3.4
当访问量大的时候主进程挂了,这意味着服务就完全不可用了。心都凉了一截。一方面复现会麻烦,另一方面Swoole里做了封装,如果是swoole层级的问题很难定位,于是在Github
上提Issue
找解决思路。也尝试将版本切换到4.2.3过,没有出现主进程退出的问题,但执行一段时间后会有连接不释放的问题,CPU会跑满,即便停止请求,连接也不会释放。后面给Rango
提供日志,调试,大概定位到中间版本有问题,对方修复后发布4.3.5
,这个问题算是告一段落。
灰度上线
评估后尝试上线,两个版本同时运行,将部分请求切换到Swoole机器。SOAP这类请求就还是回到老机器。上线还比较顺利,资源的利用率上还不错,初步评估一台16核16G的机器,可以跑到800到1000QPS。比之前不稳定的1到200QPS提升不少。
上线后发现过某些请求丢掉的情况,是数据包太大的问题,调整了buffer_output_size
参数,正当窃喜的时候,遇到一个新的问题。
用Coroutine\Http\Client
发请求, 会存在少量状态码返回-3的情况,很快就断开了。客户端请求发出后,服务器强制切断连接。但PHP版本CURL方式的调用上没有出现这个问题。不确定是服务层面的拒绝还是Swoole的问题,要抓异常包也很麻烦,期间做了很多尝试。
- 1、尝试做了重复发送,多次重复请求后,会少一些,但还是会出现这种情况。
- 2、尝试设置了backlog为65535, ss -lt也查看生效了。 设置前后-3的情况变少了一些。
- 3、尝试抓异常包,看起来服务端收到了请求并且有返回数据,但是在返回数据过程中客户端向服务端发了RST响应,还是客户端的问题。
- 4、最后尝试调试swoole源代码,找到对应错误码的地方编译后定位到位置
配合Rango
定位,排查到是底层使用了全局变量,导致并发的时候被其他的协程改写了。
正式上线
经过上个问题后,服务顺利上线了,当然还有一些功能层面的测试,但相比性能这块自己更可控。上线后整体吞吐量提升不少。调用方的QPS翻倍,机器资源减少一半。这还没完,运行一段时间后,发现会存在某个worker死锁的情况,需要重启整个服务才行。影响相对较小一些。后来Rango
在4.4的版本把底层共享内存都移除了。切到4.4.15
后没再出现。
(运行一年左右了,比较稳定)
感谢Rango
的耐心指导,Swoole目前的思路感觉是对的,后续的版本应该会越来越好。Python里有gevent,可方便的实现异步IO。swoole也是在不影响使用的情况下协程化。
总结
- PHP在Web领域还不错,但php-fpm的架构限定了他的一些场景。不知道以后有没有可能实现不阻塞的模式。
- 面对服务的高可用,在机器层面我们可以做SLB、弹性收缩,但应用层面,可以考虑下限流、熔断、隔离等常用措施。
2024-08-17 14:44
2019-10-12 07:40