##Openresty是什么
OpenResty是一个基于 Nginx 与 Lua 的高性能 Web 平台,通过把lua嵌入到Nginx中,使得我们可以用轻巧的lua语言进行nginx的相关开发,处理高并发,扩展性极高的动态 Web 应用。
大家知道lua_code_cache 开关用于控制是否缓存*_by_lua_file对应的文件里的lua代码
lua_code_cache off的情况下,跟请求有关的阶段,在每次有请求来的时候,都会重新加载最新的lua文件,这样我们修改完代码之后就不用通过reload来更新代码了
而*_by_lua_block、*_by_lua和init_by_lua_file里的代码(init_by_lua阶段和具体请求无关),如果修改的内容涉及这几个,仍需要通过reload来更新代码
那openresty是如何实现这些,如何完成加载代码,和代码缓存的呢?
##Nginx配置
假设Nginx相关的配置如下所示
1
|
lua_code_cache
off; |
1
2
3
|
location
~ ^/api/([-_a-zA-Z0-9/]+) { content_by_lua_file
lua/$1.lua; } |
当来到的请求符合 ^/api/([-_a-zA-Z0-9/] 时,会在NGX_HTTP_CONTENT_PHASE HTTP请求内容阶段交给 lua/$1.lua来处理
比如
/api/addition 交给 lua/addition.lua 处理
/api/substraction 交给 lua/substraction .lua 处理
##请求的处理
content_by_lua_file对应的请求来临时,执行流程为 ngx_http_lua_content_handler -> ngx_http_lua_content_handler_file-> ngx_http_lua_content_by_chunk
配置项相关
1
2
3
4
5
6
|
324
{ ngx_string( "content_by_lua_file" ), 325
NGX_HTTP_LOC_CONF|NGX_HTTP_LIF_CONF|NGX_CONF_TAKE1, 326
ngx_http_lua_content_by_lua, 327
NGX_HTTP_LOC_CONF_OFFSET, 328
0, 329
( void *)
ngx_http_lua_content_handler_file } |
1
2
3
4
5
6
7
8
9
10
|
670 char * 671
ngx_http_lua_content_by_lua(ngx_conf_t *cf, ngx_command_t *cmd, void *conf) 672
{ 673
... 756
llcf->content_handler = (ngx_http_handler_pt) cmd->post; //设置回调函数为ngx_http_lua_content_handler_file 757
... 768
clcf->handler = ngx_http_lua_content_handler; //使用按需挂载处理函数的方式挂载处理函数,处理函数为ngx_http_lua_content_handler 769 770 return NGX_CONF_OK; 771
} |
处理函数
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
|
135
ngx_int_t 136
ngx_http_lua_content_handler(ngx_http_request_t *r)137 { 138
... 152 153
ctx = ngx_http_get_module_ctx(r, ngx_http_lua_module); //获取请求在ngx_http_module模块对应的上下文结构 154 155
dd( "ctx
= %p" ,
ctx); 156 157 if (ctx
== NULL) { 158
ctx = ngx_http_lua_create_ctx(r); //如果之前没有设置过上下文,调用ngx_http_lua_create_ctx创建上下文结构 159 if (ctx
== NULL) { 160 return NGX_HTTP_INTERNAL_SERVER_ERROR; 161
} 162
} 163 164
dd( "entered?
%d" ,
( int )
ctx->entered_content_phase); 165 166
... 205 return llcf->content_handler(r); //调用ngx_http_content_handler_file函数 206
} |
创建上下文结构
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
259
ngx_http_lua_create_ctx(ngx_http_request_t *r) 260
{ 261
... 276 if (!llcf->enable_code_cache
&& r->connection->fd != (ngx_socket_t) -1) { //如果lua_code_cache
off 277
... 281
L = ngx_http_lua_init_vm(lmcf->lua, lmcf->cycle, r->pool, lmcf, 282
r->connection-> log ,
&cln); //为请求初始化一个新的lua_state 296
ctx->vm_state = cln->data; 297 298
} else { 299
ctx->vm_state = NULL; //不分配新的lua_state,这样所有请求都会使用ngx_http_lua_module模块的lua_state 300
} 301 302 |
276行 如果关闭了lua代码缓存,那么openresty就会为每一个请求创建一个新的lua_state 并设置相关的字段,最后赋值给ctx->vm_state,
ctx->vm_state的类型如下
1
2
3
4
|
typedef struct { lua_State
*vm; ngx_int_t
count; }
ngx_http_lua_vm_state_t; |
回调函数
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
229
ngx_int_t 230
ngx_http_lua_content_handler_file(ngx_http_request_t *r) 231
{ 232
... 244
script_path = ngx_http_lua_rebase_path(r->pool, eval_src.data, 245
eval_src.len); //获取lua文件的路径 251
L = ngx_http_lua_get_lua_vm(r, NULL); //获得lua_state,如果请求有自己的lua_state则使用请求自己的lua_state,否则使用ngx_http_lua_module模块的lua_state 252 253 /*
load Lua script file (w/ cache) sp = 1 */ 254
rc = ngx_http_lua_cache_loadfile(r->connection-> log ,
L, script_path, 255
llcf->content_src_key); //加载代码 256
... 267 return ngx_http_lua_content_by_chunk(L,
r); //创建协程执行代码的函数 268
} |
##代码加载
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
|
204
ngx_int_t 205
ngx_http_lua_cache_loadfile(ngx_log_t * log ,
lua_State *L, 206 const u_char
*script, const u_char
*cache_key) 207
{ 208
... 232
rc = ngx_http_lua_cache_load_code( log ,
L, ( char *)
cache_key); 233 if (rc
== NGX_OK) { //代码在全局变量table中存在,则返回 234
... 237 return NGX_OK; 238
} 239
... 250
rc = ngx_http_lua_clfactory_loadfile(L, ( char *)
script); // 251
... 279
rc = ngx_http_lua_cache_store_code(L, ( char *)
cache_key); 280
... 285 return NGX_OK; 286 287
error: 288
... 294
} |
代码加载分成3步完成
ngx_http_lua_cache_load_code 从lua_state的全局变量table中加载代码,如果全局缓存中有就返回
ngx_http_lua_clfactory_loadfile 用自定义的函数从文件中加载代码
ngx_http_lua_cache_store_code 把代码存放到lua_state的全局变量table中
尝试从全局变量table中加载代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
|
34 static ngx_int_t 35
ngx_http_lua_cache_load_code(ngx_log_t * log ,
lua_State *L, 36 const char *key) 37
{ 38
... 41 /*
get code cache table */ 42
lua_pushlightuserdata(L, &ngx_http_lua_code_cache_key); 43
lua_rawget(L, LUA_REGISTRYINDEX); /*
sp++ */ 44
... 52
lua_getfield(L, -1, key); /*
sp++ */ 53 54 if (lua_isfunction(L,
-1)) { 55 /*
call closure factory to gen new closure */ 56
rc = lua_pcall(L, 0, 1, 0); 57 if (rc
== 0) { 58
... 61 return NGX_OK; 62
} 63
... 75 return NGX_ERROR; 76
} 77
... 85 return NGX_DECLINED; 86
} |
42-52行,相当于LUA_REGISTRYINDEX[‘ngx_http_lua_code_cache_key’][‘key’]以ngx_http_lua_code_cache_key为索引从全局注册表表中查找key对于的value
54-61行,如果value存在并且为一个函数,因为这里的函数体是 return function() … end包裹的 所以在56行需要再调用lua_pcall执行下,以获得返回的函数并将返回的函数结果放到栈顶,最终将 LUA_REGISTRYINDEX从栈中移除
如果代码缓存关闭的时候,openresty会为每一个请求创建一个新的lua_state,这样请求来临的时候在全局变量table中找不到对应的代码缓存,需要到下一步ngx_http_lua_clfactory_loadfile中读取文件加载代码
如果代码缓存打开的时候,openresty会使用ngx_http_lua_module全局的lua_state,这样只有新的lua文件,在首次加载时需要到ngx_http_lua_clfactory_loadfile中读取文件加载代码,第二次来的时候便可以在lua_state对应的全局变量table中找到了
从文件中读取代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
598
ngx_int_t 599
ngx_http_lua_clfactory_loadfile(lua_State *L, const char *filename) 600
{ 601
... 615
lf.begin_code.ptr = CLFACTORY_BEGIN_CODE; 616
lf.begin_code_len = CLFACTORY_BEGIN_SIZE; 617
lf.end_code.ptr = CLFACTORY_END_CODE; 618
lf.end_code_len = CLFACTORY_END_SIZE; 619
... 622
lf.f = fopen (filename, "r" ); 623
... 700
status = lua_load(L, ngx_http_lua_clfactory_getF, &lf, 701
lua_tostring(L, -1)); 702
... 716 return status; |
#define CLFACTORY_BEGIN_CODE "return function() "
#define CLFACTORY_END_CODE "\nend"
700行用自定义的ngx_http_lua_clfactory_getF函数读取lua代码
并在原有代码的开头加上了return function() 结束处加上了\nend
缓存代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
103
ngx_http_lua_cache_store_code(lua_State *L, const char *key) 104
{ 105
... 108
lua_pushlightuserdata(L, &ngx_http_lua_code_cache_key); 109
lua_rawget(L, LUA_REGISTRYINDEX); 110
... 118
lua_pushvalue(L, -2); /*
closure cache closure */ 119
lua_setfield(L, -2, key); /*
closure cache */ 120
... 122
lua_pop(L, 1); /*
closure */ 123
... 125
rc = lua_pcall(L, 0, 1, 0); 126
... 131 return NGX_OK; 132
} |
108-119行,相当于 LUA_REGISTRYINDEX[‘ngx_http_lua_code_cache_key’][‘key’] = function xxx,将代码放入全局table中
122行,将 LUA_REGISTRYINDEX从栈中弹出
125行,因为代码块是 return function() … end包裹的,所以在56行需要再调用lua_pcall执行以获得返回的函数
##总结
1、当lua_code_cache off的情况下,openresty关闭lua代码缓存,为每一个请求都创建一个新的lua_state,这样每一个请求来临的时候在新创建的lua_state中,都在全局table的代码缓存中找不到代码,需要重新读取文件加载代码,
因此可以立即动态加载新的lua脚本,而不需要reload nginx,但因为每个请求都需要分配新的lua_state,和读取文件加载代码,所以性能较差
2、当lua_code_cache on的情况下,openresty打开lua代码缓存,每一个请求使用ngx_http_lua_module全局的lua_state,新的lua文件在首次加载的时候,会去读取文件加载代码,然后存放到lua的全局变量中,
请求再次的时候 就会在lua_state全局table缓存中找到了,不需要再读取文件加载代码,因此修改完代码之后,需要reload nginx之后才可以生效
3、通过 content_by_lua_file 中使用 Nginx 变量时,可以在实现在lua_code_cache on的情况下动态加载新的 Lua 脚本,而不需要reload nginx