我們最近使用 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%

io.popen 的 CPU 問題

注意高亮顯示的問題中的 [buildin#io.popen] Lua 函式幀。

問題文字顯示了完整的 Lua 程式碼路徑,包括虛擬機器內的 LuaJIT 原語,因此使用者可以在對應 Lua 原始碼中快速定位。如果我們將滑鼠游標懸停在 Lua 函式 run() 的綠框上,就會彈出一個工具提示,其中有 Lua 原始檔名和行號等細節。

io.popen 的 CPU 問題的原始檔名和行號

我們可以看到,io.popen 呼叫的位置在 Lua 原始檔 /usr/local/openresty/site/lualib/cfg-utils.lua 的第 8 行。

file:read()

在 CPU 類別下,我們發現了 file:read() 問題。它佔據了目標程序所消耗的總 CPU 時間的 26.3%

file:read 的 CPU 問題

注意高亮顯示的問題中的 [buildin#io.method.read] 函式幀。

問題文字顯示了完整的 Lua 程式碼路徑,包括虛擬機器內的 LuaJIT 原語,因此使用者可以在對應 Lua 原始碼中快速定位。如果我們將滑鼠游標懸停在 Lua 函式 run() 的綠框上,就會彈出一個工具提示,其中有 Lua 原始檔名和行號等細節。

file:close 的 cpu 問題的原始檔名和行號

我們可以看到,file:read() 的呼叫位置在 Lua 原始檔 /usr/local/openresty/site/lualib/cfg-utils.lua 的第 14 行。客戶檢查了該行,並確認它是在先前的 io.popen 呼叫所開啟的檔案柄上。

off-CPU 操作

這裡的 “off-CPU” 指的是作業系統執行緒被阻塞並處於等待狀態,不能執行後面的程式碼。

off-CPU 圖解

file:read()

在診斷報告中,我們看到 file:read() 的呼叫在 off-CPU 時間方面很熱。它佔用了目標程序所消耗的總 off-CPU 時間的 99.8%,而唯一會阻塞的應該是 Nginx 事件迴圈的事件等待操作(如 epoll_wait 系統呼叫)。

file:read 的 off-CPU 問題

注意高亮顯示的問題中的 [buildin#io.method.read] Lua 函式幀。

問題文字顯示了完整的 Lua 程式碼路徑,包括虛擬機器內的 LuaJIT 原語,因此使用者可以在其 Lua 原始碼中快速定位。如果我們將滑鼠游標懸停在 Lua 函式 run() 的綠框上,就會彈出一個工具提示,其中有 Lua 原始檔名和行號等細節。

file:read 的 off-CPU 問題的原始檔名和行號

我們可以看到,file:read() 的呼叫位置在 Lua 原始檔 /usr/local/openresty/site/lualib/cfg-utils.lua 的第 14 行。客戶檢查了該行,確認它也是在先前的 io.popen 呼叫所開啟的檔案控制代碼上。

解決方案

根據上述分析,罪魁禍首是管道的 Lua API,包括 io.popen 和對管道檔案控制代碼的讀取和關閉操作。它在 CPU 和 off-CPU 時間方面都嚴重阻塞了 Nginx 的事件迴圈。因此,解決方案也是直截了當的。

  1. 在 OpenResty 應用程式中避免使用 io.popen Lua API。使用 OpenResty 的 lua-resty-shell 庫或低階別的 Lua API ngx.pipe 來代替。
  2. 完全避免系統命令和 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、LuaJITGDBSystemTapLLVM、Perl 等,並編寫過 60 多個開源軟體庫。

關注我們

如果您喜歡本文,歡迎關注我們 OpenResty Inc. 公司的部落格網站

我們也在 B 站上也有 OpenResty 官方的影片分享空間,歡迎訂閱。

同時歡迎掃碼關注我們的微信公眾號:

我們的微信公眾號


  1. IPC 是指程序間通訊。 ↩︎