你的生產環境是否也遇到過這樣的問題?一個基於 Nginx 或 OpenResty 的核心閘道器,在沒有任何明顯流量異常的情況下,記憶體佔用卻像被施了魔法一樣,緩慢而堅定地向上攀升。

幾天過去,單個 worker 程序的記憶體就從幾百兆漲到了 1G 以上,系統 OOM (Out of Memory) 的警報隨時可能被觸發。最棘手的方案似乎只剩下那個“重啟大法”,但這在核心業務上無異於飲鴆止渴——它能暫時緩解問題,卻無法根除,更重要的是,你根本不知道下一次危機何時到來。這就是我們最近處理的一個真實案例。一個高併發的 API 閘道器,正面臨著同樣的困境。這篇文章將分享我們是如何在不重啟服務、不影響業務的前提下,精準定位並拆解這個深藏在 Nginx 內部的記憶體洩漏問題。

要破解這個困局,關鍵不在於“我該用甚麼工具”,而在於先建立一條清晰、可驗證的分析路徑。我們採用的是一種自頂向下、層層收斂的方法論。不是為了更快,而是為了在生產環境中獲得確定性。

一次生產環境記憶體洩漏的完整解剖

Step 1:系統層現象確認 → 分配器歸因

首先,我們從系統層入手,確認記憶體增長的真實載體。透過執行 OpenResty XRay的引導式分析功能,對高記憶體問題進行自動分析。從自動分析報告的結果可以明確看到:Nginx worker 程序的記憶體使用已經超過 1GB,並且仍在持續增長。

Screenshot

進一步的分解顯示,這部分記憶體開銷中,絕大多數由 Glibc Allocator 持有。這一步直接排除了核心快取、檔案系統或其他外圍元件的問題,將焦點牢牢鎖定在應用程序自身的記憶體分配行為上。

這一結論至關重要。它排除了核心態洩漏、檔案快取或外部程序干擾等可能性,將問題的邊界清晰地收斂到了應用自身的記憶體分配行為

Step 2:分配器歸因 → Nginx 記憶體池聚焦

既然記憶體主要由 Glibc 持有,而程序是 Nginx,那麼這些記憶體服務於 Nginx 的記憶體池體系的可能性最大。真正困難的地方在於:Glibc 之下的記憶體,究竟是被哪些 Nginx 記憶體池消耗的?

透過對 Nginx 記憶體池的專項分析,我們很快發現了異常集中點。

Screenshot

當前程序中,主要的記憶體消耗來自 Tengine dynamic upstream 模組所建立的 Nginx 記憶體池

至此,問題第一次從“程序級記憶體失控”,收斂為“某一類記憶體池的異常增長”。

Step 3:Nginx 記憶體池聚焦 → 模組級懷疑

在確認了問題集中於 dynamic upstream 相關的記憶體池後,我們進一步下鑽到 Glibc 分配器層面,再次執行 OpenResty XRay 對記憶體塊尺寸分佈進行分析。

Screenshot

結果非常明確:在 256k–512k 這個區間內,存在 2240 個記憶體塊,數量顯著偏離正常執行時的分佈特徵。這種尺寸的大量聚集,意味著這些記憶體並非短生命週期物件,而是被長期持有、且沒有得到釋放。

這一發現,為“記憶體洩漏”提供了強有力且可量化的證據,而不再只是經驗判斷。

Step 4:模組級懷疑 → C 程式碼呼叫鏈追溯

確定了異常記憶體池,也確認了異常記憶體塊形態,最後一步便是回答最核心的問題:這些記憶體究竟是在哪一條程式碼路徑上被分配,並最終失去了釋放的機會?

透過使用 c-memory-leak-fgraph 工具,我們將記憶體池的分配行為,完整對映回 C 程式碼層面的函式呼叫關係。一條清晰、完整的記憶體洩漏呼叫鏈隨之浮現,從記憶體分配點開始,貫穿模組邏輯,直至生命週期斷裂的位置。

Screenshot

這一刻,問題不再是“可能在哪裡”,而是“它就是在這裡發生的”。

我們能夠在不重啟、不影響生產流量的前提下,將一個深埋在 Nginx 記憶體池內部的洩漏問題,完整、準確地還原為一條可追溯的 C 程式碼呼叫路徑。不再依賴猜測,不再依賴重啟換時間,而是掌握了可以被驗證、可以被修復的確定性證據。

