openresty 总结

     本文不是要介绍如何使用openresty,这类文章太多,大家可以随手找到,今天我想一步一步引入大家去深究openresty的实现过程。这样对大家的使用会更加深刻。

     首先openresty是基于nginx+lua的,大家有没有去想过nginx和lua是如何结合的?也就是nginx为什么会执行lua代码?这也是我想第一给大家介绍的内容,nginx是c写的web服务,所以nginx+lua其实就是c语言调用lua的过程。这个过程不是春哥发明的,这是lua本身具有c的lib包,能够让c去解析lua的代码并执行。点击查看完整代码

    大家执行完这段代码恍然大悟,哦,原来c调用lua如此简单,利用lualib等lib库即可成功利用c语言执行一段lua代码。这里给大家做个介绍,c执行lua代码主要是利用堆栈L的实现机制,lua_settable相当于lua的一个struct数据结构,然后利用堆栈方式压入响应key与value,luaL_loadbuffer读取响应lua文件的时候,就会从刚才的堆栈L中把元素弹出,这里第二个比较重要的点就是,当lua从堆栈把相应table获取后会放入自己的一个全局变量表中做对应,当我们c代码需要获取lua执行结果的时候,就会从全局变量表查找数据再放入堆栈中,然后再次利用堆栈原理获取相应的返回值。点击详细查看

   

    好,在我们了解了nginx和lua的相互调用过程后,我们来看openresty是如何解析的。

   举一个例子:

       location /lua {
                set $test "hello, world.";
                content_by_lua '
                        ngx.header.content_type = "text/plain";
                        ngx.say(ngx.var.test);
                ';
        }

这是我们经常在openresty的使用的一种case,lua脚本利用ngx.say输出ngx.var定义的test变量。

lua-nginx-module这个模块在openresty下,他是专门用来进行c与lua相互解析的一个模块化,ngx.say就是在这里面进行定义。查看详细内容

void
ngx_http_lua_inject_output_api(lua_State *L)
{
    lua_pushcfunction(L, ngx_http_lua_ngx_send_headers);
    lua_setfield(L, -2, "send_headers");

    lua_pushcfunction(L, ngx_http_lua_ngx_print);
    lua_setfield(L, -2, "print");

    lua_pushcfunction(L, ngx_http_lua_ngx_say);
    lua_setfield(L, -2, "say");

    lua_pushcfunction(L, ngx_http_lua_ngx_flush);
    lua_setfield(L, -2, "flush");

    lua_pushcfunction(L, ngx_http_lua_ngx_eof);
    lua_setfield(L, -2, "eof");
}

看了我第一段的介绍,上面的代码是不是很熟悉,没错,春哥就是利用lua的lib库,进行了上面一套函数的编写。ngx_http_lua_ngx_say就是解析nginx.say的时候一个函数调用过程。

    通过上面介绍大家对nginx+lua的方式理解有没有深刻一些。

   那我们再来看一个函数,ngx.sleep,大家可以通过我刚才介绍的方式轻松找到他的函数调用

