當一個 LRU 快取的容量設定得過大、幾乎從不淘汰快取項時,它就會引發記憶體洩漏——每一個快取物件實際上都被永久保留。在這個真實案例中,一個 OpenResty 應用把解析後的 SSL 證書快取進了 lua-resty-lrucache,底層的 OpenSSL 結構體不斷堆積,直到 worker 程序 RSS 無限增長。下面看 OpenResty XRay 如何在不改程式碼、不重啟的情況下把它追蹤到根因。

為甚麼一個低流量的 OpenResty 應用會持續洩漏記憶體

某客戶的 OpenResty 應用雖然業務流量很小,但其 worker 程序的記憶體佔用卻隨著時間的推移而持續增長,遠超預期。這種典型的記憶體洩漏現象不僅造成了嚴重的資源浪費,更給線上業務的穩定性帶來了巨大隱患。由於缺乏有效的分析工具,客戶對洩漏的根源毫無頭緒。OpenResty XRay 團隊隨即介入,利用其強大的動態追蹤和分析能力,對線上執行的 OpenResty 程序進行了非侵入式的診斷。

一步步追蹤 LRU 快取記憶體洩漏的完整過程

從 RSS 增長趨勢確認洩漏

我們首先透過 OpenResty XRay 的程序記憶體趨勢圖,直觀地驗證了客戶的報告。

OpenResty XRay 程序記憶體趨勢圖:worker 程序 RSS 呈線性增長,是記憶體洩漏的典型特徵

監控資料顯示,OpenResty 程序的 RSS(Resident Set Size)記憶體呈現出明顯的線性增長,這是記憶體洩漏的典型特徵。

將洩漏範圍縮小到 Glibc Arena,而非 Nginx 記憶體池

為了弄清是哪部分記憶體出了問題,我們使用了 OpenResty XRay 的記憶體分析功能。自動分析報告顯示:

  • Glibc 記憶體分配 佔據了總記憶體的約 93%,是主要的記憶體消耗來源。
  • LuaJIT 記憶體分配 僅佔約 2.4%。

OpenResty XRay 記憶體分析:Glibc 分配約佔總記憶體 93%,LuaJIT 僅約 2.4%

透過 resty-memory 的記憶體分解圖深入分析 Glibc 的記憶體使用,我們發現其增長完全來自於 Glibc Arena

resty-memory 記憶體分解圖:記憶體增長完全來自 Glibc Arena

通常,Nginx 自身的記憶體池 Nginx memory pool 會透過 Arena 區域進行分配,但我們觀察到 Nginx 記憶體池的佔用量非常小且穩定。

OpenResty XRay 記憶體分解圖中 Nginx 記憶體池佔用量小且穩定

可以看到 Glibc 分配的記憶體隨時間線性增長,而 Nginx memory pool 的記憶體使用量非常的小。

Glibc 分配的記憶體隨時間線性增長,而 Nginx 記憶體池佔用可忽略不計

這表明,洩漏並非源於常規的請求處理,而是由其他模組透過 Glibc 直接申請的記憶體。

用火焰圖定位到 lua-resty-lrucache 物件

究竟是哪些程式碼在不斷申請 Arena 記憶體並且從不釋放?OpenResty XRay記憶體洩漏火焰圖給出了答案。

OpenResty XRay 記憶體洩漏火焰圖指向 SSL/TLS 證書和私鑰解析環節

記憶體洩露火焰圖顯示,記憶體洩漏發生在解析 SSL/TLS 證書和私鑰的環節。

但為甚麼會洩漏?我們轉而使用 Lua GC 物件火焰圖 來分析 Lua 層面上的物件引用關係。這與我們用命令列工具定位洩漏的 Lua table 是同一套物件級檢視。

Lua GC 物件火焰圖顯示 cert_cache 這個 lua-resty-lrucache 物件佔用了絕大部分記憶體

根據火焰圖顯示,一個名為 _LOADED.dynamic_cert.cert_cache 的 Lua table 佔用了絕大部分記憶體。

由於其內部結構包含 .free_queu, .hashht,.key2node,.node2key 等欄位,我們判斷這是一個 lua-resty-lrucache 物件。

結合 cert_cache 這個表名稱以及記憶體洩露火焰圖,我們可以推測,使用者把透過 ssl.parse_pem_certssl.parse_pem_priv_key 解析的結果快取到 lru cache 中。

