jinhailang / blog

技术博客:知其然,知其所以然
https://github.com/jinhailang/blog/issues
60 stars 6 forks source link

OpenResty 转发请求超时问题分析报告 #45

Open jinhailang opened 5 years ago

jinhailang commented 5 years ago

问题描述

某产品对接最新版 waf proxy(支持 http2.0)后,上传图片超时,其他请求正常。

问题分析

从 Nginx 的 error 和 access 日志发现,请求转发到 WAF 超时(超过 60 秒),导致客户端主动断开连接(499 状态)。 为了兼容 http2(构建子请求时,将http2 改写成 http1.1),我们使用开源库 resty.http 代替了原来 Openresty 原生的 API ngx.location.capture,性能下降很大,因为 ngx.location.capture 是 Nginx 子请求,本质上不是 http 请求,直接运行在 C 语言级别,效率非常高。当 POST body 数据较大时,这个库转发 的速度会非常慢,从而引起超时。

经过多次对比测试发现,跟 client_body_buffer_size 设置的大小有关,当请求 body 大于 client_body_buffer_size 设置的值时,请求就会变得很慢,甚至超时;反之则正常。 client_body_buffer_size 用来指定 Nginx 读取请求 body 的 buffer 大小值,默认是 8k(32位操作系统)当请求的 body 大于这个值,则会将剩余的字节写入本地文件(磁盘)。所以,根本原因是 resty.http 库转发请求读写磁盘文件效率太低。

解决方案

可以从两方面进行优化解决:

更多

问题相关代码片段

access_by_lua_block {
            local http = require "resty.http"

            local httpc = http.new()
            httpc:set_timeout(300)
            httpc:set_proxy_options({http_proxy = "http://127.0.0.1:9107"})

            ngx.req.read_body()

            local req_method = ngx.req.get_method()
            local req_body = ngx.req.get_body_data()
            local req_headers = ngx.req.get_headers()
            local req_url = "http://" .. ngx.var.host .. "/__to_waf__" .. ngx.var.uri
            if ngx.var.is_args == "?" then
                req_url = req_url .. "?" .. ngx.var.query_string
            end

            local res, err = httpc:request_uri(req_url, {
                version = 1.1,
                method = req_method,
                body = req_body,
                headers = req_headers,

                keepalive_timeout = 60,
                keepalive_pool = 16,
            })

            if not res then
                ngx.log(ngx.ERR, "failed to request: ", err, ". url: ", req_url)
                return ngx.exit(ngx.OK)
            end

            local status = res.status or 0
            local body = res.body

            if status == 200 then
                if not body or body == "" then
                    return ngx.exit(ngx.HTTP_CLOSE)
                end
            elseif status == 403 or status == 400 then
                ngx.req.set_method(ngx.HTTP_GET)
                return ngx.exec("/__waf_page__/" .. status .. ".html")
            end
        }