瞭解 OpenResty XRay 是如何做到幫助企業定位應用程式存在的問題以及最佳化其效率的。

瞭解更多 LIVE DEMO

本 OpenResty 教程系列將帶你從一臺全新的 Linux 伺服器出發,透過七個動手步驟搭建出一個可執行的 Nginx + Lua 應用——無需任何 Lua 基礎。

OpenResty 是一個功能完整的 Web 平臺,它在 Nginx 之上內建了 LuaJIT 執行時。你不再需要編寫 C 模組來定製 Nginx 的行為,而是編寫 Lua——讓 Nginx 變快的那套非阻塞事件迴圈,同樣也讓你的 Lua 程式碼變快。單個 OpenResty 工作程序無需建立執行緒,就能處理數萬個併發連線。

下面的每一節都涵蓋學習路徑上的一個里程碑,附帶你將構建內容的概述以及指向完整實戰文章的連結。如果你剛接觸 OpenResty,請按順序完成這些步驟;如果你在尋找某個特定主題,也可以直接跳到相應的章節。

甚麼是 OpenResty?

OpenResty 將 Nginx、LuaJIT 以及一組 Lua 庫打包成一個統一的發行版。當一個請求到達時,Nginx 會透過其事件驅動的流水線對其進行路由,並在可配置的各個階段將控制權交給一個 Lua 協程。這個協程可以執行非阻塞 I/O——TCP 連線、DNS 查詢、定時器——而不會阻塞工作執行緒,因為 OpenResty 的 cosocket API 會掛起該協程,並在資料就緒時將其恢復。

其結果是這樣一種架構:

  • 一個作業系統執行緒多路複用數千個併發請求。
  • Lua 程式碼取代 C 來實現自定義邏輯——限流、認證、動態路由、響應轉換。
  • LuaJIT 將熱點 Lua 路徑編譯為原生機器碼,在 CPU 密集型工作上縮小與手寫 C 之間的差距。

OpenResty 已部署在全球超過 4000 萬個域名上,並且是 Kong、Apache APISIX 以及 OpenResty Edge 等產品的基礎。

本教程適合誰

本系列面向已經理解 HTTP、並且使用過 Nginx 作為反向代理或靜態檔案伺服器的工程師。不需要任何 Lua 基礎——每篇文章都會恰好引入該步驟所需的 Lua。如果你有 Python、Go 或 Node.js 的背景,你會發現這種非阻塞模型很熟悉;主要的適應點在於,一切都執行在 Nginx 的事件迴圈內部,而不是一個獨立的程序中。

OpenResty 教程系列

本系列遵循一條循序漸進的學習路徑。每篇文章都建立在前一篇的基礎之上。

第 1 步 —— 安裝 OpenResty

在編寫任何 Lua 之前,你需要一個可用的 OpenResty 安裝。OpenResty 為所有主流 Linux 發行版提供了預編譯的二進位制包——使用官方的 APT 或 YUM 倉庫安裝耗時不到五分鐘,且無需編譯。

如何安裝 OpenResty

如需覆蓋所有受支援 Linux 發行版的官方軟體包倉庫說明,請參閱 OpenResty 官方 Linux 軟體包頁面

安裝後,透過以下命令驗證你執行的是 OpenResty 的 Nginx 二進位制檔案(而非系統自帶的 Nginx):

export PATH=/usr/local/openresty/nginx/sbin:$PATH
which nginx
nginx -v

第 2 步 —— Hello World:你的第一個 OpenResty 應用

OpenResty 中的 Hello World HTTP 示例

第一篇文章將帶你瞭解從零執行一個 OpenResty 應用所需的最小專案結構。你會建立 conf/logs/ 目錄,編寫一個最小化的 nginx.conf,用 content_by_lua_block 嵌入 Lua,並用 curl 驗證結果。

location / {
    default_type text/plain;
    content_by_lua_block {
        ngx.say("Hello World")
    }
}

讀完之後,你將理解 nginx -p 字首約定,知道如何啟動和停止伺服器,並擁有一個可在本系列後續每篇文章中複用的專案骨架。

第 3 步 —— Lua 模組:組織你的應用程式碼

如何在 OpenResty 中編寫 Lua 模組

內聯的 content_by_lua_block 程式碼用於單行內容沒問題,但任何真實應用都需要把可複用的函式放進獨立的檔案中。本文介紹 _M 表模式——在 OpenResty 中宣告 Lua 模組的標準方式——並展示如何配置 lua_package_path,讓 require 能在執行時找到你的模組。

local _M = {}

function _M.greet(name)
    ngx.say("Greetings from ", name)
end

return _M

本文還涵蓋 init_by_lua_block,它在伺服器啟動時預載入模組,這樣第一個真實請求就不必再付出載入成本。這是後續每篇文章的基礎。

第 4 步 —— 在請求間共享資料

如何在 OpenResty 中跨請求共享資料

