lua代碼的加載
Openresty是什么
OpenResty是一個基於 Nginx 與 Lua 的高性能 Web 平台,通過把lua嵌入到Nginx中,使得我們可以用輕巧的lua語言進行nginx的相關開發,處理高並發,擴展性極高的動態 Web 應用、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/lua/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) {
//如果<span style="font-family: -webkit-standard; white-space: normal">lua_code_cache off</span>
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,這樣所有請求都會使用<span style="font-family: -webkit-standard; orphans: 2; white-space: normal; widows: 2">ngx_http_lua_module模塊的lua_state</span>
300 }
301
302
|
1
|
|
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) {
//全局變量中存在,則返回
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,這樣請求來臨的時候在全局變量中找不到對應的代碼緩存,都需要到下一步ngx_http_lua_clfactory_loadfile中讀取文件加載
如果代碼緩存打開的時候,openresty會使用ngx_http_lua_module全局的lua_state,這樣只有新的lua文件 在首次加載時需要到下一步ngx_http_lua_clfactory_loadfile中 讀取文件加載,第二次來的時候 便可以在lua_state對應的全局變量中找到了
從文件中讀取代碼
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
122行 將 LUA_REGISTRYINDEX從棧中彈出
125行因為代碼塊是 return function() … end包裹的 所以在56行需要再調用lua_pcall執行以下以獲得返回的函數
##總結
1、當lua_code_cache off的情況下 openresty關閉lua代碼緩存,為每一個請求都創建一個獨立的lua_state,這樣每一個請求來臨的時候 在新創建的lua_state中 都沒有代碼記錄 需要重新讀取文件加載代碼,
因此可以立即動態加載新的lua腳本,而不需要reload nginx,但因為每個請求都需要分配新的lua_state,和讀取文件加載代碼,所以性能較差
2、當lua_code_cache on的情況下 openresty打開lua代碼緩存,每一個請求使用ngx_http_lua_module全局的lua_state,新的lua文件在首次加載的時候,會去讀取文件,然后存放到lua的全局變量中,請求再次的時候 就會在lua_state全局變量中找到了,
因此修改完代碼之后,需要reload nginx之后 才可以生效
3、通過 content_by_lua_file 中使用 Nginx 變量時,可以在實現在lua_code_cache on的情況下動態加載新的 Lua 腳本的,而不需要reload nginx