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

记一次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 调整为一个合适的值即可。

目录
相关文章
|
5月前
|
编解码 测试技术 索引
性能工具之 Jmeter 使用 HTTP 请求编写 HLS 脚本
在我们简要介绍了 HLS 协议的基础知识,接下来我们详细介绍一种使用 Jmeter 编写压测 HLS 协议脚本的方法。
141 1
性能工具之 Jmeter 使用 HTTP 请求编写 HLS 脚本
|
5月前
|
存储 缓存 Java
Openresty(lua+nginx)-Guava-Redis做多级缓存
Openresty(lua+nginx)-Guava-Redis做多级缓存
60 1
|
1月前
|
数据采集
Haskell爬虫:连接管理与HTTP请求性能
Haskell爬虫:连接管理与HTTP请求性能
|
5月前
|
消息中间件 前端开发 JavaScript
第七篇 提升网页性能:深入解析HTTP请求优化策略(二)
第七篇 提升网页性能:深入解析HTTP请求优化策略(二)
185 1
|
5月前
|
Web App开发 存储 缓存
第八篇 提升网页性能:深入解析HTTP请求优化策略(三)
第八篇 提升网页性能:深入解析HTTP请求优化策略(三)
160 0
|
5月前
|
编解码 前端开发 JavaScript
第六篇 提升网页性能:深入解析HTTP请求优化策略(一)
第六篇 提升网页性能:深入解析HTTP请求优化策略(一)
|
5月前
|
运维 Serverless API
Serverless 应用引擎产品使用之阿里函数计算中要关掉http触发器的jwt认证才可以进行性能探测如何解决
阿里云Serverless 应用引擎(SAE)提供了完整的微服务应用生命周期管理能力,包括应用部署、服务治理、开发运维、资源管理等功能,并通过扩展功能支持多环境管理、API Gateway、事件驱动等高级应用场景,帮助企业快速构建、部署、运维和扩展微服务架构,实现Serverless化的应用部署与运维模式。以下是对SAE产品使用合集的概述,包括应用管理、服务治理、开发运维、资源管理等方面。
|
5月前
|
网络协议 开发者
深入理解HTTP/2:提升Web性能的秘密 - 蓝易云
理解并利用HTTP/2的这些特性,可以显著提高Web应用的性能。同时,由于HTTP/2是完全兼容HTTP/1.1的,所以开发者可以平滑地过渡到这个新协议,无需担心兼容性问题。
93 0
|
5月前
|
网络协议 Java API
JDK 9新特性探秘:HTTP/2支持及其性能优势
本文将深入探讨JDK 9中引入的一项重要新特性——对HTTP/2协议的原生支持。HTTP/2作为下一代互联网传输协议,相较于HTTP/1.1在性能、安全性和效率方面有着显著的提升。JDK 9通过内置HTTP/2客户端API,为Java开发者提供了更加便捷和高效的网络通信手段。本文将详细介绍HTTP/2的特性、JDK 9中HTTP/2的支持方式,以及如何在实际项目中应用这一新特性来提升网络应用的性能。
|
5月前
|
NoSQL 关系型数据库 应用服务中间件
Linux安装 OpenResty、Nginx、PHP、Mysql、Redis、Lua、Node、Golang、MongoDB、Kafka等
Linux安装 OpenResty、Nginx、PHP、Mysql、Redis、Lua、Node、Golang、MongoDB、Kafka等
165 0