东莞市盛裕绒艺玩具有限公司

东莞市盛裕绒艺玩具有限公司

久发国际手机版下载

13918892228
联系方式
全国服务热线: 13918892228

咨询热线:13605032303
联系人:丁铭杰
地址:山西省运城市西花园一巷六号

Openresty的同步输出与流式响应

来源:久发国际手机版下载   发布时间:2019-07-17   点击量:437

Openresty的同步输出与流式响应

默认情况下, ngx.say和ngx.print都是异步输出的,先来看一个例子:

location /test { content_by_lua_block { ngx.say("hello") ngx.sleep(3) ngx.say("the world") }}

执行测试,可以发现首先, /test 响应内容是在触发请求 3s 后一起接收到响应体,第一个ngx.say好像是被“绕过”,先执行sleep,然后和最后一个ngx.say的内容一起输出。

location /test { content_by_lua_block { ngx.say("hello") ngx.flush() -- 显式的向客户端刷新响应输出 ngx.sleep(3) ngx.say("the world") }}

首先输出"hello",然后停顿3秒,最后输出"the world"——正如我们想象的那样。ngx.flush执行显示的输出,前一个ngx.say被“阻塞”住,执行完输出后方往下执行。

再看一个例子:

server { listen 80; lua_code_cache off; location /test { content_by_lua_block { ngx.say(string.rep("hello", 4000)) ngx.sleep(3) ngx.say("the world") } }}

这个例子和第一个例子相比,唯一不同就是ngx.say输出内容长了不少,我们发现浏览器先收到所有的hello,接着又收到了"the world" 。然而如果我们把4000改为小一点的值如2000(不同配置这个相对大小或有不同),那么仍然会出现先停顿3s,然后所有"hello"连同最后"the world"一起输出的情况。

通过以上三个例子,我们可以得出下面的结论:

ngx.say和ngx.print的同步和异步

nginx有个输出缓冲(system send buffer),如16k。ngx.say和ngx.print默认是向这个输出缓冲写入数据,如果没有显示的调用ngx.flush,那么在content阶段结束后输出缓冲会写入客户端;

如果没有ngx.flush也没有到结束阶段,但如果输出缓冲区满了,那么也会输出到客户端;

因此ngx.say和ngx.print的默认向客户端的输出都是异步的非实时性的,改变这一行为的是ngx.flush,可以做到同步和实时输出。这在流式输出,比如下载大文件时非常有用。

ngx.flush的同步和异步

lua-nginx也提到了ngx.flush的同步和异步。某一个ngx.say或者ngx.print调用后,这部分输出内容会写到输出缓冲区,同步的方式ngx.flush(true)会等到内容全部写到缓冲区再输出到客户端,而异步的方式ngx.flush()会将内容一边写到缓冲区,而缓冲区则一边将这些内容输出到客户端。

openresty和nginx流式输出的比较

流式输出,或者大文件的下载,nginx的upstream模块已经做得非常好,可以通过proxy_buffering|proxy_buffer_size|proxy_buffers 等指令精细调控,而且这些指令的默认值已经做了妥善处理。我们来看看这些指令以及默认值:

proxy_buffering on;proxy_buffer_size 4k|8k; proxy_buffers 8 4k|8k; proxy_busy_buffers_size 8k|16k;proxy_temp_path proxy_temp;proxy_buffering on表示内存做整体缓冲,内存不够时多余的存在由proxy_temp_path指定的临时文件中,off表示不做任何输出缓冲,从上游响应中接收一点就向客户端输出一点proxy_buffer_size和proxy_buffers都是指定内存缓冲区的大小,默认为一页的大小,proxy_buffers还可以指定这样的缓冲区的个数proxy_busy_buffers_size 这个"busy"看得出,这个指令一定是用在比较繁忙的时候了。在比较繁忙的时候(高并发或者大文件下载)时,就没有必要等到上游响应全部来了再发给客户端,可以来了一部分(proxy_busy_buffers_size)就发过去。于此同时,缓冲区的另外部分可以继续读。如果内存缓冲区不够用了,还可以开启文件缓冲区proxy_temp_path 使用文件作为接受上游请求的缓冲区buffer,当内存不够用时启用

openresty的怎么做到过大响应的输出呢? 《OpenResty 最佳实践》 提到了两种情况:

输出内容本身体积很大,例如超过 2G 的文件下载输出内容本身是由各种碎片拼凑的,碎片数量庞大

前面一种情况非常常见,后面一种情况比如上游已经开启Chunked的传输方式,而且每片chunk非常小。笔者就遇到了一个上游服务器通过Chunked分片传输日志,而为了节省上游服务器的内存将每片设置为一行日志,一般也就几百字节,这就太“碎片”了,一般日志总在几十到几百M,这么算下来chunk数量多大10w+。笔者用了resty.http来实现文件的下载,文件总大小48M左右。

local http = require "resty.http"local httpc = http.new()httpc:set_timeout(6000)httpc:connect(host, port)local client_body_reader, err = httpc:get_client_body_reader()local res, err = httpc:request({ version = 1.1, method = ngx.var.request_method, path = ngx.var.app_uri, headers = headers, query = ngx.var.args, body = client_body_reader})if not res then ngx.say("Failed to request ".. ngx.var.app_name .." server: ", err) returnend-- Response statusngx.status = res.status-- Response headersfor k, v in pairs(res.headers) do if k ~= "Transfer-Encoding" then --必须删除上游Transfer-Encoding响应头 ngx.header[k] = v endend-- Response bodylocal reader = res.body_readerrepeat local chunk, err = reader(8192) if err then ngx.log(ngx.ERR, err) break end if chunk then ngx.print(chunk) ngx.flush(true) -- 开启ngx.flush,实时输出 enduntil not chunklocal ok, err = httpc:set_keepalive()if not ok then ngx.say("Failed to set keepalive: ", err) returnend

多达10w+的"碎片"的频繁的调用ngx.pirnt()和ngx.flush(true),使得CPU不堪重负,出现了以下的问题:

CPU轻轻松松冲到到100%,并保持在80%以上由于CPU的高负荷,实际的下载速率受到显著的影响并发下载及其缓慢。笔者开启到第三个下载连接时基本就没有反应了

这是开启了ngx.flush(true)的情况(ngx.flush()时差别不大),如果不开启flush同步模式,则情况会更糟糕。CPU几乎一直维持在100%左右:

可见,在碎片极多的流式传输上,以上官方所推荐的openresty使用方法效果也不佳。

于是,回到nginx的upstream模块,改content_by_lua_file为proxy_pass再做测试,典型的资源使用情况为:

无论是CPU还是内存占用都非常低,开启多个下载链接后并无显著提升,偶尔串升到30%但迅速下降到不超过10%。

因此结论是,涉及到大输出或者碎片化响应的情况,最好还是采用nginx自带的upstream方式,简单方便,精确控制。而openresty提供的几种方式,无论是异步的ngx.say/ngx.print还是同步的ngx.flush,实现效果都不理想。

, 1, 0, 9);

相关产品

COPYRIGHTS©2017 久发国际手机版下载 ALL RIGHTS RESERVED 备案号:437