OpenResty 概要及原理科普
OpenResty? 是一個基于?Nginx?與 Lua 的高性能 Web 平臺,其內部集成了大量精良的 Lua 庫、第三方模塊以及大多數的依賴項。用于方便地搭建能夠處理超高并發、擴展性極高的動態 Web 應用、Web 服務和動態網關。OpenResty 官網地址:https://openresty.org/cn/。
OpenResty主要包含兩方面的技術:
-
Nginx: 一個免費的、開源的、高性能的 HTTP 服務器和反向代理,也是一個電子郵件(IMAP/POP3/SMTP)代理服務器。有關Nginx的介紹,可以查看這篇《Nginx架構原理科普》。
-
Lua: 一種輕量、小巧、可移植、快速的腳本語言;LuaJIT即時編譯器會將頻繁執行的Lua代碼編譯成本地機器碼交給CPU直接執行,執行效率更高,OpenResty會默認啟用LuaJIT。
歷史
OpenResty 最早是雅虎中國的一個公司項目,起步于 2007 年 10 月。當時興起了 OpenAPI 的熱潮,用于滿足各種 Web Service 的需求,就誕生了 OpenResty。在公司領導的支持下,最早的 OpenResty 實現從一開始就開源了。
最初的定位是服務于公司外的開發者,像其他的 OpenAPI 那樣,但后來越來越多地是為雅虎中國的搜索產品提供內部服務。這是第一代的 OpenResty,當時的想法是,提供一套抽象的 Web Service,能夠讓用戶利用這些 Web Service 構造出新的符合他們具體業務需求的 Web Service 出來,所以有些“meta web service”的意味,包括數據模型、查詢、安全策略都可以通過這種 meta web service 來表達和配置。同時這種 Web Service 也有意保持 REST 風格。與這種概念相對應的是純 AJAX 的 web 應用,即 web 應用幾乎都使用客戶端 JavaScript 來編寫,然后完全由 ?Web Service 讓 web 應用“活”起來。用戶把 .html/ .js/ .css/ .jpg 等靜態文件下載到 web browser 中,然后 js 開始運行,跨域請求雅虎提供的經過站長定制過的 Web Service ,然后應用就可以運行起來。不過隨著后來的發展,公司外的用戶畢竟還是少數,于是應用的重點是為公司內部的其他團隊提供 Web Service e,比如雅虎中國的全能搜索產品,及其外圍的一些產品。從那以后,開發的重點便放在了性能優化上面。
章亦春在加入淘寶數據部門的量子團隊之后,決定對 OpenResty 進行重新設計和徹底重寫,并把應用重點放在支持像量子統計這樣的 web 產品上面,所以量子統計 3.0 開始也幾乎完全是 Web Service 驅動的純 AJAX 應用。這是第二代的 OpenResty,一般稱之為 ngx_openresty,以便和第一代基于 Perl 和 Haskell 實現的 OpenResty 加以區別。章亦春和他的同事王曉哲一起設計了第二代的 OpenResty。在王曉哲的提議下,選擇基于 Nginx 和 Lua 進行開發。
為什么要取 OpenResty 這個名字呢?OpenResty 最早是順應 OpenAPI 的潮流做的,所以 Open 取自“開放”之意,而 Resty 便是 REST 風格的意思。雖然后來也可以基于 ngx_openresty 實現任何形式的 Web Service 或者傳統的 web 應用。也就是說?Nginx?不再是一個簡單的靜態網頁服務器,也不再是一個簡單的反向代理了。第二代的 OpenResty 致力于通過一系列 Nginx 模塊,把?Nginx?擴展為全功能的 web 應用服務器。(摘自:OpenResty作者章亦春訪談實錄[1])
Lua 與 LuaJIT
要了解OpenResty,那么Lua語言是必須先要了解的,它是 OpenResty 中使用的編程語言。Lua 是一個小巧的腳本語言。是巴西里約熱內盧天主教大學(Pontifical Catholic University of Rio de Janeiro)里的一個研究小組,由 Roberto Ierusalimschy、Waldemar Celes 和 Luiz Henrique de Figueiredo 所組成并于 1993 年開發。Lua在葡萄牙語里代表美麗的月亮。
Lua 在設計之初,就把自己定位為一個簡單、輕量、可嵌入的膠水語言,沒有走大而全的路線。雖然你平常工作中可能沒有直接編寫 Lua 代碼,但 Lua 的使用其實非常廣泛。很多的網游,比如魔獸世界,都會采用 Lua 來編寫插件;而鍵值數據庫 Redis 則是內置了 Lua 來控制邏輯。另一方面,雖然 Lua 自身的庫比較簡單,但它可以方便地調用 C 庫,大量成熟的 C 代碼都可以為其所用。比如在 OpenResty 中,很多時候都需要你調用 NGINX 和 OpenSSL 的 C 函數,而這都得益于 Lua 和 LuaJIT 這種方便調用 C 庫的能力。
Lua 非常高效,它運行得比許多其它腳本(如 Perl、Python、Ruby)都快,這點在第三方的獨立測評中得到了證實。盡管如此,仍然會有人不滿足,他們總覺得“嗯,還不夠快!”。LuaJIT 就是一個為了再榨出一些速度的嘗試,它利用即時編譯(Just-in Time)技術把 Lua 代碼編譯成本地機器碼后交由 CPU 直接執行。LuaJIT 2 的測評報告表明,在數值運算、循環與函數調用、協程切換、字符串操作等許多方面它的加速效果都很顯著。憑借著 FFI 特性,LuaJIT 2 在那些需要頻繁地調用外部 C/C++ 代碼的場景,也要比標準 Lua 解釋器快很多。目前 LuaJIT 2 已經支持包括 i386、x86_64、ARM、PowerPC 以及 MIPS 等多種不同的體系結構。
LuaJIT 是采用 C 和匯編語言編寫的 Lua 解釋器與即時編譯器。LuaJIT 被設計成全兼容標準的 Lua 5.1 語言,同時可選地支持 Lua 5.2 和 Lua 5.3 中的一些不破壞向后兼容性的有用特性。因此,標準 Lua 語言的代碼可以不加修改地運行在 LuaJIT 之上。LuaJIT 和標準 Lua 解釋器的一大區別是,LuaJIT 的執行速度,即使是其匯編編寫的 Lua 解釋器,也要比標準 Lua 5.1 解釋器快很多,可以說是一個高效的 Lua 實現。另一個區別是,LuaJIT 支持比標準 Lua 5.1 語言更多的基本原語和特性,因此功能上也要更加強大。
對于 Lua 語法的學習和使用可以參考這里[2]。
使用示例
為了能夠讓大家對 OpenResty 有個大致的使用印象,這里引用一個官網的示例[3]來做講解。在安裝完OpenResty之后(安裝過程略過),創建工作目錄:
mkdir ~/work cd ~/work mkdir logs/ conf/在新創建的conf/ 目錄下創建一個nginx.conf配置文件,其內容如下:
pid logs/nginx.pid; events{worker_connections 1024; }http{server {listen 8080;location / {content_by_lua 'ngx.say("hello, world")';}} }啟動OpenResty服務:
openresty -p `pwd` -c conf/nginx.conf如果沒有報錯的話,OpenResty服務已經啟動成功了。你可以打開瀏覽器,或者使用curl命令來查看返回的結果:
hidden:~ hidden$ curl -i localhost:8080 HTTP/1.1 200 OK Server: openresty/1.15.8.3 Date: Wed, 22 Apr 2020 03:57:56 GMT Content-Type: text/plain Transfer-Encoding: chunked Connection: keep-alivehello, world這里只是簡單的打印一個“hello, world”,真實使用場景下,肯定會包含大堆的代碼,如果這些代碼全部包含在nginx.conf配置文章中,那么可閱讀性和可維護性是會大大降低的。所以,我們要進一步地將Lua代碼抽離出來。
我們在 ~/work 目錄下再創建一個 lua/ 的目錄,然后再在 lua/ 目錄下創建一個 hello.lua 文件,文件內的內容為:ngx.say("hello, world")。對應的目錄結構如下:
hidden:work hidden$ tree . ├── conf │ ├── nginx.conf ├── logs │ └── nginx.pid ├── lua└── hello.lua之后修改 nginx.conf 的配置,把其中的 content_by_lua 改為 content_by_lua_file:
pid logs/nginx.pid; events{worker_connections 1024; }http{server {listen 8080;location / {content_by_lua_file lua/hello.lua;}} }最后,重啟OpenResty的服務就可以了。
做 OpenResty 開發,lua-nginx-module 的文檔[4]?是你的首選,Lua 語言的庫都是同步阻塞的,用的時候要三思。也就是說,盡量使用 ngx_lua提供的api,而不是使用 Lua 本身的。例如 ngx.sleep()與 lua提供的sleep,前者不會造成阻塞,后者是會阻塞的
原理剖析
OpenResty的工作原理如下圖所示。
如《Nginx架構原理科普》介紹,Nginx?服務器啟動后,產生一個 Master 進程(Master Process),Master 進程執行一系列工作后產生一個或者多個 Worker 進程(Worker Processes)。其中,Master 進程用于接收來自外界的信號,并向各 Worker 進程發送信號,同時監控 Worker 進程的工作狀態。當 Worker 進程退出后(異常情況下),Master 進程也會自動重新啟動新的 Worker 進程。Worker 進程則是外部請求真正的處理者。
多個 Worker 進程之間是對等的,他們同等競爭來自客戶端的請求,各進程互相之間是獨立的。一個請求,只可能在一個 Worker 進程中處理,一個 Worker 進程不可能處理其它進程的請求。Worker 進程的個數是可以設置的,一般我們會設置與機器 CPU 核數一致。同時,Nginx?為了更好的利用多核特性,具有 CPU 綁定選項,我們可以將某一個進程綁定在某一個核上,這樣就不會因為進程的切換帶來cache的失效(CPU affinity)。所有的進程的都是單線程(即只有一個主線程)的,進程之間通信主要是通過共享內存機制實現的。
OpenResty本質上是將 LuaJIT 的虛擬機嵌入到?Nginx?的管理進程和工作進程中,同一個進程內的所有協程都會共享這個虛擬機,并在虛擬機中執行Lua代碼。在性能上,OpenResty接近或超過?Nginx?的C模塊,而且開發效率更高。下面深入介紹一下OpenResty的原理。
Lua協程
協程是不被操作系統內核所管理的,而完全由程序控制(也就是用戶態執行),這樣帶來的好處就是性能得到了極大地提升。進程和線程切換要經過用戶態到內核態再到用戶態的過程,而協程的切換可以直接在用戶態完成,不需要陷入內核態,切換效率高,降低資源消耗。Lua協程與線程類似,擁有獨立的堆棧、獨立的局部變量、獨立的指令指針,同時又與其他協同程序共享全局變量和其他大部分東西。
cosocoket
OpenResty中的核心技術cosocket將 Lua 協程和?Nginx?的事件機制結合在一起,最終實現了非阻塞網絡IO。不僅和HTTP客戶端之間的網絡通信是非阻塞的,與MySQL、Memcached以及Redis等眾多后端之間的網絡通信也是非阻塞的。在OpenResty中調用一個cosocket相關的網絡函數,內部關鍵實現如圖所示:
從圖中可以看出,用戶的Lua腳本每觸發一個網絡操作,都會有協程的yield和resume。當遇到網絡 I/O 時,Lua協程會交出控制權(yield),把網絡事件注冊到?Nginx?監聽列表中,并把運行權限交給?Nginx?。當有?Nginx?注冊網絡事件到達觸發條件時,便喚醒(resume)對應的協程繼續處理。這樣就可以實現全異步的?Nginx?機制,不會影響?Nginx?的高并發處理性能。
多階段處理
基于?Nginx?使用的多模塊設計思想,Nginx?將HTTP請求的處理過程劃分為多個階段。這樣可以使一個HTTP請求的處理過程由很多模塊參與處理,每個模塊只專注于一個獨立而簡單的功能處理,可以使性能更好、更穩定,同時擁有更好的擴展性。
OpenResty在HTTP處理階段基礎上分別在Rewrite/Access階段、Content階段、Log階段注冊了自己的handler,加上系統初始階段master的兩個階段,共11個階段為Lua腳本提供處理介入的能力。下圖描述了OpenResty可以使用的主要階段:
(圖片來源于 lua-nginx-module 文檔)
OpenResty將我們編寫的Lua代碼掛載到不同階段進行處理,每個階段分工明確,代碼獨立。
-
init_by_lua*:Master進程加載?Nginx?配置文件時運行,一般用來注冊全局變量或者預加載Lua模塊。
-
init_worker_by_lua*:每個worker進程啟動時執行,通常用于定時拉取配置/數據或者進行后端服務的健康檢查。
-
set_by_lua*:變量初始化。
-
rewrite_by_lua*:可以實現復雜的轉發、重定向邏輯。
-
access_by_lua*:IP準入、接口權限等情況集中處理。
-
content_by_lua*:內容處理器,接收請求處理并輸出響應。
-
header_filter_by_lua*:響應頭部或者cookie處理。
-
body_filter_by_lua*:對響應數據進行過濾,如截斷或者替換。
-
log_by_lua*:會話完成后,本地異步完成日志記錄。
資料推薦
OpenResty最佳實踐https://legacy.gitbook.com/book/moonbingbing/openresty-best-practices/details
OpenResty官網:https://openresty.org/cn/
lua-nginx-module文檔: https://github.com/openresty/lua-nginx-module#version
極客時間- OpenResty從入門到實踐
參考資料
[1] OpenResty作者章亦春訪談實錄:?https://www.oschina.net/question/28_60461
[2] 這里:?https://moonbingbing.gitbooks.io/openresty-best-practices/content/lua/main.html
[3] 官網的示例:?https://openresty.org/cn/getting-started.html
[4] lua-nginx-module 的文檔:?https://github.com/openresty/lua-nginx-module#version
總結
以上是生活随笔為你收集整理的OpenResty 概要及原理科普的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 苏宁高时效、高并发秒杀业务中台的设计与实
- 下一篇: 秒杀系统流量削峰,这事应该怎么做?