OpenResty 的工作程序是相互隔離的作業系統程序,預設情況下不共享記憶體。本文介紹 OpenResty 提供的兩種跨請求共享狀態的機制:

Lua 模組變數lua_shared_dict
作用域單個工作程序所有工作程序
值型別任意 Lua 型別字串、數字、布林值
典型用途單程序 LRU 快取、配置計數器、限流、共享標誌位
原子操作是(incraddset

在構建任何需要跟蹤狀態的東西——計數器、限流器或連線池——之前,理解單程序共享與跨程序共享之間的區別至關重要。

第 5 步 —— 流式 HTTP 響應

在 OpenResty 中用 ngx.flush 流式輸出 HTTP 響應

OpenResty 的 ngx.flush(true) 呼叫是完全非阻塞的:它把緩衝的輸出推送給客戶端,並掛起當前協程直到寫入完成,在此期間釋放工作執行緒去服務其他請求。這使得流式輸出大響應——或實現伺服器傳送事件(SSE)——變得直截了當,而不會造成阻塞。

本文演示了:

  • 為分塊流式輸出配置一個 location
  • 在迴圈中使用 ngx.flush(true)ngx.sleep()
  • 用單個工作程序對 500 個併發的慢速流式連線進行壓測(結果:120+ 請求/秒,單個作業系統執行緒)
  • 透過 XMLHttpRequest 從瀏覽器消費該流

第 6 步 —— 命令列工具:resty 與 restydoc

OpenResty 附帶了兩個能加速日常開發的命令列工具。它們用途不同,因此分成了兩篇文章介紹。

resty 命令列執行器

OpenResty resty 命令列教程

resty 會在幕後啟動一個無頭的 Nginx,直接從終端執行你的 Lua 程式碼——無需 nginx.conf。它是測試 cosocket 呼叫、快速原型化模組函式或檢視 LuaJIT 編譯輸出的最快方式。

resty -e 'ngx.say("Hello World")'
resty -I lua/ -e 'require "mymodule".run()'
resty --shdict 'cache 10m' -e 'print(ngx.shared.cache:set("k", 1))'
resty -jv bench.lua   # 檢視 JIT trace

用 restydoc 查閱文件

如何用 restydoc 查閱 OpenResty 文件

restydoc 把完整的 OpenResty 參考文件——Nginx 指令、ngx.* Lua API、LuaJIT 擴充套件以及內建 Lua 模組文件——帶進你的終端,無需開啟瀏覽器。它仿照 Perl 的 perldoc 設計,並使用 less 分頁器。

restydoc -s ngx.flush          # OpenResty Lua API
restydoc -s lua_shared_dict    # Nginx 指令
restydoc -s string.find        # 標準 Lua
restydoc resty.redis           # 完整的 Lua 模組文件

第 7 步 —— 效能:壓測與 LuaJIT 位元組碼

正確地對 Lua 程式碼進行壓測

如何在 OpenResty 中對 Lua 程式碼進行壓測

time resty 來測量 Lua 效能會產生誤導——resty 程序的啟動會增加約 11 毫秒的開銷,足以淹沒納秒級的函式成本。正確的做法是使用 ngx.now() 測量耗時,用一個預熱迴圈確保函式在開始記錄之前已被 JIT 編譯,並用 collectgarbage() 清除待處理的 GC 物件。

local function target()
    ngx.re.find("hello, world.", [[\w+\.]], "jo")
end

for i = 1, 100 do target() end  -- JIT 預熱

ngx.update_time()
local begin = ngx.now()
local N = 1e7
for i = 1, N do target() end
ngx.update_time()
ngx.say("elapsed: ", (ngx.now() - begin) / N)

將 Lua 模組預編譯為 LuaJIT 位元組碼

OpenResty 中的 LuaJIT 位元組碼:如何預編譯 Lua 模組

對於較大的 Lua 模組,預編譯為 .ljbc 位元組碼檔案能顯著縮短啟動時間。一個 1.6 MB 的 Lua 原始檔原本需要 12 毫秒載入,預編譯後降到了 2 毫秒——提升了 6 倍。位元組碼檔案也比原始檔小 50% 以上。

/usr/local/openresty/luajit/bin/luajit -bg mymodule.lua mymodule.ljbc

nginx.conf 中,讓 OpenResty 優先使用 .ljbc 檔案:

lua_package_path "$prefix/lua/?.ljbc;$prefix/lua/?.lua;;";

注意:.ljbc 檔案在不同的 OpenResty 構建模式(x64 與 GC64)之間不可移植。升級 OpenResty 後請始終從原始檔重新編譯位元組碼。

核心概念速查表

概念作用引入於
content_by_lua_block在 location 的 content 階段嵌入一個 Lua 處理器Hello World
ngx.say()向響應緩衝區寫入一行Hello World
lua_package_path告訴 require 到哪裡找 .lua.ljbc 檔案Lua 模組
init_by_lua_block在伺服器啟動時執行一次 Lua;用於預載入模組Lua 模組
lua_shared_dict宣告一塊所有工作程序都能訪問的具名共享記憶體區資料共享
ngx.shared.DICT用於讀寫共享記憶體字典的 Lua API資料共享
ngx.flush(true)非阻塞地將寫緩衝區重新整理給客戶端流式輸出
ngx.sleep()讓出當前協程而不阻塞工作執行緒流式輸出
resty -e從終端執行內聯 Lua,無需配置檔案resty 命令列
restydoc -s在終端分頁器中查閱某個指令或 API 章節restydoc
ngx.now()返回以秒為單位、毫秒精度的牆上時鐘時間壓測
.ljbcLuaJIT 預編譯位元組碼檔案;載入速度快於 .lua 原始檔位元組碼

常見問題

OpenResty 可以免費使用嗎?

可以。OpenResty 是免費的開源軟體。它在 BSD 許可證下發布,與 Nginx 使用的許可證相同。完整的原始碼、面向 Linux 的預編譯二進位制包以及內建的 Lua 庫,都可以從 openresty.org 免費獲取。在生產環境中執行 OpenResty 無需任何付費許可證。

OpenResty 和 Nginx 有甚麼區別?

OpenResty 是 Nginx 的超集。根據 OpenResty 官方網站,OpenResty 是"一個基於 NGINX 與 LuaJIT 的動態 Web 平臺"。每一份有效的 nginx.conf 都能在 OpenResty 上原樣執行,而且你可以逐步新增 Lua——一次一個 location。區別在於,OpenResty 內嵌了 LuaJIT,並暴露了一套非阻塞的 Lua API——即 ngx.* 名稱空間——讓你無需編譯 C 模組就能編寫自定義的請求處理邏輯。如果你需要可程式設計的請求邏輯,就用 OpenResty;如果你只需要一個不含自定義程式碼的普通反向代理或靜態檔案伺服器,單獨的 Nginx 就足夠了。

OpenResty 用來做甚麼?

OpenResty 最常見的用途是作為可程式設計的 API 閘道器、Web 應用防火牆(WAF)、帶自定義認證或路由邏輯的反向代理,以及 CDN 邊緣節點。由於 Lua 在 Nginx 的事件迴圈內部以納秒級延遲執行,它非常適合任何原本需要一個 sidecar 程序或一次到外部服務往返的任務——令牌校驗、限流、請求改寫和 A/B 路由都是典型的例子。

OpenResty 使用哪個版本的 Lua?

OpenResty 使用 LuaJIT 2.x,它實現了 Lua 5.1 語言,並帶有一小組 Lua 5.2 相容擴充套件。OpenResty 維護著自己的 LuaJIT 分支,包含用於正確性和效能的額外補丁。標準的 Lua 5.1 程式碼無需修改即可執行;LuaJIT 特有的特性,如 FFI 和 bit 庫,同樣可用。

如果我已經會 Nginx,OpenResty 難學嗎?

Nginx 配置這一側是完全相同的。新的部分是 Lua 層:ngx.* API、基於協程的併發模型,以及 Nginx 請求階段(access_by_lua_blockcontent_by_lua_blocklog_by_lua_block 等)的概念。本教程系列會在每個概念第一次被需要的地方引入它,因此不需要 Lua 基礎也能跟上。

OpenResty 有企業版嗎?

有。OpenResty Edge 是構建在 OpenResty 之上的商業產品。它將私有 CDN、通用閘道器、WAF、DDoS 防護和 Kubernetes 整合打包進一個可本地部署的平臺——透過 Web UI 管理,而不再手動編輯 nginx.conf 檔案。OpenResty Edge 由 OpenResty 的創始人所創立的 OpenResty Inc. 公司開發和支援。

如何除錯 OpenResty 中的 Lua 錯誤?

Lua 執行時錯誤會被寫入 Nginx 的錯誤日誌。首先應該檢視的是:

tail -f logs/error.log

如需更結構化的除錯,restydoc -s ngx.log 記錄了日誌 API。resty 命令列工具在把一個 bug 從執行中的伺服器裡隔離出來並復現時也很有用。

本系列之後可以繼續探索甚麼

完成上面的七個步驟後,你就具備了構建生產級 OpenResty 應用的基礎。接下來可以探索的主題包括:

  • resty.lrucache —— 一個單程序 LRU 快取模組,適用於無需跨程序共享的熱點資料
  • Cosocket 連線 —— 用 ngx.socket.tcp() 非阻塞地連線 Redis、MySQL、PostgreSQL 及其他後端
  • ngx.timer.atngx.timer.every —— 用於在請求週期之外執行週期性任務的後臺定時器
  • OpenResty XRay —— 一款非侵入式的動態追蹤工具,無需修改程式碼即可對執行中的 OpenResty 應用進行剖析和排障

如果你喜歡這個教程系列,請訂閱本部落格和我們的 YouTube 頻道

關於作者

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

我們的微信公眾號

翻譯

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