一. 概述
Nginx是一個高性能,支持高併發的,輕量級的web服務器。目前,Apache依然web服務器中的老大,可是在全球前1000大的web服務器中,Nginx的份額爲22.4%。Nginx採用模塊化的架構,官方版本的Nginx中大部分功能都是經過模塊方式提供的,好比Http模塊、Mail模塊等。經過開發模塊擴展Nginx,能夠將Nginx打形成一個全能的應用服務器,這樣能夠將一些功能在前端Nginx反向代理層解決,好比登陸校驗、js合併、甚至數據庫訪問等等。 可是,Nginx模塊須要用C開發,並且必須符合一系列複雜的規則,最重要的用C開發模塊必需要熟悉Nginx的源代碼,使得開發者對其望而生畏。淘寶的agentzh和chaoslawful開發的ngx_lua模塊經過將lua解釋器集成進Nginx,能夠採用lua腳本實現業務邏輯,因爲lua的緊湊、快速以及內建協程,因此在保證高併發服務能力的同時極大地下降了業務邏輯實現成本。 本文向你們介紹ngx_lua,以及我在使用它開發項目的過程當中遇到的一些問題。前端
二. 準備
首先,介紹一下Nginx的一些特性,便於後文介紹ngx_lua的相關特性。web
Nginx進程模型
Nginx採用多進程模型,單Master—多Worker,由Master處理外部信號、配置文件的讀取及Worker的初始化,Worker進程採用單線程、非阻塞的事件模型(Event Loop,事件循環)來實現端口的監聽及客戶端請求的處理和響應,同時Worker還要處理來自Master的信號。因爲Worker使用單線程處理各類事件,因此必定要保證主循環是非阻塞的,不然會大大下降Worker的響應能力。數據庫
Nginx處理Http請求的過程
表面上看,當Nginx處理一個來自客戶端的請求時,先根據請求頭的host、ip和port來肯定由哪一個server處理,肯定了server以後,再根據請求的uri找到對應的location,這個請求就由這個location處理。實際Nginx將一個請求的處理劃分爲若干個不一樣階段(phase),這些階段按照先後順序依次執行,也就是說NGX_HTTP_POST_READ_PHASE在第一個,NGX_HTTP_LOG_PHASE在最後一個。
[plain] view plain copy 在CODE上查看代碼片派生到個人代碼片
NGX_HTTP_POST_READ_PHASE, //0讀取請求phase
NGX_HTTP_SERVER_REWRITE_PHASE,//1這個階段主要是處理全局的(server block)的rewrite
NGX_HTTP_FIND_CONFIG_PHASE, //2這個階段主要是經過uri來查找對應的location,而後根據loc_conf設置r的相應變量
NGX_HTTP_REWRITE_PHASE, //3這個主要處理location的rewrite
NGX_HTTP_POST_REWRITE_PHASE, //4postrewrite,這個主要是進行一些校驗以及收尾工做,以便於交給後面的模塊。
NGX_HTTP_PREACCESS_PHASE, //5好比流控這種類型的access就放在這個phase,也就是說它主要是進行一些比較粗粒度的access。
NGX_HTTP_ACCESS_PHASE, //6這個好比存取控制,權限驗證就放在這個phase,通常來講處理動做是交給下面的模塊作的.這個主要是作一些細粒度的access
NGX_HTTP_POST_ACCESS_PHASE, //7通常來講當上面的access模塊獲得access_code以後就會由這個模塊根據access_code來進行操做
NGX_HTTP_TRY_FILES_PHASE, //8try_file模塊,就是對應配置文件中的try_files指令,可接收多個路徑做爲參數,當前一個路徑的資源沒法找到,則自動查找下一個路徑
NGX_HTTP_CONTENT_PHASE, //9內容處理模塊
NGX_HTTP_LOG_PHASE //10log模塊
每一個階段上能夠註冊handler,處理請求就是運行每一個階段上註冊的handler。Nginx模塊提供的配置指令只會通常只會註冊並運行在其中的某一個處理階段。
好比,set指令屬於rewrite模塊的,運行在rewrite階段,deny和allow運行在access階段。瀏覽器
子請求(subrequest)
其實在Nginx 世界裏有兩種類型的「請求」,一種叫作「主請求」(main request),而另外一種則叫作「子請求」(subrequest)。 所謂「主請求」,就是由 HTTP 客戶端從 Nginx 外部發起的請求。好比,從瀏覽器訪問Nginx就是一個「主請求」。 而「子請求」則是由 Nginx 正在處理的請求在 Nginx 內部發起的一種級聯請求。「子請求」在外觀上很像 HTTP 請求,但實現上卻和 HTTP 協議乃至網絡通訊一點兒關係都沒有。它是 Nginx 內部的一種抽象調用,目的是爲了方便用戶把「主請求」的任務分解爲多個較小粒度的「內部請求」,併發或串行地訪問多個 location 接口,而後由這些 location 接口通力協做,共同完成整個「主請求」。固然,「子請求」的概念是相對的,任何一個「子請求」也能夠再發起更多的「子子請求」,甚至能夠玩遞歸調用(即本身調用本身)。
當一個請求發起一個「子請求」的時候,按照 Nginx 的術語,習慣把前者稱爲後者的「父請求」(parent request)。
[plain] view plain copy 在CODE上查看代碼片派生到個人代碼片
location /main {
echo_location /foo; # echo_location發送子請求到指定的location
echo_location /bar;
}
location /foo {
echo foo;
}
location /bar {
echo bar;
}
輸出:
$ curl location/main
$ foo 03. bar服務器
這裏,main location就是發送2個子請求,分別到foo和bar,這就相似一種函數調用。網絡
「子請求」方式的通訊是在同一個虛擬主機內部進行的,因此 Nginx 核心在實現「子請求」的時候,就只調用了若干個 C 函數,徹底不涉及任何網絡或者 UNIX 套接字(socket)通訊。咱們由此能夠看出「子請求」的執行效率是極高的。多線程
協程(Coroutine)
協程相似一種多線程,與多線程的區別有:架構
三. ngx_lua
原理
ngx_lua將Lua嵌入Nginx,可讓Nginx執行Lua腳本,而且高併發、非阻塞的處理各類請求。Lua內建協程,這樣就能夠很好的將異步回調轉換成順序調用的形式。ngx_lua在Lua中進行的IO操做都會委託給Nginx的事件模型,從而實現非阻塞調用。開發者能夠採用串行的方式編寫程序,ngx_lua會自動的在進行阻塞的IO操做時中斷,保存上下文;而後將IO操做委託給Nginx事件處理機制,在IO操做完成後,ngx_lua會恢復上下文,程序繼續執行,這些操做都是對用戶程序透明的。 每一個NginxWorker進程持有一個Lua解釋器或者LuaJIT實例,被這個Worker處理的全部請求共享這個實例。每一個請求的Context會被Lua輕量級的協程分割,從而保證各個請求是獨立的。 ngx_lua採用「one-coroutine-per-request」的處理模型,對於每一個用戶請求,ngx_lua會喚醒一個協程用於執行用戶代碼處理請求,當請求處理完成這個協程會被銷燬。每一個協程都有一個獨立的全局環境(變量空間),繼承於全局共享的、只讀的「comman data」。因此,被用戶代碼注入全局空間的任何變量都不會影響其餘請求的處理,而且這些變量在請求處理完成後會被釋放,這樣就保證全部的用戶代碼都運行在一個「sandbox」(沙箱),這個沙箱與請求具備相同的生命週期。 得益於Lua協程的支持,ngx_lua在處理10000個併發請求時只須要不多的內存。根據測試,ngx_lua處理每一個請求只須要2KB的內存,若是使用LuaJIT則會更少。因此ngx_lua很是適合用於實現可擴展的、高併發的服務。併發