上线了一个基于 swoole http server 的服务以后,发现这个服务的请求耗时监控毛刺十分严重,接口耗时波动比较大,经过一段时间的分析,发现这个服务 response 包十分大,有些 response 包高达1 ~ 2M,甚至更大,这样就很清楚了,因为包太多,导致服务相应波动比较大。

这里稍微解释下,为什么 response 包会导致相应时间波动,这里主要有两个方面的影响,第一是包大会导致 swoole 之间进程通信更加耗时,并占用更多资源,第二是包大会导致 swoole 的 reactor 线程发包更加耗时,关于 reactor 的解释,摘自

Swoole的主进程是一个多线程的程序。其中有一组很重要的线程,称之为Reactor线程。它就是真正处理TCP连接,收发数据的线程。 Swoole的主线程在Accept新的连接后,会将这个连接分配给一个固定的Reactor线程,并由这个线程负责监听此socket。在socket可读时读取数据,并进行协议解析,将请求投递到Worker进程。在socket可写时将数据发送给TCP客户端。

那么怎么优化呢?

其实很简单,那就是直接在 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 分段发送,上线之后效果很明显,耗时监控毛刺少了很多。

有些机智的同学或许会有这个疑问,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 也同样适用。