至此,整個問題鏈路完全清晰:

  1. 客戶為了最佳化效能,將動態載入的 SSL 證書和私鑰解析結果快取到了一個 LRU 快取 cert_cache 中。
  2. 問題在於,這個 LRU 快取在建立時設定的容量過大,導致它幾乎永遠不會淘汰任何快取項。
  3. 因此,每一個新域名證書被解析後,其結果物件都被“永久”地存放在了 LRU 快取中。這些物件被快取引用,導致 Lua 的 GC 回收器無法回收它們,其底層的 OpenSSL 證書結構體所佔用的記憶體也隨之洩漏。
  4. 隨著時間推移,解析的證書越來越多,記憶體佔用也就隨之線性增長。

最終結果:從無限增長到記憶體平穩

很多企業都遇到過類似的情況:系統記憶體使用持續飆升,卻遲遲找不到根本原因,明明問題很小,但一不小心就可能導致線上服務宕機——就像我們不重啟服務就排查掉的另一個 OpenResty 隱形記憶體洩漏

OpenResty XRay 只用了一次分析,就幫客戶解決了這個長期未解的記憶體洩漏難題。

  1. 精準定位,一擊即中OpenResty XRay 透過記憶體趨勢圖、記憶體分解分析和獨特的記憶體洩漏火焰圖,層層遞進地揭示了問題本質。從現象到根因,整個分析過程清晰可視,避免了傳統方法中的盲目猜測和反覆嘗試。

  2. 非侵入式診斷,生產環境安全:全程無需修改程式碼、無需重啟服務,也不會增加系統負載。即使在高併發的生產環境,也能做到安全、穩定、實時分析。

  3. 給客戶帶來的業務價值

    • 記憶體使用率大幅降低,從持續增長到穩定可控
    • 消除了因記憶體耗盡導致的潛在宕機風險
    • 提升了系統整體效能和響應速度
    • 運維壓力和資源浪費大幅減少,為企業節約了真金白銀

這個案例告訴我們,即使是看似簡單的記憶體洩漏問題,如果沒有專業工具的輔助,也可能成為技術團隊的“攔路虎”。而 OpenResty XRay 憑藉其強大的可觀測性和分析能力,能夠快速切入問題核心,為客戶提供清晰的解決思路,最終實現技術價值到業務價值的完美轉化。

常見問題

為甚麼 LRU 快取會引發記憶體洩漏?

LRU 快取只有在淘汰快取項時才會釋放記憶體。如果它的容量設定得遠大於實際工作集,就幾乎不會淘汰任何東西,於是每一個插入的物件都會在程序的整個生命週期內被引用。本案例中,cert_cache 這個 LRU 快取持有著從不被淘汰的已解析證書,因此隨著新域名不斷被解析,記憶體呈線性增長。

快取 SSL 證書會導致記憶體洩漏嗎?

會。快取 ssl.parse_pem_certssl.parse_pem_priv_key 的解析結果,會讓底層的 OpenSSL 證書和私鑰結構體一直存活。當這些解析結果被放進一個容量過大、從不淘汰的 LRU 快取時,OpenSSL 結構體就會在 Glibc Arena 中不斷堆積,RSS 持續攀升。

為甚麼 Lua 的垃圾回收器無法回收這些快取物件?

Lua 的垃圾回收器只回收不再可達的物件。由於這些已解析的證書物件仍被 cert_cache 這個 LRU table 引用,它們始終可達,GC 無法回收。這不是 GC 的 bug,而是快取的無限制保留。

如何找出是哪個 Lua 物件在洩漏記憶體?

OpenResty XRay 的 Lua GC 物件火焰圖會展示 Lua 層面的物件引用關係,讓你直接看到是哪個 table 持有著記憶體。本案例中它直接指向 _LOADED.dynamic_cert.cert_cache,其內部欄位(.free_queu.hashht.key2node.node2key)表明它是一個 lua-resty-lrucache 物件——全程無需改程式碼、無需重啟程序。

關於 OpenResty XRay

OpenResty XRay 是一款動態追蹤產品,它可以自動分析執行中的應用,以解決效能問題、行為問題和安全漏洞,並提供可行的建議。在底層實現上,OpenResty XRay 由我們的 Y 語言驅動,可以在不同環境下支援多種不同的執行時,如 Stap+、eBPF+、GDB 和 ODB。

關於作者

章亦春是開源 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. 公司的部落格網站 。也歡迎掃碼關注我們的微信公眾號:

我們的微信公眾號

翻譯

我們提供了英文版原文和中譯版(本文)。我們也歡迎讀者提供其他語言的翻譯版本,只要是全文翻譯不帶省略,我們都將會考慮採用,非常感謝!