##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相關的配置如下所示
lua_code_cache off;
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
配置項相關
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 }
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 }
處理函數
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 }
創建上下文結構
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的類型如下
typedef struct {
lua_State *vm;
ngx_int_t count;
} ngx_http_lua_vm_state_t;
回調函數
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 }
##代碼加載
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中加載代碼
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中找到了
從文件中讀取代碼
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
緩存代碼
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
