您的位置:首页 > 运维架构 > Nginx

nginx_lua 扩展让 nginx 拥有可编程能力

2016-01-26 08:56 561 查看
         公司使用 lighttpd 的比较多, 主要是接入层的一些工作,而且增加了一些很多自己的模块防火墙等等. 

后来nginx开始流行起来因为 lighttpd 和 nginx整体是实现方式比较类似(个人感觉nginx 借鉴了 lighttpd的实

现方式),都使用了多进程异步非阻塞处理请求I/O和timer,对于静态文件服务使用sendfile系统调用.  作为

静态文件server 和接入层来说 lighttpd 已经足够的快,所以用lighttpd 和 nginx没太大区别.  简单来说nginx

 相对于lighttpd 没有 质的提高, 所以公司推广nginx 的动力不是太大.

         nginx_lua 模块的推出使得 nginx 和 lighttpd 不在一个水平线上了. nginx_lua大大降低了nginx moudle

开发的门槛,使用lua 语言可以替代以前使用c开发nginx moudle的很多场景. 可以很方便的给nginx 增加功能

这点lighttpd 很难做到.

        关于nginx_lua的介绍可以看看作者的一个演讲记录: 由Lua 粘合的Nginx生态环境 ,本文主要介绍lua

 socket I/O特点。 根据nginx 工作方式的特点每Nginx工作进程使用一个Lua VM,工作进程内所有协程共

享VM. 每一个外部请求都由一个新的Lua协程处理, 协程之间数据隔离. 当Lua代码调用I/O操作接口时,若

该操作无法立刻完成(例如 recv 会引起阻塞)协程会保存当前状态, 由Nginx 继续处理其他请求,  相关数据I/O

操作完成时resume相关协程并继续运行。

location = /tcptest {
content_by_lua '
local sock = ngx.socket.tcp()
sock:settimeout(1000)
local ok, err = sock:connect("127.0.0.1", 11211)
if not ok then
ngx.say("failed to connect: ", err)
return
end

local bytes, err = sock:send("flush_all\r\n")
if not bytes then
ngx.say("failed to send query: ", err)
return
end

local line, err = sock:receive()
if not line then
ngx.say("failed to receive a line: ", err)
return
end

ngx.say("result: ", line)
';
}

      上述例子中的 socket:receive()要等待对方数据返回的, nginx_lua 模块适配了 nginx.socket I/O操作,

nginx.socket的 I/O 操作都不会阻塞当前工作进程, nginx.socket 可以使用同步的方式实现异步I/O, 工作

方式上面说了一些,下面简单来分析一下:

    1.  新的http请求执行到 lua 代码时会创建一个新的lua 协程

    2.  当该lua 协程调用sock:receive()等函数时, 会有nginx.socket 来接管 I/O操作,nginx.socket 会yield当前

         协程,注册 I/O的回调到nginx事件循环中,继续Nginx的其他处理

    3.  当收到数据时,Nginx获得到该事件调用回调,唤醒之前的协程继续处理

    我们拿最简单的ngx.sleep 流程看一下nginx 的内部处理, 下面lua 代码针对该请求sleep 1秒钟, 并返回结果

location /sleep {
content_by_lua_block {
ngx.sleep(1)
ngx.say("1s later..")
}
}

Nginx lua 实现:

/*
* Copyright (C) Xiaozhe Wang (chaoslawful)
* Copyright (C) Yichun Zhang (agentzh)
*/

#ifndef DDEBUG
#define DDEBUG 0
#endif
#include "ddebug.h"

#include "ngx_http_lua_util.h"
#include "ngx_http_lua_sleep.h"
#include "ngx_http_lua_contentby.h"

static int ngx_http_lua_ngx_sleep(lua_State *L);
static void ngx_http_lua_sleep_handler(ngx_event_t *ev);
static void ngx_http_lua_sleep_cleanup(void *data);
static ngx_int_t ngx_http_lua_sleep_resume(ngx_http_request_t *r);

static int
ngx_http_lua_ngx_sleep(lua_State *L)
{
int                          n;
ngx_int_t                    delay; /* in msec */
ngx_http_request_t          *r;
ngx_http_lua_ctx_t          *ctx;
ngx_http_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);
}