static int
ngx_stream_lua_ngx_sleep(lua_State *L)
{
    int                          n;
    ngx_int_t                    delay; /* in msec */
    ngx_stream_lua_request_t    *r;

    ngx_stream_lua_ctx_t                *ctx;
    ngx_stream_lua_co_ctx_t             *coctx;
    n = lua_gettop(L);
    if (n != 1) {
        return luaL_error(L, "attempt to pass %d arguments, but accepted 1", n);
    }
    r = ngx_stream_lua_get_req(L);
    if (r == NULL) {
        return luaL_error(L, "no request found");
    }
    delay = (ngx_int_t) (luaL_checknumber(L, 1) * 1000);
    if (delay < 0) {
        return luaL_error(L, "invalid sleep duration \"%d\"", delay);
    }
    ctx = ngx_stream_lua_get_module_ctx(r, ngx_stream_lua_module);
    if (ctx == NULL) {
        return luaL_error(L, "no request ctx found");
    }
    ngx_stream_lua_check_context(L, ctx, NGX_STREAM_LUA_CONTEXT_CONTENT

                               | NGX_STREAM_LUA_CONTEXT_PREREAD

                               | NGX_STREAM_LUA_CONTEXT_TIMER);
    coctx = ctx->cur_co_ctx;
    if (coctx == NULL) {
        return luaL_error(L, "no co ctx found");
    }

    ngx_stream_lua_cleanup_pending_operation(coctx);
    coctx->cleanup = ngx_stream_lua_sleep_cleanup;
    coctx->data = r;

    coctx->sleep.handler = ngx_stream_lua_sleep_handler;
    coctx->sleep.data = coctx;
    coctx->sleep.log = r->connection->log;

    if (delay == 0) {
#ifdef HAVE_POSTED_DELAYED_EVENTS_PATCH
        dd("posting 0 sec sleep event to head of delayed queue");

        coctx->sleep.delayed = 1;
        ngx_post_event(&coctx->sleep, &ngx_posted_delayed_events);
#else
        ngx_log_error(NGX_LOG_WARN, r->connection->log, 0, "ngx.sleep(0)"
                      " called without delayed events patch, this will"
                      " hurt performance");
        ngx_add_timer(&coctx->sleep, (ngx_msec_t) delay);
#endif

    } else {
        dd("adding timer with delay %lu ms, r:%p", (unsigned long) delay, r);

        ngx_add_timer(&coctx->sleep, (ngx_msec_t) delay);
    }

    ngx_log_debug1(NGX_LOG_DEBUG_STREAM, r->connection->log, 0,
                   "lua ready to sleep for %d ms", delay);

    return lua_yield(L, 0);
}

大家会想,不用看代码都知道sleep是干嘛的,那好,那你有没有想过nginx是一堆worker的子进程,如果有个子进程执行了ngx.sleep(100000),那这个worker会怎样?阻塞其他的请求?你觉得春哥会设计一个这么弱智的api吗?那不阻塞,又是怎样实现的呢?这个问题是不是很值得琢磨呢?

那好我揭晓答案,这个问题点又涉及到了一个lua语言的特性,就是协程,不是go又协程的哦。lua可算是协程的鼻祖,他是最早提出协程概念的语言。

这时候大家就恍然大悟,原来是协程,ngx.sleep只是在协程中执行,如果一个请求是一个协程的话,那它也只是阻塞我这个请求,不影响其他,没错,是这么个意思。

  我们详细解释下,首先当执行ngx.sleep的时候最后返回值lua_yield(L, 0),通知主协程该协程挂起,这个时候主协程就去读取链表下其他的子协程,当时间到了后触发ngx_http_lua_sleep_resume,

static ngx_int_t
ngx_http_lua_sleep_resume(ngx_http_request_t *r)
{
    lua_State                   *vm;
    ngx_connection_t            *c;
    ngx_int_t                    rc;
    ngx_uint_t                   nreqs;
    ngx_http_lua_ctx_t          *ctx;

    ctx = ngx_http_get_module_ctx(r, ngx_http_lua_module);
    if (ctx == NULL) {
        return NGX_ERROR;
    }

    ctx->resume_handler = ngx_http_lua_wev_handler;

    c = r->connection;
    vm = ngx_http_lua_get_lua_vm(r, ctx);
    nreqs = c->requests;

    rc = ngx_http_lua_run_thread(vm, r, ctx, 0);

    ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
                   "lua run thread returned %d", rc);

    if (rc == NGX_AGAIN) {
        return ngx_http_lua_run_posted_threads(c, vm, r, ctx, nreqs);
    }

    if (rc == NGX_DONE) {
        ngx_http_lua_finalize_request(r, NGX_DONE);
        return ngx_http_lua_run_posted_threads(c, vm, r, ctx, nreqs);
    }

    if (ctx->entered_content_phase) {
        ngx_http_lua_finalize_request(r, rc);
        return NGX_DONE;
    }

    return rc;
}

我们看到了这个过程,ngx_stream_lua_sleep_handler是触发器回调执行,然后会调用ngx_http_lua_sleep_resume,这其中会执行ngx_http_lua_run_thread,没错,这里会重新启动一个子协程,进行相应的恢复工作。点击详细查看openresty协程调度

这边文章算是抛砖引玉,如果有同学对nginx+lua感兴趣,顺着我的这条线钻研下去,让你少走弯路。

 

 

 

 

你可能感兴趣的:(openresty 总结)