ngx_lua的代碼緩存

Lua代碼的執行通常要先將代碼變成成字節碼,而後再Lua虛擬機中執行字節碼。lua-nginx-module將編譯後的結果保存了下來,這樣只須要編譯一次,以後即可以直接使用,省去了編譯的消耗。nginx

Lua代碼的加載

以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

Closure factory

在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的做用

這個closure factory存在的意思是什麼呢?從表面上看的話,這種作法除了每次使用時增長了一次lua_pcall的調用外,沒有什麼做用。確實,即便不這樣封裝也能夠照常運行,實際上最初的lua-nginx-module也沒有使用closure factory。後面改用closure factory是爲了讓處理不一樣請求的Lua協程徹底分離,互不影響。

這個涉及到Lua中的環境的概念,每一個Lua函數都關聯了一個稱爲環境的table,在Lua代碼中的全局變量實際是在關聯的環境中,若是沒有closure factory,全部的協程在Access階段執行的是同一個函數,使用的就會使同一個環境。一個協程聲明瞭或者修改全局變量,其餘的協程都能看到,都會收到影響。而使用了closure factory後,每一個協程操做的是獨立的函數,並且會爲函數設置單獨的環境,這樣協程之間不會由任何的相互影響。

關於環境的介紹,能夠參考另外一篇文章:Lua的全局變量與環境

lua_code_cache指令

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代碼那麼簡單,這個只能用於開發調試。

相關文章
相關標籤/搜索