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