OpenResty 中的 LuaJIT:執行機制與 luajit2 分支
OpenResty 使用 LuaJIT 2.1 執行 Lua 程式碼。LuaJIT 嵌在每個 Nginx worker 程序中,請求處理程式碼執行在輕量級協程裡,熱點路徑會被編譯成機器碼。開源發行版捆綁的是 OpenResty 維護的 luajit2 分支,並與該版 OpenResty 的其他元件一起釋出。簡單來說,LuaJIT 是執行時,luajit2 是 OpenResty 維護、帶有面向 OpenResty 改動的 LuaJIT 分支;luajit2 的 README 把它定位為分支而非 fork,因為它會定期同步上游改動。OpenResty Inc. 還提供了可直接替代標準 LuaJIT 執行時的私有增強版 LuaJIT-plus。本文介紹 LuaJIT 在 OpenResty 中如何工作,以及這些差異在實踐中意味著甚麼。
OpenResty 使用甚麼 Lua 執行時
LuaJIT 是 Lua 語言的直譯器和即時編譯器(JIT),由 Mike Pall 建立。它實現 Lua 5.1 語言,並提供一部分 Lua 5.2 相容擴充套件,以及 ffi、bit、jit 等 LuaJIT 功能。OpenResty 使用 LuaJIT 2.1;從 lua-nginx-module v0.10.16 起,該模組要求 LuaJIT,不再支援標準 Lua 直譯器(PUC-Rio Lua)。
OpenResty 發行版使用的 LuaJIT 來自 OpenResty 維護的 luajit2 分支。該分支會定期同步上游改動,也包含 OpenResty 自己維護的修改。
每個 OpenResty 發行版捆綁的 LuaJIT 版本可以從對應發行說明和原始碼包中確認。例如,OpenResty 1.31.1.1 捆綁 LuaJIT v2.1-20260415。部署時通常使用發行版捆綁的組合,避免自行拼接 LuaJIT、lua-nginx-module 和其他依賴的版本。
Lua 程式碼如何在 Nginx worker 中執行
worker、虛擬機器與模組狀態
LuaJIT 虛擬機器存在於 Nginx worker 程序中。HTTP 和 Stream 子系統各自使用 Lua 虛擬機器;一個 worker 是否有兩個例項,取決於它實際啟用了哪些 Lua 子系統。不同 worker 程序之間不共享 Lua 虛擬機器。
透過 require 載入的模組會快取在相應虛擬機器中。模組級變數及其引用的狀態會在該 worker 的後續請求間保留,但其他 worker 有各自的模組例項和狀態。需要跨 worker 共享的資料,應使用 lua_shared_dict 等共享機制,參見在 OpenResty 中跨請求共享資料。
請求階段與協程
Nginx 將請求處理劃分為 rewrite、access、content、log 等階段。OpenResty 透過 rewrite_by_lua*、access_by_lua*、content_by_lua* 等指令,在相應階段執行 Lua 程式碼。
請求處理中的 Lua 程式碼執行在輕量級 Lua 協程裡。使用 cosocket 訪問 Redis、MySQL 或 HTTP 上游時,網路等待期間當前協程會掛起,worker 可以繼續處理其他事件;資料就緒後再恢復協程。這個行為依賴 OpenResty 提供的非阻塞 API,Lua 標準庫中的阻塞呼叫不會自動變成非阻塞操作。剛接觸 OpenResty 的讀者可以從七步實戰教程瞭解完整應用流程。
從解釋執行到 JIT 編譯
LuaJIT 先用直譯器執行程式碼,並在執行過程中監測熱點路徑。達到編譯條件後,JIT 會記錄執行路徑並嘗試生成機器碼,稱為 trace。後續執行符合該 trace 條件的路徑時,可以執行生成的機器碼。
Trace abort 可能由多種情況引起,例如遇到尚未實現 JIT 支援的操作(NYI),或達到記錄限制。它表示某次 trace 記錄中止,並不等於整個函式從此只能解釋執行:後續執行可能繼續走直譯器,也可能再次觸發編譯嘗試。因此,abort 計數、lj_BC_* 幀或 trace#N 幀都只是診斷線索,應結合取樣和具體分析工具解讀。
基準測試也要考慮 JIT 預熱:若目標是測量穩定執行後的熱點效能,應先讓被測程式碼執行到預期狀態,再計時。測量方法見在 OpenResty 中正確地測量 Lua 程式碼的執行時間。關於 trace 和解釋執行在取樣火焰圖中的表現,見Lua CPU 火焰圖實戰;開發時檢視 JIT 日誌可參考 resty 命令列工具演示。
luajit2 與上游 LuaJIT 有哪些差異
分支改動會隨著版本變化。下面列出 luajit2 README 中記錄的代表性改動;具體功能和引數應以正在使用的 LuaJIT 版本及構建選項為準。
Lua 表操作 API
luajit2 提供若干表操作函式,例如 table.isempty、table.isarray、table.nkeys 和 table.clone。它們用於判斷表是否為空、判斷陣列形態、統計表中鍵值數量和淺複製表。分支文件說明這些 API 可以被 JIT 編譯。使用時應查閱對應版本文件,尤其是 table.isarray 對空表及稀疏表的定義。
面向大型應用的 JIT 預設引數
luajit2 對部分 JIT 引數採用了更適合大型 OpenResty 應用的預設值。分支 README 列出的設定包括:
maxtrace=8000
maxrecord=16000
minstitch=3
maxmcode=40960 -- KB
maxtrace 控制 trace 快取的數量上限,maxrecord 限制單條 trace 可記錄的 IR 指令數,minstitch 是拼接 trace 所需的最少 IR 指令數,maxmcode 限制機器碼記憶體用量。作為比較,LuaJIT 上游文件列出的預設值中,maxtrace 為 1000、maxrecord 為 4000、maxmcode 為 2048 KB。引數可能因分支版本和執行時設定不同而變化;可用 jit.opt 或相應診斷工具確認程序裡的實際值。
OpenResty 核心使用的 C API
分支增加了供 OpenResty 核心使用的 C API。例如,lua_resetthread 可將 Lua 執行緒重置後複用,lua-nginx-module 的 lua_thread_cache_max_entries 會利用該能力快取輕執行緒。分支還在 luajit.h 中定義了 OPENRESTY_LUAJIT 宏,供 C 模組在編譯期識別 OpenResty 特定版本。
這些介面主要面向 OpenResty 核心和 C 模組開發者。若使用上游 LuaJIT 或自行組合依賴,應按具體版本檢查介面是否存在,不能假設所有分支功能都能互換。lua-nginx-module 仍支援官方 LuaJIT 2.x 釋出版,但其文件指出效能會明顯下降;檢測到非 OpenResty 的 LuaJIT 時,Nginx 還會記錄一條告警日誌。
位元組碼工具與其他修改
luajit2 的位元組碼轉儲工具增加了 -bL 原始碼行號顯示選項,並更新了 -bl 輸出。分支還提高了允許的 upvalue 數量上限,幷包含尚未合入上游的 JIT、虛擬機器修復和除錯輔助功能。完整清單及適用版本見 luajit2 README。
GC64 是另一個容易與分支改動混為一談的特性:它屬於 LuaJIT 的構建模式。OpenResty 從 1.15.8.1 起在 x86_64 上預設以 GC64 模式構建;該模式提高 LuaJIT GC 可管理的地址空間上限。背景和限制詳見 LuaJIT GC64 模式。
LuaJIT-plus:面向生產記憶體治理的執行時增強版
上面介紹的是 OpenResty 開源發行版預設捆綁的 luajit2。對於需要更嚴格控制長期執行記憶體的生產服務,OpenResty Inc. 還提供 LuaJIT-plus——它位於同一個 LuaJIT 執行時層,可以作為 OpenResty 部署中標準 LuaJIT 的直接替代,並屬於 OpenResty 私有庫家族。它不是 luajit2 的另一個 Lua 語法模式,也不是 GC64 的別名,而是面向生產環境的增強執行時;具體版本、發行方式和相容性應以私有庫文件為準。
LuaJIT 的 GC 回收 Lua 物件,並不保證分配器會立即把底層物理頁歸還給作業系統。因此,在大量建立和釋放短生命週期物件的長時間執行服務中,可能出現 Lua GC 指標正常但 RSS 持續升高的現象。LuaJIT-plus 針對的是這個分配器層的問題:它增加了對空閒、碎片化記憶體的主動治理能力,使 RSS 更接近實際工作集,從而降低因 RSS 虛高導致的 OOM Kill 風險。關於診斷方法、分配器行為和生產資料,見 修復 OpenResty 中 LuaJIT RSS 記憶體洩漏。
兩者的使用邊界可以這樣理解:普通 OpenResty 應用使用發行版捆綁的 luajit2 即可;如果服務遇到 RSS 只增不降、記憶體限制難以設定或需要更可預測的長期執行記憶體模型,可以評估 LuaJIT-plus。它替換的是執行時,因此需要在構建或部署時使用對應的發行版;但其主動歸還記憶體的機制用於線上執行過程中,不等同於透過定時重啟釋放記憶體。LuaJIT-plus 還整合了高效能 JSON 庫 jit.cjson,這是執行時增強之外的配套能力。
FFI 與 OpenResty API
LuaJIT 自帶 FFI,可讓 Lua 程式碼呼叫 C 函式並操作 C 資料結構。OpenResty 隨發行版提供的 lua-resty-core 使用 FFI 重新實現了部分 ngx.* 和 ndk.* API;這些 FFI 實現可以與 LuaJIT JIT 編譯器配合工作。該庫預設隨 OpenResty 提供,建議使用對應發行版捆綁的版本,以保持相容性。
LuaJIT 與 luajit2:常見問題
LuaJIT 和 luajit2 有甚麼區別?
luajit2 是 OpenResty 維護的 LuaJIT 2.1 分支,不是另一種語言:它執行同樣的 Lua 5.1 程式碼,並定期同步上游改動。在上游之外,它增加了 table.isempty、table.nkeys 等表操作 API、面向大型應用的 JIT 預設引數,以及 OpenResty 核心使用的 C API。具體差異取決於發行版捆綁的分支版本。
OpenResty 應該用上游 LuaJIT 還是 luajit2?
標準的 OpenResty 部署應使用該 OpenResty 發行版捆綁的 LuaJIT,其中的 LuaJIT、lua-nginx-module 及相關元件是一起打包的。lua-nginx-module 也支援官方 LuaJIT 2.x 釋出版,但其文件指出效能會明顯下降;檢測到非 OpenResty 的 LuaJIT 時,Nginx 還會記錄一條告警日誌。
luajit2 和 LuaJIT-plus 有甚麼區別?
luajit2 是 OpenResty 開源發行版捆綁的 LuaJIT 分支。LuaJIT-plus 是另行提供的增強執行時,面向生產環境的記憶體治理,針對的是 Lua 物件被回收後 RSS 仍居高不下的分配器行為。它不是 luajit2 或 GC64 的別名。
按問題繼續閱讀
- 啟動時 Lua 模組載入較慢:看預編譯 Lua 模組到 LuaJIT 位元組碼。
- 遇到 LuaJIT GC 管理記憶體上限:看LuaJIT GC64 模式。
- RSS 持續增長,但 Lua GC 指標看起來正常:看LuaJIT RSS 記憶體問題與 LuaJIT-plus。
- 想定位線上 Lua CPU 熱點:看Lua CPU 火焰圖實戰。
- 想檢查 JIT 編譯日誌或測量程式碼耗時:看
resty命令列工具演示和Lua 程式碼執行時間測量。
關於作者
章亦春是開源 OpenResty 專案創始人,也是 OpenResty Inc. 的創始人兼 CEO。













