Lua代碼的執行通常要先將代碼變成成字節碼,而後再Lua虛擬機中執行字節碼。lua-nginx-module將編譯後的結果保存了下來,這樣只須要編譯一次,以後即可以直接使用,省去了編譯的消耗。nginx
以access_by_lua爲例,在Access階段會執行指定的一段Lua代碼,這是會調用ngx_http_lua_cache_loadbuffer來加載Lua代碼,函數的實現以下所示緩存
ngx_int_t ngx_http_lua_cache_loadbuffer(ngx_log_t *log, lua_State *L, const u_char *src, size_t src_len, const u_char *cache_key, const char *name) { int n; ngx_int_t rc; const char *err = NULL; n = lua_gettop(L); dd("XXX cache key: [%s]", cache_key); rc = ngx_http_lua_cache_load_code(log, L, (char *) cache_key); if (rc == NGX_OK) { /* code chunk loaded from cache, sp++ */ dd("Code cache hit! cache key='%s', stack top=%d, script='%.*s'", cache_key, lua_gettop(L), (int) src_len, src); return NGX_OK; } if (rc == NGX_ERROR) { return NGX_ERROR; } /* rc == NGX_DECLINED */ dd("Code cache missed! cache key='%s', stack top=%d, script='%.*s'", cache_key, lua_gettop(L), (int) src_len, src); /* load closure factory of inline script to the top of lua stack, sp++ */ rc = ngx_http_lua_clfactory_loadbuffer(L, (char *) src, src_len, name); if (rc != 0) { /* Oops! error occurred when loading Lua script */ if (rc == LUA_ERRMEM) { err = "memory allocation error"; } else { if (lua_isstring(L, -1)) { err = lua_tostring(L, -1); } else { err = "unknown error"; } } goto error; } /* store closure factory and gen new closure at the top of lua stack to * code cache */ rc = ngx_http_lua_cache_store_code(L, (char *) cache_key); if (rc != NGX_OK) { err = "fail to generate new closure from the closure factory"; goto error; } return NGX_OK; error: ngx_log_error(NGX_LOG_ERR, log, 0, "failed to load inlined Lua code: %s", err); lua_settop(L, n); return NGX_ERROR; }
函數中首先調用ngx_http_lua_cache_load_code獲取緩存,沒有的話調用ngx_http_lua_clfactory_loadbuffer來加載Lua代碼,完成後經過ngx_http_lua_cache_store_code將加載的結果緩存起來。app
在ngx_http_lua_clfactory_loadbuffer加載Lua代碼時,在文件的頭和尾分別加了一段代碼,以下面的代碼,socket
local a = 1 local b = 4 ngx.log(ngx.ERR, a+b)
實際加載時至關與函數
return function() local a = 1 local b = 4 ngx.log(ngx.ERR, a+b) end
本來經過lua_load加載一段代碼,生成函數A(以A標識),執行函數A便可。 這裏經過ngx_http_lua_clfactory_loadfile中的封裝,獲得一個Closure Factory(實際上也是一個Lua函數)。每次調用這個closure factory就會返回一個函數(即函數A)性能
ngx_http_lua_clfactory_loadbuffer代碼以下所示lua
ngx_int_t ngx_http_lua_clfactory_loadbuffer(lua_State *L, const char *buff, size_t size, const char *name) { ngx_http_lua_clfactory_buffer_ctx_t ls; ls.s = buff; ls.size = size; ls.sent_begin = 0; ls.sent_end = 0; return lua_load(L, ngx_http_lua_clfactory_getS, &ls, name); }
實際調用的時lua_load,參數中的ngx_http_lua_clfactory_getS表示經過這個函數讀取Lua代碼。調試
#define CLFACTORY_BEGIN_CODE "return function() " #define CLFACTORY_BEGIN_SIZE (sizeof(CLFACTORY_BEGIN_CODE) - 1) #define CLFACTORY_END_CODE "\nend" #define CLFACTORY_END_SIZE (sizeof(CLFACTORY_END_CODE) - 1) static const char * ngx_http_lua_clfactory_getS(lua_State *L, void *ud, size_t *size) { ngx_http_lua_clfactory_buffer_ctx_t *ls = ud; if (ls->sent_begin == 0) { ls->sent_begin = 1; *size = CLFACTORY_BEGIN_SIZE; return CLFACTORY_BEGIN_CODE; } if (ls->size == 0) { if (ls->sent_end == 0) { ls->sent_end = 1; *size = CLFACTORY_END_SIZE; return CLFACTORY_END_CODE; } return NULL; } *size = ls->size; ls->size = 0; return ls->s; }
在ngx_http_lua_clfactory_getS中在Lua代碼的首尾添加的響應的代碼。code
static ngx_int_t ngx_http_lua_cache_store_code(lua_State *L, const char *key) { int rc; /* get code cache table */ lua_pushlightuserdata(L, &ngx_http_lua_code_cache_key); lua_rawget(L, LUA_REGISTRYINDEX); dd("Code cache table to store: %p", lua_topointer(L, -1)); if (!lua_istable(L, -1)) { dd("Error: code cache table to load did not exist!!"); return NGX_ERROR; } lua_pushvalue(L, -2); /* closure cache closure */ lua_setfield(L, -2, key); /* closure cache */ /* remove cache table, leave closure factory at top of stack */ lua_pop(L, 1); /* closure */ /* call closure factory to generate new closure */ rc = lua_pcall(L, 0, 1, 0); if (rc != 0) { dd("Error: failed to call closure factory!!"); return NGX_ERROR; } return NGX_OK; }
先將加載Lua代碼生成的函數保存在table中,此table經過全局的註冊表索引,以避免被GC回收掉。 而後調用lua_pcall執行函數工廠,生成一個新的函數,以後Access階段執行這個新的函數,完成須要的工做。協程
相似在ngx_http_lua_cache_load_code中拿到保存的closure factory後,也須要調用lua_pcall返回新的函數。
這個closure factory存在的意思是什麼呢?從表面上看的話,這種作法除了每次使用時增長了一次lua_pcall的調用外,沒有什麼做用。確實,即便不這樣封裝也能夠照常運行,實際上最初的lua-nginx-module也沒有使用closure factory。後面改用closure factory是爲了讓處理不一樣請求的Lua協程徹底分離,互不影響。
這個涉及到Lua中的環境的概念,每一個Lua函數都關聯了一個稱爲環境的table,在Lua代碼中的全局變量實際是在關聯的環境中,若是沒有closure factory,全部的協程在Access階段執行的是同一個函數,使用的就會使同一個環境。一個協程聲明瞭或者修改全局變量,其餘的協程都能看到,都會收到影響。而使用了closure factory後,每一個協程操做的是獨立的函數,並且會爲函數設置單獨的環境,這樣協程之間不會由任何的相互影響。
關於環境的介紹,能夠參考另外一篇文章:Lua的全局變量與環境
lua-nginx-module提供了lua_code_cache指令能夠開啓/關閉代碼的緩存,若是設置爲OFF,每次執行都會從新加載一遍,比較方便開發時調試。
實際上這個指令爲OFF是不僅是從新加載響應的代碼,而是每一個請求會建立一個全新的Lua運行環境。
正常的流程中,Nginx啓動時會建立一個Lua運行環境,以後對於每一個的請求,會基於這個Lua運行環境建立新的協程去處理,若是設置了lua_code_cache off;
,會爲每一個請求建立徹底獨立的Lua運行環境。執行Lua代碼前都會經過ngx_http_lua_create_ctx建立lua-nginx-module模塊的上下文結構體,建立過程以下所示
static ngx_inline ngx_http_lua_ctx_t * ngx_http_lua_create_ctx(ngx_http_request_t *r) { lua_State *L; ngx_http_lua_ctx_t *ctx; ngx_pool_cleanup_t *cln; ngx_http_lua_loc_conf_t *llcf; ngx_http_lua_main_conf_t *lmcf; ctx = ngx_palloc(r->pool, sizeof(ngx_http_lua_ctx_t)); if (ctx == NULL) { return NULL; } ngx_http_lua_init_ctx(r, ctx); ngx_http_set_ctx(r, ctx, ngx_http_lua_module); llcf = ngx_http_get_module_loc_conf(r, ngx_http_lua_module); if (!llcf->enable_code_cache && r->connection->fd != (ngx_socket_t) -1) { lmcf = ngx_http_get_module_main_conf(r, ngx_http_lua_module); dd("lmcf: %p", lmcf); L = ngx_http_lua_init_vm(lmcf->lua, lmcf->cycle, r->pool, lmcf, r->connection->log, &cln); if (L == NULL) { ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "failed to initialize Lua VM"); return NULL; } if (lmcf->init_handler) { if (lmcf->init_handler(r->connection->log, lmcf, L) != NGX_OK) { /* an error happened */ return NULL; } } ctx->vm_state = cln->data; } else { ctx->vm_state = NULL; } return ctx; }
能夠看到若是llcf->enable_code_cache
爲0(即設置了lua_code_cache off
),會經過ngx_http_lua_init_vm建立新的Lua運行環境,保存在ctx->vm_state中。
因此關閉代碼緩存後,增長的性能消耗遠不止編譯一段Lua代碼那麼簡單,這個只能用於開發調試。