一次openresty http.lua 性能调优之旅

记一次openresty http.lua 性能调优之旅

1 背景

最近要用Nginx lua进行http 数据交互,因此想到了resty/http.lua,因此开启一段性能调优之旅。

2 发送HTTP GET请求代码

local ok, status, headers, code, body  = hc:request {
   url = uri,
   method = "GET",
}

很简单的一段代码,利用http.lua request 函数发送http get 请求并返回body及相关信息。

3 性能表现及现象

在get 小文件的时候性能表现正常,符合预期,但是get 大文件的时候非常慢,在内网环境下GET 1个 1M左右的Object 竟然需要1s+,这性能实在不能忍,而且随着文件增大性能急剧下降。开始怀疑是不是http server 的原因,用wget 试了一下,发现很快,排除server的原因。百思不得其解后开始分析http.lua 代码

4 http.lua 分析

这是Lua 读取http body 代码,可以看出这里有个fetch_size参数,从代码上看直观含义是一次从底层网络读上来数据块的大小

161 local function read_body_data(sock, size, fetch_size, callback)
162     local p_size = fetch_size
163     while size and size > 0 do
164         if size < p_size then
165             p_size = size
166         end
167         local data, err, partial = sock:receive(p_size)
168         if not err then
169             if data then
170                 callback(data) --这里有个callback,下面看看是啥
171             end
172         elseif err == "closed" then
173             if partial then
174                 callback(partial)
175             end
176             return 1 -- 'closed'
177         else
178             return nil, err
179         end
180         size = size - p_size
181     end
182     return 1
183 end

看下fetch size 设置值是多少

nreqt.fetch_size = reqt.fetch_size or 16*1024

默认为16K

再看一下function read_body_data 在哪里调用的,参数callback 传又是什么

185 local function receivebody(sock, headers, nreqt)
186     local t = headers["transfer-encoding"] -- shortcut
187     local body = ''
188     local callback = nreqt.body_callback
189     if not callback then
190         local function bc(data, chunked_header, ...)
191             if chunked_header then return end
192             body = body .. data
193         end
194         callback = bc
195     end
196     if t and t ~= "identity" then
197         -- chunked
198         while true do
199             local chunk_header = sock:receiveuntil("\r\n")
200             local data, err, partial = chunk_header()
201             if not data then
202                 return nil,err
203             else
204                 if data == "0" then
205                     return body -- end of chunk
206                 else
207                     local length = tonumber(data, 16)
208
209                     -- TODO check nreqt.max_body_size !!
210
211                     local ok, err = read_body_data(sock,length, nreqt.fetch_size, callback)
212                     if err then
213                         return nil,err
214                     end
215                 end
216             end
217         end
218     elseif headers["content-length"] ~= nil and tonumber(headers["content-length"]) >= 0 then
219         -- content length
220         local length = tonumber(headers["content-length"])
221         if length > nreqt.max_body_size then
222             ngx.log(ngx.INFO, 'content-length > nreqt.max_body_size !! Tail it !')
223             length = nreqt.max_body_size
224         end
225
226         local ok, err = read_body_data(sock,length, nreqt.fetch_size, callback)
227         if not ok then
228             return nil,err
229         end
230     else
231         -- connection close
232         local ok, err = read_body_data(sock,nreqt.max_body_size, nreqt.fetch_size, callback)
233         if not ok then
234             return nil,err
235         end
236     end
237     return body
238 end

这里可以看到我们的程序中没有传callback 进去,callback 默认是

190         local function bc(data, chunked_header, ...)
191             if chunked_header then return end
192             body = body .. data -- 注意这里会对每次接收到的body 进行拼接
193         end
194         callback = bc

分析到这里问题已经很明显了

fetch_size 是一次sock:receive 调用读上来的body 的size,每次读出来fetch_size 的body 后会回调默认callback 对body 进行拼接,如果文件size 很大而fetch size 很小就会造成因字符串拼接造成的CPU资源消耗及内存消耗。而我们的场景是需要缓存所有body后处理,所以一次读出越多body越好。

默认Callback是
local function bc(data, chunked_header, ...)
if chunked_header then return end
body = body .. data
end

假设按照fetch size默认值16k 来算,get 1MB 文件光string 拼接就要进行64次,所以一次性接收所有body性能最佳,fetch_size 设置为1GB。(大家都知道字符串拼接需要额外内存分配会消耗大量CPU)

5 结论

fetch_size 设置太小导致大文件body 拼接次数过多导致,从我的场景来看要缓存所有body后才能进行下一步因此fetch_size 设置越大越好
修正后代码为:
local ok, status, headers, code, body = hc:request {
url = uri,
fetch_size = 102410241024,
method = "GET",
}

注意:如果你的业务场景是需要流式处理或者转发这个值只需要将fetch_size 调整为一个合适的值即可。

时间: 2025-01-27 03:47:00

一次openresty http.lua 性能调优之旅的相关文章

oracle 性能调优之旅开始

1.先看三篇帖子: www.itpub.net/thread-137600-1-1.html http://www.itpub.net/thread-124424-1-1.html http://blog.itpub.net/post/96/14353 执行下面的语句: SQL> show parameter area_size; NAME                                 TYPE        VALUE ----------------------------