lua_pushlightuserdata(L, &ngx_http_lua_request_key);
lua_rawget(L, LUA_GLOBALSINDEX);
r = lua_touserdata(L, -1);
lua_pop(L, 1);

delay = luaL_checknumber(L, 1) * 1000;

if (delay < 0) {
return luaL_error(L, "invalid sleep duration \"%d\"", delay);
}

if (delay == 0) {
ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
"lua sleep for 0ms");
return 0;
}

ctx = ngx_http_get_module_ctx(r, ngx_http_lua_module);
if (ctx == NULL) {
return luaL_error(L, "no request ctx found");
}

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

coctx->data = r;
// 指定 timer超时的回调函数
coctx->sleep.handler = ngx_http_lua_sleep_handler;
coctx->sleep.data = coctx;
coctx->sleep.log = r->connection->log;

dd("adding timer with delay %lu ms, r:%.*s", (unsigned long) delay,
(int) r->uri.len, r->uri.data);
// 将timer 回调event 加入到 nginx timer 管理中
ngx_add_timer(&coctx->sleep, (ngx_msec_t) delay);

coctx->cleanup = ngx_http_lua_sleep_cleanup;

ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
"lua ready to sleep for %d ms", delay);
// 保存当前协程上下文, 并返回到nginx主循环由nginx继续处理其他请求
return lua_yield(L, 0);
}

// timer expired 触发的回调
void
ngx_http_lua_sleep_handler(ngx_event_t *ev)
{
ngx_connection_t        *c;
ngx_http_request_t      *r;
ngx_http_lua_ctx_t      *ctx;
ngx_http_log_ctx_t      *log_ctx;
ngx_http_lua_co_ctx_t   *coctx;

coctx = ev->data;

r = coctx->data;
c = r->connection;

ctx = ngx_http_get_module_ctx(r, ngx_http_lua_module);

if (ctx == NULL) {
return;
}

log_ctx = c->log->data;
log_ctx->current_request = r;

coctx->cleanup = NULL;

ngx_log_debug2(NGX_LOG_DEBUG_HTTP, c->log, 0,
"lua sleep timer expired: \"%V?%V\"", &r->uri, &r->args);

ctx->cur_co_ctx = coctx;

if (ctx->entered_content_phase) {
//恢复之前的 lua协程,继续处理
(void) ngx_http_lua_sleep_resume(r);

} else {
ctx->resume_handler = ngx_http_lua_sleep_resume;
ngx_http_core_run_phases(r);
}

ngx_http_run_posted_requests(c);
}

void
ngx_http_lua_inject_sleep_api(lua_State *L)
{
lua_pushcfunction(L, ngx_http_lua_ngx_sleep);
lua_setfield(L, -2, "sleep");
}

static void
ngx_http_lua_sleep_cleanup(void *data)
{
ngx_http_lua_co_ctx_t          *coctx = data;

if (coctx->sleep.timer_set) {
dd("cleanup: deleting timer for ngx.sleep");

ngx_del_timer(&coctx->sleep);
}
}

static ngx_int_t
ngx_http_lua_sleep_resume(ngx_http_request_t *r)
{
ngx_connection_t            *c;
ngx_int_t                    rc;
ngx_http_lua_ctx_t          *ctx;
ngx_http_lua_main_conf_t    *lmcf;

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;

lmcf = ngx_http_get_module_main_conf(r, ngx_http_lua_module);

c = r->connection;

rc = ngx_http_lua_run_thread(lmcf->lua, 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, lmcf->lua, r, ctx);
}

if (rc == NGX_DONE) {
ngx_http_finalize_request(r, NGX_DONE);
return ngx_http_lua_run_posted_threads(c, lmcf->lua, r, ctx);
}

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

return rc;
}


   nginx_lua 还有其他很多强大的功能,像是一把瑞士军刀大大增强了nignx的扩展性和灵活性,应用场景

很多可以参照下京东的应用:http://jinnianshilongnian.iteye.com/category/333854
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: