Nginx是事件驱动的异步处理方式,Lua语言本身是同步处理,但是Lua原生支持协程,给Nginx与Lua的结合提供了机会。
Nginx可以同时处理数以万计的网络连接,Lua可以同时存在很多协程,简单一点想,对每个到来的网络连接,创建一个新的协程去处理,处理完毕后释放协程。和Apache为每个连接fork一个进程处理的流程十分相似,只不过多个进程换成了多个协程。
协程相比较进程占用资源很小,协程之间的切换性能消耗非常小,几乎就相当于函数调用一样。以同步的方式写程序,实现了异步处理的效率。当然实际的编程实现并没有多进程那么简单。
在Lua中,每个协程对应有一个lua_State
结构体, 这个结构体中保存了协程的所有信息。所有的协程共享一个global_State
结构体,这个结构体保存全局相关的一些信息,主要是所有需要垃圾回收的对象。
通常创建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
在多个阶段挂载了回调函数,这里盗个图.
在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代码。
1 |
|
在balancer_by_lua*
, header_filter_by_lua*
, body_filter_by_lua
, log_by_lua
阶段中,直接在主协程中执行代码,而在access,content等其他几个阶段中,会创建一个新的协程去执行此阶段的lua代码。表现在API层面,两者的区别就是能否执行ngx.sleep, ngx.socket, ngx.thread这几个命令。
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保存所有的协程。
1 |
|
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
将请求与协程关联。
1 |
|
通过ngx_http_lua_get_req
从lua_State中获取协程关联的请求。
1 |
|
下面这个是ngx.get_method
的API的实现,很简单的逻辑,通过ngx_http_lua_get_req
获取请求的ngx_http_request_t
结构体,从结构体中把代表请求方法字符串返回。ngx_http_lua_module
提供的API大都通过这种方式来实现。
1 |
|