Openresty的同步输出与流式响应
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_buffer_size和proxy_buffers都是指定内存缓冲区的大小,proxy_buffer_size通常缓冲响应头,proxy_buffers缓冲响应内容,默认为一页的大小,proxy_buffers还可以指定这样的缓冲区的个数
- proxy_busy_buffers_size nginx在试图缓冲整个响应过程中,可以让缓冲区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)
return
end
-- Response status
ngx.status = res.status
-- Response headers
for k, v in pairs(res.headers) do
if k ~= "Transfer-Encoding" then --必须删除上游Transfer-Encoding响应头
ngx.header[k] = v
end
end
-- Response body
local reader = res.body_reader
repeat
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,实时输出
end
until not chunk
local ok, err = httpc:set_keepalive()
if not ok then
ngx.say("Failed to set keepalive: ", err)
return
end
多达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,实现效果都不理想。
Openresty的同步输出与流式响应的更多相关文章
- Django的视图流式响应机制
Django的视图流式响应机制 Django的响应类型:一次性响应和流式响应. 一次性响应,顾名思义,将响应内容一次性反馈给用户.HttpResponse类及子类和JsonResponse类属于一次性 ...
- 飘城旅游网pc,流式,响应式布局
相关视频教程http://pan.baidu.com/s/1o77wirK 我的源码链接:http://pan.baidu.com/s/1czTsKI
- 基于grpc的流式方式实现双向通讯(python)
grpc介绍 grpc是谷歌开源的一套基于rpc实现的通讯框架(官网有更完整的定义).在搞懂grpc之前,首先要弄懂rpc是什么.下面是自己理解的rpc定义,若有不对,望指出: rpc官方称为 远程过 ...
- Go gRPC教程-服务端流式RPC(三)
前言 上一篇介绍了简单模式RPC,当数据量大或者需要不断传输数据时候,我们应该使用流式RPC,它允许我们边处理边传输数据.本篇先介绍服务端流式RPC. 服务端流式RPC:客户端发送请求到服务器,拿到一 ...
- CSS3与页面布局学习笔记(四)——页面布局大全(负边距、双飞翼、多栏、弹性、流式、瀑布流、响应式布局)
一.负边距与浮动布局 1.1.负边距 所谓的负边距就是margin取负值的情况,如margin:-100px,margin:-100%.当一个元素与另一个元素margin取负值时将拉近距离.常见的功能 ...
- EasyDSS直播服务器如何帮助用户解决OBS不能同时同步输出多路直播流到直播平台、CDN平台的限制
最近有用户突然寻求帮助,大概的意思就是说: 他需要同步将桌面的直播同时RTMP发布到:斗鱼.熊猫TV等等多个平台,但是OBS又只能同时采集并发布推流直播到单一个平台,而且有时候在4G或者网络比较差的情 ...
- 浅谈静态布局、流式布局,rem布局,弹性布局、响应式布局
静态布局: 特点:没有兼容性问题 PC:居中布局,所有样式使用绝对宽度/高度(px),设计一个Layout,在屏幕宽高有调整时,使用横向和竖向的滚动条来查阅被遮掩部分:移动设备:另外建立移动网站,单独 ...
- 流式布局&固定宽度&响应式&rem
我们现在在切页面布局的使用常用的单位是px,这是一个绝对单位,web app的屏幕适配有很多中做法,例如:流式布局.限死宽度,还有就是通过响应式来做,但是这些方案都不是最佳的解决方法. 1.流式布局: ...
- HttpURLConnection的流式输出的缺陷和解决方法
转自:http://www.mzone.cc/article/198.html 最近在用applet写文件上传控件的时候发现使用URLConnection来对服务器进行流式输出时的一些问题.我们通常要 ...
随机推荐
- 前端笔记之Vue(一)初识SPA和Vue&webpack配置和vue安装&指令
一.单页面应用(SPA) 1.1 C/S到B/S页面架构的转变 C/S:客户端/服务器(Client/Server)架构的软件. C/S 软件的特点: ① 从window桌面双击打开 ② 更新的时候会 ...
- CSS 圣杯布局 / 双飞翼布局的实现
工作的越久,有些基础知识我们可能就逐渐淡忘了,今天我们来回顾一下css的圣杯布局和双飞翼布局, 这两个名词你可能不熟, 那三栏布局你肯定就非常熟悉了, 就是两边定宽, 中间自适应 的 布局 1 , 圣 ...
- ASP.NET Core中实现单体程序的事件发布/订阅
标题:ASP.NET Core中实现单体程序的事件发布/订阅 作者:Lamond Lu 地址:https://www.cnblogs.com/lwqlun/p/10468058.html 项目源代码: ...
- HttpClient Received an unexpected EOF or 0 bytes from the transport stream
请求https链接时报错,奇怪的是pc1正常,pc2异常 Unhandled Exception: System.AggregateException: One or more errors occu ...
- 【Android Studio安装部署系列】目录
概述 从刚开始使用Android Studio到现在,下面所有目录下的操作,当时习惯性的把每一个整理成一个文档(其实就是简单文字描述+截图):有些地方当时是一知半解,现在会稍微明白一些.正好赶上现在有 ...
- RabbitMQ死信队列另类用法之复合死信
前言 在业务开发过程中,我们常常需要做一些定时任务,这些任务一般用来做监控或者清理任务,比如在订单的业务场景中,用户在创建订单后一段时间内,没有完成支付,系统将自动取消该订单,并将库存返回到商品中,又 ...
- 用markdown + html写一封简历
0. 前言 1. 阶段1 - 确定需要几个模块 2. 阶段2 - 使用纯文字填充简历 3. 阶段3 - 预留空格 4. 阶段4 - 文章垂直方向的调整 5. 阶段5 - 居中对齐 6. 阶段6 - 加 ...
- 设计模式 | 策略模式(strategy)
参考:https://www.cnblogs.com/lewis0077/p/5133812.html(深入解析策略模式) 定义: 策略模式定义了一系列的算法,并将每一个算法封装起来,使每个算法可以相 ...
- js 对象拷贝
在JavaScript中,数据类型分为两大类:基本数据类型和复杂数据类型.基本数据类型包括Number.Boolean.String.Null.String),而复杂数据类型包括Object.Func ...
- GeoServer中sld文件的获取来源
众所周知,uDig是GeoServer的一个客户端,可以方便地可视化配置样式. QGIS行不行呢? 当然可以,双击图层名称,弹出图层属性对话框 在符号化(style)标签页选择样式导出即可. 参考博客 ...