OpenResty® 開源 Web 平臺以 高 性能 和 低 內存佔用著稱。咱們有一些用戶甚至在嵌入式系統中運行復雜的 OpenResty 應用,好比機器人。也有一些用戶在把他們的應用從其餘技術棧(好比 Java,NodeJS 和 PHP)遷移到 OpenResty 以後,觀察到內存使用量上的顯著降低。然而,有時候咱們仍是須要優化某些 OpenResty 應用的內存使用。這些應用中的 Lua 代碼、NGINX 配置、第三方 Lua 庫或第三方 NGINX 模塊均可能會有 BUG 或者性能問題,從而致使應用佔用過多的內存,甚至存在內存泄露。html
爲了有效地調試和優化內存的過分使用或者內存泄漏問題,咱們須要瞭解 OpenResty、Nginx 和 LuaJIT 在內部是如何分配和管理內存的。咱們的 OpenResty XRay 商業產品,可以在不修改目標應用的狀況下,自動分析和診斷幾乎全部的內存使用問題,即便是線上的生產應用。咱們將撰寫一個系列的文章(本文是第一篇),使用 OpenResty XRay 在真實案例裏獲取到的數據和圖表,來詳細闡述 OpenResty、Nginx 和 LuaJIT 的內存分配和管理機制。nginx
下面咱們首先介紹 Nginx 進程在系統層面的內存佔用分佈,而後再逐個介紹應用層面的各類內存分配器。git
在現代操做系統中,進程在最高層面上申請和使用的內存都是虛擬內存。操做系統爲每一個進程分配和管理虛擬內存,並將實際使用的虛擬內存頁,映射到物理內存頁上去(好比 DDR4 內存條等設備裏的)。一個很重要的概念是,進程可能會申請不少的虛擬內存空間,而實際只使用其中很小一部分。好比,一個進程能夠向操做系統申請 2TB 的虛擬內存空間,即便當前系統只有 8GB 的物理內存(RAM)。只要這個進程沒有在這個巨大的虛擬內存空間中讀寫不少內存頁,就不會有任何問題。這部分實際映射到物理內存設備上的虛擬內存空間,纔是咱們真正須要關注的。因此不要由於看到 ps
或者 top
裏顯示佔用了很大的虛擬內存空間(一般叫作 VIRT
)而感到驚慌。github
實際使用的那一小部分虛擬內存(即讀寫了數據的),一般被叫作 RSS
,即 常駐內存(resident memory)。當系統的物理內存快耗盡的時候,一部分常駐內存頁裏的數據會被 交換 到硬盤上1。這部分被交換出去的內存空間再也不是常駐內存的一部分,而是成爲 「交換出去的內存」(簡稱 "swap")。segmentfault
有不少工具能夠提供任意進程(包括 OpenResty 應用的 nginx
工做進程)的虛擬內存佔用、常駐內存佔用和交換出去的內存空間大小。OpenResty XRay 能夠自動分析任意一個正在運行中的 nginx 工做進程,並繪製出很漂亮的內存使用量的分解餅圖:安全
在這張圖裏,整塊餅表明 Nginx 進程從操做系統申請的所有虛擬內存空間。餅中的 Resident Memory
那一片則表明常駐內存的使用量,即實際使用的內存量。最後,Swap
塊則表明被交換出去的內存(此圖中並無出現,這是由於這個進程並無任何被交換出去的內存頁)。服務器
如上所述,咱們一般最關心的是 Resident Memory
這一部分。不過若是餅圖中出現了 Swap
組分,也是很是值得注意的,由於這意味着系統的物理內存已不足,可能會因頻繁換入換出內存頁而過載。另外,咱們也須要注意一下圖中 未使用 的虛擬內存空間。這部分多是由於應用申請了過多過大的 Nginx 共享內存區域。這些還沒有使用的共享內存空間可能在將來某一天被寫滿數據(即它們將轉變成爲 Resident Memory
組分的一部分),從而致使物理內存枯竭。session
咱們將在後續專門的一篇文章裏展開介紹常駐內存相關的更多有趣問題。下面先讓咱們一塊兒看看應用層面的內存使用分解。多線程
在應用層面分析內存使用細節每每會更有幫助。咱們更關心當前使用的內存空間裏有多少是由 LuaJIT 內存分配器分配的,多少是 Nginx 核心和模塊分配的、而多少又是爲 Nginx 的共享內存區域所佔用的,諸如此類。ide
好比下面這個新類型的餅圖,是 OpenResty XRay 自動分析一個 OpenResty 應用的 Nginx 工做進程時獲得的:
餅圖裏的 Glibc Allocator
(Glibc 的分配器)部分是經過 Glibc 庫分配的總內存(Glibc 是 GNU 實現的標準 C 運行時庫)。一般咱們在 C 代碼裏調用 malloc()
、realloc()
、calloc()
等函數就在使用這個內存分配器。它一般也被稱爲系統分配器。Nginx 核心及其模塊也經過這個系統分配器分配內存(有一個例外是 Nginx 的共享內存區域,咱們後面會講到)。一些包含 C 組件或者 FFI 調用的 Lua 庫有時也會直接調用這個系統分配器,不過它們更經常使用的仍是 LuaJIT 的內建分配器。固然,有些用戶也會選擇使用其餘的標準 C 運行時庫實現,來編譯和構建 OpenResty 或 Nginx ,好比 musl libc。咱們也會在後續專門的文章中展開討論系統分配器和 Nginx 的分配器。
餅圖中的 Nginx Shm Loaded
組分是 Nginx 核心及其模塊分配的共享內存(即 "shm")區域中 實際使用 的那部分空間。這些共享內存是經過 UNIX 系統調用 mmap()
直接分配的,所以徹底繞過了標準 C 運行時庫的分配器。
Nginx 共享內存是全部 Nginx 工做進程之間共享的。這些共享內存區域一般是經過標準 Nginx 配置指令來建立的,好比 ssl_session_cache、proxy_cache_path、limit_req_zone、limit_conn_zone、和 upstream 的 zone 指令。Nginx 的第三方模塊也可能會建立本身的共享內存區域,好比 OpenResty 的核心組件 ngx_http_lua_module。OpenResty 應用一般在 Nginx 配置文件中使用 lua_shared_dict 指令來建立本身的共享內存區域。咱們近期也會有專門文章更詳細地闡述 Nginx 的共享內存相關的細節。
餅圖中的 HTTP/Stream LuaJIT Allocator
這兩個組分則表明 LuaJIT 的內建分配器分配和管理的內存大小。
其中一個表示 Nginx 的 HTTP 子系統中的 LuaJIT 虛擬機(VM)實例,另一個表明 Nginx 的 Stream 子系統中的 LuaJIT VM 實例。LuaJIT 有一個編譯選項能夠強制使用系統分配器2,不過這個選項一般只用於特殊的調試和測試工具(好比 Valgrind 和 AddressSanitizer)。Lua 字符串、表(table)、函數、cdata、userdata、upvalue 等等,都是經過這個分配器來分配的。與之相反,原初類型的 Lua 值,好比整數3、浮點數、light userdata 以及布爾值等等,則不須要任何動態內存分配。此外,在 Lua 代碼裏調用 ffi.new()
所分配的 C 級別的內存塊,也是經過 LuaJIT 本身的分配器來分配的。由這個分配器分配的全部內存塊,都由 LuaJIT 的垃圾回收器(GC)來統一管理,所以咱們無需主動釋放再也不須要的內存塊4。這些內存對象也被叫作「GC 對象」。咱們將在其餘文章裏闡述這個課題。
餅圖裏的 Text Segments
組分則對應全部可執行文件和動態連接庫的 .text
段,映射到虛擬內存空間以後的總大小。這些 .text
段一般包含可執行的二進制機器代碼。
最後,圖中的 System Stacks
組分指的是目標進程裏全部系統棧(或者說 「C 棧」)佔用的總大小。每一個操做系統(OS)線程都有本身的系統棧。只有當使用了多線程的時候纔會出現多個系統棧(請注意 OpenResty 中使用 ngx.thread.spawn 建立的 「輕線程」 跟這種系統級別的線程,是徹底不一樣的兩種東西)。Nginx 工做進程一般只有一個系統線程,除非配置了 OS 線程池(經過 aio threads
配置指令)。
有些用戶可能會選擇在本身編譯的 OpenResty 或者 Nginx 中使用第三方內存分配器。常見的例子是 tcmalloc 和
jemalloc,由於它們能夠加速系統分配器(好比 malloc
)。對於一些 Nginx 第三方模塊、Lua C 模塊或 C 庫(包括 OpenSSL!)中直接調用 malloc()
申請小內存塊的場景,它們確實能夠提供比較明顯的加速效果。即是對於那些已經使用了設計良好的分配器(好比 Nginx 的內存池和 LuaJIT 的內建分配器)的部分,使用它們則沒有太多好處。反之,使用這樣的「外掛」分配器的軟件庫,會引入新的複雜性和問題。咱們將會在後續文章中更加詳細地闡述。
使用上面介紹的應用級別的內存分解圖,並不太好直接分析哪些虛擬內存頁被實際使用,而哪些並無。只有餅圖中的 Nginx Shm Loaded
組分是實際使用的虛擬內存空間,而其餘組分則同時包含了使用了的和還沒有使用的虛擬內存頁。幸運的是,Glibc 的分配器和 LuaJIT 的分配器分配的內存,常常都會被當即實際使用的,因此絕大多數時候,兩者並無多少差異。
傳統的 Nginx 服務器軟件只是 OpenResty 應用的嚴格子集。這些用戶仍會看到系統分配器的內存用量和 Nginx 共享內存區域的使用量,偶爾也會涉及一些其餘內存分配器。OpenResty XRay 仍然能夠用於直接檢查和分析這些服務器進程,甚至在生產環境。固然,若是你沒有編譯 Lua 模塊進你的 Nginx,那就不會看到任何與 Lua 相關的內存使用。
本文是一個系列文章中的第一篇。這個系列會詳細介紹 OpenResty 和 Nginx 分配和管理內存的細節,以便幫助那些基於這些技術構建的應用可以有效地優化其內存使用。後續的文章會展開介紹每個細分的主題,覆蓋各個不一樣的內存分配器和內存管理機制。敬請期待!
OpenResty XRay 是由 OpenResty Inc. 公司提供的商業產品。咱們使用此產品爲咱們的文章(好比本文)提供直觀的圖表演示和真實系統內部的統計數據。OpenResty XRay 能夠在無需目標程序任何配合的狀況下,幫助用戶深刻洞察其線上或者線下的各類軟件系統的行爲細節,有效地分析和定位各類性能問題、可靠性問題和安全問題。
章亦春是開源項目 OpenResty® 的創始人,同時也是 OpenResty Inc. 公司的創始人和 CEO。他貢獻了許多 Nginx 的第三方模塊,至關多 Nginx 和 LuaJIT 核心補丁,而且設計了 OpenResty XRay 等產品。
咱們提供了英文版原文和中譯版(本文),也在 OpenResty Inc. 官方博客 提供和維護英文版原文和中譯版 。咱們也歡迎讀者提供其餘語言的翻譯版本,只要是全文翻譯不帶省略,咱們都將會考慮採用,很是感謝!