git 地址:https://github.com/pintsized/lua-resty-http

有个项目,需要用到openresty做代理。代码如下:

local reader = res_c.body_reader
repeat
    local chunk, err = reader(65536) --1m
    if err then
        ngx.log(ngx.ERR, err)
        break
    end
    if chunk then
        -- notice: ngx.say will emit a trailing newline. 
        ngx.print(chunk)
    end
until not chunk

这段代码,在请求小文件时,工作的挺好。一旦请求体积大的媒体内容,问题就暴露出来了。

http 响应头如下:

HTTP/1.1 200 OK
Access-Control-Allow-Methods: GET
Access-Control-Allow-Origin: *
Content-Type: application/octet-stream
Connection: close
Content-Length: 2118371780

可以看到该文件有2G左右,并且未使用chunked编码。
文档中,对于函数body_reader的介绍如下:

If the reader is called with no arguments, the behaviour depends on the type of connection. If the response is encoded as chunked, then the iterator will return the chunks as they arrive. If not, it will simply return the entire body.

这样一来,nginx会缓存整个body体(直接占用2G内存)。直到客户端请求结束才会释放。如果并发一大,nginx就会应为内存不足的原因异常关闭。

于是乎,官方建议在处理大体积的流媒体内容时,ngx.print()ngx.flush() 一起使用。修改后,代码如下:

local reader = res_c.body_reader
repeat
    local chunk, err = reader(65536) --1m
    if err then
        ngx.log(ngx.ERR, err)
        break
    end
    if chunk then
        -- notice: ngx.say will emit a trailing newline. 
        ngx.print(chunk)
        ngx.flush(true)
    end
until not chunk

When ngx.flush(true) is called immediately after ngx.print or ngx.say, it causes the latter functions to run in synchronous mode. This can be particularly useful for streaming output.

在实际测试中,发现这段代码确实让内存占用率降下去了。但是由于ngx.flush() 的频繁调用,单个连接居然占到了10~20%的cpu利用率。并发一大,同样没得玩。

遂想到nginx 的 http-proxy 模块。
这里大概介绍一下proxy的工作原理。nginx在和后端server建立连接后,proxy模块会在本地生成一个临时文件。将后端的server的响应写入,然后将已写入的body返回给客户端。这样一来,cpu 和 内存占用率都降下来了(但是在请求开始阶段会出现CPU抖动)。当然磁盘io会上去,但是这个好解决多了。除此之外http-proxy 还有许多其他功能(例如限速,缓存参数调节等)可以使用,简直既优雅又强大。

但是一般proxy_pass 是配合upstream 模块使用。当然直接在proxy_pass 后面接上服务器地址也是可以的。

注意: proxy_pass 不能喝 content_by_lua* 混用。

修改后代码如下:

# nginx.conf

location ~ ^/bigfile {
    set $upstream "";
    access_by_lua_block {
        local args = ngx.req.get_uri_args()
        local newLimit = args["limit"]
        if newLimit then
            ngx.var.limit_rate = newLimit
        end
    }
    rewrite_by_lua_file /usr/local/openresty/site/lualib/axiba.lua;
    proxy_pass $upstream;
}

可以在rewirte 块里面重写$upstream和url. 可以说是非常方便了。

local redirect_to_content = "http://172.16.6.1/test/1.ts?limit=102400"
local http_c = http.new()
local scheme, host, port, path, querys = unpack(http_c:parse_uri(redirect_to_content, false))

ngx.var.upstream = scheme.."://"..host..":"..port

if querys and querys ~= "" then
    ngx.req.set_uri_args(querys)
end

ngx.req.set_uri(path)
ngx.log(ngx.INFO, "request url: "..ngx.var.upstream..ngx.var.uri.."?"..querys)

实际测试的时候,情况好了不少。是比http-resty-lua更好的选择,但是配置更麻烦了点。如果遇上后端要做负载均衡的话,那还是绕不开upstream

Logo

旨在为数千万中国开发者提供一个无缝且高效的云端环境,以支持学习、使用和贡献开源项目。

更多推荐