erlang网络编程的几个性能调优和注意点

问题描述 前些天给echo_**写了个非常简单的连接压力**程序,代码-module(stress_**). -export(). start() -> **s(12345). **s(Port) -> io:format("starting~n"), spawn(fun() -> **(Port) end), spawn(fun() -> **(Port) end), spawn(fun() -> **(Port) end), spawn(fun() -

oracle数据库性能调优技术:深入理解单表执行计划

一.概述 这篇文章是数据库性能调优技术的第二篇.上一篇讲解的索引调优是数据库性能调优技术的基础.这篇讲解的深入理解单表执行计划,是数据库性能调优的有力工具. 查询语句可以有多种可选执行计划,如何选择效率最高的执行计划?达梦数据库.oracle数据库.sql server数据库都是采用基于成本的查询优化,对备选执行计划进行打分,选择大家最小的执行计划进行执行.这些内容,我会在后续的几篇文章中进行详细的描述.在此之前,我们首先需要掌握如何理解数据库执行计划.这篇文章讲解只涉及单表操作的执行计划. 达

Liferay前端性能调优(1) 测评工具YSlow

最近我们团队要问Liferay做前端页面调优,当然了,测评工具是最重要的,为了看具体的页面加载时间等 ,我们首选当然是Chrome浏览器的诊断工具,但是总感觉不专业,基于我已有的经验,我还是推荐了YSlow,它 会对于页面的各项指标进行打分,然后最终获得总分然后评级,一般级别有A,B,C,D,E,F6个级别. 如何安 装和测试YSlow: (1) 从Firefox的Add-on上下载 "YSlow" (2)重启Firefox检查是否YSlow 已经被正确的安装 (3)到我们要测试的页面

数据库性能调优技术

一.概述 随着数据库在各个领域的使用不断增长,越来越多的应用提出了高性能的要求.数据库性能调优是知识密集型的学科,需要综合考虑各种复杂的因素:数据库缓冲区的大小.索引的创建.语句改写等等.总之,数据库性能调优的目的在于使系统运行得更快. 调优需要有广泛的知识,这使得它既简单又复杂. 说调优简单,是因为调优者不必纠缠于复杂的公式和规则.许多学术界和业界的研究者都在尝试将调优和查询处理建立在数学基础之上. 称调优复杂,是因为如果要完全理解常识所依赖的原理,还需要对应用.数据库管理系统.操作系统以及硬

可预见的Oracle应用程序的性能调优

这篇技巧性文章是由"国际Oracle用户组"(IOUG)提供的,它是一个由用户组成的组织,这个组织通过提供高质量的信息.培训.网络和支持,来提高Oracle数据库专家和数据库开发者的水平.这篇文章摘自由David Welch所写的论文<可预见的Oracle应用程序性能调优>.点击这里成为"国际Oracle用户组"的一员,从而获得成千上万的由Oracle用户写的技巧性文章和科技文献. 引言 我们见到过很多带有巨大性能问题的Oracle应用程序和电子商务套件

oracle数据库性能调优技术:索引调优

一.概述 随着数据库在各个领域的使用不断增长,越来越多的应用提出了高性能的要求.数据库性能调优是知识密集型的学科,需要综合考虑各种复杂的因素:数据库缓冲区的大小.索引的创建.语句改写等等.总之,数据库性能调优的目的在于使系统运行得更快. 调优需要有广泛的知识,这使得它既简单又复杂. 说调优简单,是因为调优者不必纠缠于复杂的公式和规则.许多学术界和业界的研究者都在尝试将调优和查询处理建立在数学基础之上. 称调优复杂,是因为如果要完全理解常识所依赖的原理,还需要对应用.数据库管理系统.操作系统以及硬

oracle数据库性能调优技术:深入理解嵌套循环执行计划

一.概述 这篇文章是数据库性能调优技术的第三篇.上一篇文章讲解了深入了解单表执行计划,单表执行计划是理解多表执行计划的基础. 两张表的连接有三种执行方式:1)嵌套循环连接:2)散列连接:3)归并连接.两张表连接时选择这三种中的哪一种呢?这取决于索引.以及连接的代价.在该系列的第三篇(本文)文章中讲解嵌套循环连接,第四篇文章中讲解散列连接,第五篇文章中讲解归并连接.在第六篇以后会分析IN子查询以及EXISTS子查询. 达梦数据库.oracle数据库.sql server数据库在数据库执行计划方面并

oracle数据库性能调优技术:深入理解散列连接执行计划

一.概述 这篇文章是数据库性能调优技术系列的第四篇.上一篇文章讲解了深入理解嵌套循环连接执行计划. 上一篇文章中提到两张表的连接有三种执行方式:1)嵌套循环连接:2)散列连接:3)归并连接.散列连接是很重要的连接方式,包含比较多的内容,这篇文章中讲解为什么需要散列连接?如何理解散列连接? 和前三篇文章一样,本文讲解的是些比较抽象的内容,不拘泥于具体的数据.所以本文中使用的代价评估模型也是抽象的,假设了数据库缓冲区大小只有一个页,新页的读取必然导致旧页的释放.读完本文之后应该能够读懂达梦数据库.o