上线了一个基于 swoole http server 的服务之后,发现这个服务的请求耗时监控毛刺十分严重,接口耗时波动比较大,通过一段时间的分析,发现这个服务 response 包十分大,有些 response 包高达1 ~ 2M,甚至更大,这样就很清楚了,由于包太多,致使服务相应波动比较大。php
这里稍微解释下,为何 response 包会致使相应时间波动,这里主要有两个方面的影响,第一是包大会致使 swoole 之间进程通讯更加耗时,并占用更多资源,第二是包大会致使 swoole 的 reactor 线程发包更加耗时,关于 reactor 的解释,摘自react
Swoole的主进程是一个多线程的程序。其中有一组很重要的线程,称之为Reactor线程。它就是真正处理TCP链接,收发数据的线程。 Swoole的主线程在Accept新的链接后,会将这个链接分配给一个固定的Reactor线程,并由这个线程负责监听此socket。在socket可读时读取数据,并进行协议解析,将请求投递到Worker进程。在socket可写时将数据发送给TCP客户端。nginx
那么怎么优化呢?swoole
其实很简单,那就是直接在 swoole 里开启 gzip,这里摘自 swoole 文档网络
启用Http GZIP压缩。压缩能够减少HTML内容的尺寸,有效节省网络带宽,提升响应时间。必须在write/end发送内容以前执行gzip,不然会抛出错误。swoole_http_response->gzip(int $level = 1);$level 压缩等级,范围是1-9,等级越高压缩后的尺寸越小,但CPU消耗更多。默认为1,调用gzip方法后,底层会自动添加Http编码头,PHP代码中不该当再行设置相关Http头多线程
刚开始我是采用这个方式,确实有些效果,可是效果还不是很明显,而后我想到了一个新的方案,就是 http chunk + gzip,这个 swoole 自己是仅提供了chunk的支持,chunk+gzip须要本身实现,实现也很简单架构
swoole_http_response->header('Content-Encoding', "gzip"); $content = gzencode($content, 6); $arr = str_split($content, 1024); foreach ($arr as $v) { swoole_http_response->write($v); } swoole_http_response->end();
使用php压缩相应内容,而后使用 swoole_http_response->write 分段发送,上线以后效果很明显,耗时监控毛刺少了不少。socket
有些机智的同窗或许会有这个疑问,swoole http server 通常状况下,会被 nginx 反向代理,nginx 广泛会打开 gzip,那么问题来了,swoole 把数据 gzip 了,nginx 会不会把数据二次压缩。函数
固然不会了,这但是nginx,来,虽然nginx的文档里并无说明gzip不会被二次压缩,可是我在源码里找到了相关逻辑优化
src/http/modules/ngx_http_gzip_filter_module.c,略去无关代码,下面函数调用ngx_http_gzip_ok检测是不是gzip
static ngx_int_t ngx_http_gunzip_header_filter(ngx_http_request_t *r) { …… if (!r->gzip_tested) { if (ngx_http_gzip_ok(r) == NGX_OK) { return ngx_http_next_header_filter(r); } } else if (r->gzip_ok) { return ngx_http_next_header_filter(r); } …… }
src/http/ngx_http_core_module.c,略去无关代码,ngx_http_gzip_ok根据http header检测是不是gzip
ngx_int_t ngx_http_gzip_ok(ngx_http_request_t *r) { …… if (ae->value.len < sizeof("gzip") - 1) { return NGX_DECLINED; } …… }
从上述两块代码能够看出,nginx 并不会对 gzip 的包二次压缩。
因此,放心大胆的使用吧。
最后,虽然这篇文章,讲的是针对swoole http server 作的一个优化,可是这个思路,对于其余http server 也一样适用。
更多架构、PHP、GO相关踩坑实践技巧请关注个人公众号:PHP架构师