开发者社区> 简单爱0107> 正文
阿里云
为了无法计算的价值
打开APP
阿里云APP内打开

一次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是

            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 设置越大越好
    修正后代码为:
   url = uri,
   fetch_size = 1024*1024*1024,
   method = "GET", 
   }```

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

版权声明:本文内容由阿里云实名注册用户自发贡献,版权归原作者所有,阿里云开发者社区不拥有其著作权,亦不承担相应法律责任。具体规则请查看《阿里云开发者社区用户服务协议》和《阿里云开发者社区知识产权保护指引》。如果您发现本社区中有涉嫌抄袭的内容,填写侵权投诉表单进行举报,一经查实,本社区将立刻删除涉嫌侵权内容。

相关文章
SAP的ICF发送HTTP请求
本文翻译了sap的官方help文档地址为:SAP发送HTTP请求的exampleExample program: Executing an HTTP Request
1285 0
vue + axios---封装一个http请求
在使用vue开发时,官方推荐使用axios来请求接口 // axios官方地址 https://github.com/axios/axios 但是axios并不像 vue-resource 一样拥有install,即不能直接 Vue.use(axios) 来使用,所以需要我们自己根据axios来写一个具有install方法的Http库。
3005 0
如何使用事务码SMICM分析ABAP代码发起的HTTP请求的错误
当我用CL_HTTP_CLIENT往一个外网的url发请求时,遇到错误:ICM_HTTP_SSL_PEER_CERT_UNTRUSTED 错误是从这段ABAP代码里抛出来的: CALL METHOD lo_http_client->get_last_error IMPORTING code = lv_sysubrc message = ev_error_message. "BREAK-POINT. WRITE: / 'error: ' , ev_error_message. 只有这一个错误,或许您觉得很难找到问题根源。
1278 0
关于HTTP请求你需要知道的一切
前言 现在几乎所有的客户端(不管是APP也好,H5页面也好),都需要用到网络请求,而绝大多数公司都会把客户端的网络框架封装好,我们直接拿来用就好了,不用管什么原理,只要请求以后能得到正确的返回就可以了,但如果让你从零开始封装一个请求框架,或者你接到了一个别的项目,当前的网络请求框架不合适或者不好用了,这时候你可能就懵了。
979 0
ASP.NET WebApi服务接口如何防止重复请求实现HTTP幂等性(八)
一、背景描述与课程介绍 明人不说暗话,跟着阿笨一起玩WebApi。在我们平时开发项目中可能会出现下面这些情况; 1)、由于用户误操作,多次点击网页表单提交按钮。由于网速等原因造成页面卡顿,用户重复刷新提交页面。
2728 0
使用Python请求http/https时设置失败重试次数
使用Python的requests库时,默认是没有失败时重试请求的,通过下面的方式可以支持重试请求 设置请求时的重试规则 import requests from requests.
1101 0
使用Apache-httpclient发送http请求
pom.xml org.apache.httpcomponents httpclient 4.
1479 0
+关注
简单爱0107
Aliyun OSS 开发
11
文章
82
问答
来源圈子
更多
阿里云存储基于飞天盘古2.0分布式存储系统,产品包括对象存储OSS、块存储Block Storage、共享文件存储NAS、表格存储、日志存储与分析、归档存储及混合云存储等,充分满足用户数据存储和迁移上云需求,连续三年跻身全球云存储魔力象限四强。
+ 订阅
文章排行榜
最热
最新
相关电子书
更多
低代码开发师(初级)实战教程
立即下载
阿里巴巴DevOps 最佳实践手册
立即下载
冬季实战营第三期:MySQL数据库进阶实战
立即下载