ngx_lua中的lua協程

Nginx是事件驅動的異步處理方式,Lua語言自己是同步處理,可是Lua原生支持協程,給Nginx與Lua的結合提供了機會。編程

Nginx能夠同時處理數以萬計的網絡鏈接,Lua能夠同時存在不少協程,簡單一點想,對每一個到來的網絡鏈接,建立一個新的協程去處理,處理完畢後釋放協程。和Apache爲每一個鏈接fork一個進程處理的流程十分類似,只不過多個進程換成了多個協程。網絡

協程相比較進程佔用資源很小,協程之間的切換性能消耗很是小,幾乎就至關於函數調用同樣。以同步的方式寫程序,實現了異步處理的效率。固然實際的編程實現並無多進程那麼簡單。異步

在Lua中,每一個協程對應有一個lua_State結構體, 這個結構體中保存了協程的全部信息。全部的協程共享一個global_State結構體,這個結構體保存全局相關的一些信息,主要是全部須要垃圾回收的對象。socket

一般建立Lua執行環境都是從lua_open(即luaL_newstate)開始, lua_open會建立一個global_State結構,建立一個協程做爲主協程。ngx_http_lua_module是在讀取配置後的postconfiguration階段建立Lua環境的,除此以外還作了一個額外的操做,主要是建立了名爲ngx,類型爲table的全局變量,全部Lua與Nginx的交互都是經過ngx這個全局變量來實現的,如ngx.sleep, ngx.socket等方法都在這個的table中。函數

Nginx中請求的處理是分階段的,ngx_http_lua_module在多個階段掛載了回調函數,這裏盜個圖.post

在rewrite, access 等多個階段,都有相應的*_by_lua*處理。性能

這裏以access階段爲例。先經過ngx_http_lua_get_lua_vm獲取主協程的lua_State結構體L,再經過ngx_http_lua_cache_loadbuffer獲取解析後的lua代碼,而後經過ngx_http_lua_access_by_chunk執行lua代碼。lua

ngx_int_t
ngx_http_lua_access_handler_inline(ngx_http_request_t *r)
{
    ngx_int_t                  rc;
    lua_State                 *L;
    ngx_http_lua_loc_conf_t   *llcf;

    llcf = ngx_http_get_module_loc_conf(r, ngx_http_lua_module);

    L = ngx_http_lua_get_lua_vm(r, NULL);

    /*  load Lua inline script (w/ cache) sp = 1 */
    rc = ngx_http_lua_cache_loadbuffer(r->connection->log, L,
                                       llcf->access_src.value.data,
                                       llcf->access_src.value.len,
                                       llcf->access_src_key,
                                       (const char *) llcf->access_chunkname);

    if (rc != NGX_OK) {
        return NGX_HTTP_INTERNAL_SERVER_ERROR;
    }

    return ngx_http_lua_access_by_chunk(L, r);
}

 

在balancer_by_lua*, header_filter, body_filter, log階段中,直接在主協程中執行代碼,而在access,content等其餘幾個階段中,會建立一個新的協程去執行此階段的lua代碼。表如今API層面,二者的區別就是可否執行ngx.sleep, ngx.socket, ngx.thread這幾個命令。debug

Lua中的協程能夠隨時掛起,一段時間後繼續運行。在access等階段會新建協程, 新的協程只處理一個請求,能夠方便的掛起來,不會影響其餘的協程。而在log階段沒有建立新的協程,主協程是不能執行ngx.sleep等阻塞操做的。指針

 

Lua中的協程也是GC對象,會被系統進行垃圾回收時銷燬掉,爲了保證掛起的協程不會被GC掉,ngx_http_lua_module在全局的註冊表中建立了一個table,新建立的協程保存在table中,協程執行完畢後從table中註銷,GC時就會將已註銷的協程回收掉。

ngx_http_lua_module初始Lua運行環境時,執行ngx_http_lua_init_registry函數,在註冊表建立了幾個table,key爲ngx_http_lua_coroutines_key的table保存全部的協程。

static void
ngx_http_lua_init_registry(lua_State *L, ngx_log_t *log)
{
    ngx_log_debug0(NGX_LOG_DEBUG_HTTP, log, 0,
                   "lua initializing lua registry");

    /* {{{ register a table to anchor lua coroutines reliably:
     * {([int]ref) = [cort]} */
    lua_pushlightuserdata(L, &ngx_http_lua_coroutines_key);
    lua_createtable(L, 0, 32 /* nrec */);
    lua_rawset(L, LUA_REGISTRYINDEX);
    /* }}} */

    /* create the registry entry for the Lua request ctx data table */
    lua_pushliteral(L, ngx_http_lua_ctx_tables_key);
    lua_createtable(L, 0, 32 /* nrec */);
    lua_rawset(L, LUA_REGISTRYINDEX);

    /* create the registry entry for the Lua socket connection pool table */
    lua_pushlightuserdata(L, &ngx_http_lua_socket_pool_key);
    lua_createtable(L, 0, 8 /* nrec */);
    lua_rawset(L, LUA_REGISTRYINDEX);

#if (NGX_PCRE)
    /* create the registry entry for the Lua precompiled regex object cache */
    lua_pushlightuserdata(L, &ngx_http_lua_regex_cache_key);
    lua_createtable(L, 0, 16 /* nrec */);
    lua_rawset(L, LUA_REGISTRYINDEX);
#endif

    /* {{{ register table to cache user code:
     * { [(string)cache_key] = <code closure> } */
    lua_pushlightuserdata(L, &ngx_http_lua_code_cache_key);
    lua_createtable(L, 0, 8 /* nrec */);
    lua_rawset(L, LUA_REGISTRYINDEX);
    /* }}} */
}

 

Nginx中處理請求都是圍繞ngx_http_request_t結構體進行了,一個ngx_http_request_t結構體表明瞭當前正在處理的一個請求。ngx_http_lua_module處理Lua腳本時要與Nginx進行交互,也要經過這個結構體實現。爲此在建立新的協程後,將相關聯的ngx_http_request_t的指針保存在了lua_State的全局變量中。

以下所示,經過ngx_http_lua_set_req將請求與協程關聯。

static ngx_inline void
ngx_http_lua_set_req(lua_State *L, ngx_http_request_t *r)
{
    lua_pushlightuserdata(L, r);
    lua_setglobal(L, ngx_http_lua_req_key);
}

經過ngx_http_lua_get_req從lua_State中獲取協程關聯的請求。

static ngx_inline ngx_http_request_t *
ngx_http_lua_get_req(lua_State *L)
{
    ngx_http_request_t    *r;

    lua_getglobal(L, ngx_http_lua_req_key);
    r = lua_touserdata(L, -1);
    lua_pop(L, 1);

    return r;
}

 

下面這個是ngx.get_method的API的實現,很簡單的邏輯,經過ngx_http_lua_get_req獲取請求的ngx_http_request_t結構體,從結構體中把表明請求方法字符串返回。ngx_http_lua_module提供的API大都經過這種方式來實現。

static int
ngx_http_lua_ngx_req_get_method(lua_State *L)
{
    int                      n;
    ngx_http_request_t      *r;

    n = lua_gettop(L);
    if (n != 0) {
        return luaL_error(L, "only one argument expected but got %d", n);
    }

    r = ngx_http_lua_get_req(L);
    if (r == NULL) {
        return luaL_error(L, "request object not found");
    }

    ngx_http_lua_check_fake_request(L, r);

    lua_pushlstring(L, (char *) r->method_name.data, r->method_name.len);
    return 1;
}
相關文章
相關標籤/搜索