我們最近使用 OpenResty XRay 幫助網絡安全行業的一個企業客户優化了他們的 OpenResty/Nginx 應用程序請求吞吐量低的問題。
OpenResty XRay 在客户的生產環境中自動進行了所有分析並生成了診斷報告,在報告中我們立即看到了瓶頸問題。Lua IPC1 管道操作,io.popen 等,嚴重阻塞了 OpenResty/Nginx 的事件循環。
使用 OpenResty 自身的非阻塞 lua-resty-shell 庫替換標準的 Lua API 函數調用,吞吐量幾乎有七倍的改進。
用 OpenResty 的非阻塞 cosocket API 替換系統管道,可以將吞吐量進一步提高 150 倍。
OpenResty XRay 是一個動態追蹤產品,它可以自動分析正在運行中的應用程序,以排除性能問題、行為問題和安全漏洞,並提供可行的建議。在底層實現上,OpenResty XRay 由我們的 Y 語言驅動,可以在不同環境下支持多種不同的運行時,如 Stap+, eBPF+, GDB 和 ODB。
問題
客户在他們的在線 OpenResty 應用程序中遭遇了嚴重的性能問題。在他們的服務器上,每秒最大請求數非常低,僅有 130 左右。這糟糕到讓服務幾乎不可用。此外,儘管他們的服務器上配有高端 CPU,他們的 Nginx worker 進程只能利用不到一半的 CPU 資源。
分析
OpenResty XRay 對客户的在線進程進行了深入分析。它不需要客户的應用程序進行任何協作。
- 沒有額外的插件、模塊或庫。
- 沒有代碼注入或補丁。
- 沒有特殊的編譯或啓動選項。
- 甚至不需要重新啓動應用程序進程。
分析完全是以“事後”的方式進行的。這得益於 Openresty XRay 採用的動態追蹤技術。
這類性能問題對於 OpenResty XRay 來説很容易分析。它發現 CPU 和 off-CPU 操作一起阻塞了 OpenResty/Nginx 的事件循環。
CPU 操作
在 OpenResty XRay 的自動分析報告中,我們可以看到 io.popen 及其相關的 file:close() 操作讓 CPU 使用率非常高。
io.popen
在 CPU 類別下,我們可以找到 io.popen 問題。它佔據了目標進程所消耗的總 CPU 時間的 93.8%。
注意高亮顯示的問題中的 [buildin#io.popen] Lua 函數幀。
問題文本顯示了完整的 Lua 代碼路徑,包括虛擬機內的 LuaJIT 原語,因此用户可以在對應 Lua 源代碼中快速定位。如果我們將鼠標光標懸停在 Lua 函數 run() 的綠框上,就會彈出一個工具提示,其中有 Lua 源文件名和行號等細節。
我們可以看到,io.popen 調用的位置在 Lua 源文件 /usr/local/openresty/site/lualib/cfg-utils.lua 的第 8 行。
file:read()
在 CPU 類別下,我們發現了 file:read() 問題。它佔據了目標進程所消耗的總 CPU 時間的 26.3%。
注意高亮顯示的問題中的 [buildin#io.method.read] 函數幀。
問題文本顯示了完整的 Lua 代碼路徑,包括虛擬機內的 LuaJIT 原語,因此用户可以在對應 Lua 源代碼中快速定位。如果我們將鼠標光標懸停在 Lua 函數 run() 的綠框上,就會彈出一個工具提示,其中有 Lua 源文件名和行號等細節。
我們可以看到,file:read() 的調用位置在 Lua 源文件 /usr/local/openresty/site/lualib/cfg-utils.lua 的第 14 行。客户檢查了該行,並確認它是在先前的 io.popen 調用所打開的文件柄上。
off-CPU 操作
這裏的 “off-CPU” 指的是操作系統線程被阻塞並處於等待狀態,不能執行後面的代碼。
file:read()
在診斷報告中,我們看到 file:read() 的調用在 off-CPU 時間方面很熱。它佔用了目標進程所消耗的總 off-CPU 時間的 99.8%,而唯一會阻塞的應該是 Nginx 事件循環的事件等待操作(如 epoll_wait 系統調用)。
注意高亮顯示的問題中的 [buildin#io.method.read] Lua 函數幀。
問題文本顯示了完整的 Lua 代碼路徑,包括虛擬機內的 LuaJIT 原語,因此用户可以在其 Lua 源代碼中快速定位。如果我們將鼠標光標懸停在 Lua 函數 run() 的綠框上,就會彈出一個工具提示,其中有 Lua 源文件名和行號等細節。
我們可以看到,file:read() 的調用位置在 Lua 源文件 /usr/local/openresty/site/lualib/cfg-utils.lua 的第 14 行。客户檢查了該行,確認它也是在先前的 io.popen 調用所打開的文件句柄上。
解決方案
根據上述分析,罪魁禍首是管道的 Lua API,包括 io.popen 和對管道文件句柄的讀取和關閉操作。它在 CPU 和 off-CPU 時間方面都嚴重阻塞了 Nginx 的事件循環。因此,解決方案也是直截了當的。
- 在 OpenResty 應用程序中避免使用 io.popen Lua API。使用 OpenResty 的 lua-resty-shell 庫或低級別的 Lua API ngx.pipe 來代替。
- 完全避免系統命令和 IPC 管道。使用 OpenResty 提供的更有效的 cosocket API 或建立在它之上的更高級別的庫。
結果
客户聽從了我們的建議,在他們的網關應用中從標準的 Lua API io.popen遷移到 OpenResty 的 lua-resty-shell 庫。然後他們立即看到了大約七倍的改進。
我們進一步建議他們應該完全避免昂貴的系統命令調用。然後他們修整了業務邏輯,並利用 OpenResty 的非阻塞 cosocket API 來獲取元數據。這一變化帶來了 150 倍的改進,讓1個 CPU 核心達到每秒數以萬計的請求。
客户對現在的性能表現很滿意。
關於作者
章亦春是開源 OpenResty® 項目創始人兼 OpenResty Inc. 公司 CEO 和創始人。
章亦春(Github ID: agentzh),生於中國江蘇,現定居美國灣區。他是中國早期開源技術和文化的倡導者和領軍人物,曾供職於多家國際知名的高科技企業,如 Cloudflare、雅虎、阿里巴巴, 是 “邊緣計算“、”動態追蹤 “和 “機器編程 “的先驅,擁有超過 22 年的編程及 16 年的開源經驗。作為擁有超過 4000 萬全球域名用户的開源項目的領導者。他基於其 OpenResty® 開源項目打造的高科技企業 OpenResty Inc. 位於美國硅谷中心。其主打的兩個產品 OpenResty XRay(利用動態追蹤技術的非侵入式的故障剖析和排除工具)和 OpenResty Edge(最適合微服務和分佈式流量的全能型網關軟件),廣受全球眾多上市及大型企業青睞。在 OpenResty 以外,章亦春為多個開源項目貢獻了累計超過百萬行代碼,其中包括,Linux 內核、Nginx、LuaJIT、GDB、SystemTap、LLVM、Perl 等,並編寫過 60 多個開源軟件庫。
關注我們
如果您喜歡本文,歡迎關注我們 OpenResty Inc.
公司的博客網站 。
我們也在 B 站上也有 OpenResty 官方的視頻分享空間,歡迎訂閲。
同時歡迎掃碼關注我們的微信公眾號:
- IPC 是指進程間通信。 ↩