這條分析路徑本身並不複雜,但它有一個嚴格前提:必須能夠在生產環境中,安全地跨越 Glibc 分配器、Nginx 記憶體池以及 C 程式碼三層邊界進行觀測與關聯。

也正是在這種真實而苛刻的場景下,OpenResty XRay 專業效能工程能力的價值,才真正顯現出來。

為甚麼這類問題幾乎無解?

大多數團隊在面對這類問題時,往往束手無策。這並非技術實力不足,而是因為問題的複雜性超出了常規工具和知識的範疇。

1. Nginx 記憶體池:一個黑盒

與我們熟悉的 malloc/free 不同,Nginx 為了效能,設計了一套自己的記憶體池(Memory Pool)。記憶體的申請和釋放被 Nginx 託管,這意味著 valgrind 這類傳統的記憶體洩漏檢測工具幾乎完全失效。它們無法理解 Nginx 記憶體池的生命週期,強行在生產環境使用 valgrind 更是一種自殺行為,會帶來毀滅性的效能影響。

2. Glibc 分配器:問題的“藏身之所”

更深一層,Nginx 記憶體池本身也是構建在 Glibc 的 malloc 之上。當洩漏發生時,我們能從 toppmap 看到的,只是 Glibc 分配器持有的記憶體總量在增長。而在本案中,問題被巧妙地隱藏在了大量 256k-512k 的記憶體塊中,從外部看,你根本無法將這些記憶體塊與 Nginx 內部的任何具體物件關聯起來。

3. 動態模組的複雜性

此案例中的服務使用了 Tengine 的動態上游(dynamic upstream)模組。這類模組的特點是其內部物件具有複雜的、長生命週期的管理邏輯。當這些邏輯與 Nginx 的事件驅動、非同步 I/O 模型結合時,記憶體的分配和釋放在不同的程式碼路徑和時間點上,追蹤難度呈指數級增長。

這三座大山,讓工程師陷入了“錯誤嘗試”的迴圈:調整核心引數 drop_caches?治標不治本。增加 worker_rlimit?遲早都會崩潰。最終,只剩下那個讓 SRE 團隊夜不能寐的 systemctl restart nginx

在這種時刻,工程師們往往會形成一個清醒卻無力的共識:**我們知道有問題,但我們看不見它。**不是不知道記憶體正在增長,而是不知道它為甚麼增長,更不知道該在哪裡下刀。

從“靠運氣”到“可預測”

定位並修復這個 bug 帶來的價值,遠不止是節省了記憶體。它代表著系統從一個“靠運氣執行”的狀態,演進為一個“健康可預測”的系統。基於我們的經驗,這類修復通常會帶來顯著的量化收益:

記憶體與可用性收益:

  • 記憶體佔用降低 70-90%:worker 程序記憶體從超過 1G 穩定在 200-300MB 水平。
  • 杜絕 OOM 崩潰:服務可用性從 99.5%(依賴定期重啟)提升至 99.9%+ 的穩態。

效能與成本收益:

  • 運維成本降低 50%+:告警風暴消失,SRE 團隊無需再為記憶體問題投入無效的排查和重啟操作。
  • 硬體成本最佳化:基於更低的記憶體佔用,未來可節省 30-50% 的伺服器記憶體配置。

核心價值在於:它將一個可能導致每月數萬元業務損失的定時炸彈,徹底拆除了。

當效能問題超越“工具”範疇

這個案例完美詮釋了一個觀點:當系統複雜度超過某個閾值,效能問題已經不再是單純的“工具問題”,而是“方法論 + 經驗密度”的綜合挑戰。

傳統工具之所以失效,是因為它們被設計用來解決更簡單、更通用的問題。而面對 Nginx 記憶體池與 Glibc 分配器交織的複雜場景,你需要的是 OpenResty XRay 這樣能夠深入系統“毛細血管”的洞察力,以及在不中斷服務的前提下完成診斷的工程經驗。

這也解釋了為甚麼即使是技術實力雄厚的團隊,在面對某些棘手的效能瓶頸時,仍然需要專業的效能工程師介入。因為他們帶來的不僅僅是工具,更是一種能夠將複雜問題系統化、視覺化的工程方法論。

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

我們的微信公眾號

翻譯

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