閱讀963 返回首頁    go 阿裏雲 go 技術社區[雲棲]


一次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 = 1024*1024*1024,
method = "GET",
}

注意:如果你的業務場景是需要流式處理或者轉發這個值隻需要將fetch_size 調整為一個合適的值即可。

最後更新:2017-06-05 11:33:57

  上一篇:go  《Cucumber:行為驅動開發指南》——1.5 我們學到了什麼
  下一篇:go  搗蛋SQL導致實例iops 100%