lua代码的加载 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

670char*

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

770returnNGX_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

157if(ctx == NULL) {

158 ctx = ngx_http_lua_create_ctx(r);//如果之前没有设置过上下文,调用ngx_http_lua_create_ctx创建上下文结构

159if(ctx == NULL) {

160returnNGX_HTTP_INTERNAL_SERVER_ERROR;

161 }

162 }

163

164 dd("entered? %d", (int) ctx->entered_content_phase);

165

166 ...

205returnllcf->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 ...

276if(!llcf->enable_code_cache && r->connection->fd != (ngx_socket_t) -1) {//如果<span >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 >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

typedefstruct{

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 ...

267returnngx_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,

206constu_char *script,constu_char *cache_key)

207 {

208 ...

232 rc = ngx_http_lua_cache_load_code(log, L, (char*) cache_key);

233if(rc == NGX_OK) {//全局变量中存在,则返回

234 ...

237returnNGX_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 ...

285returnNGX_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

34staticngx_int_t

35 ngx_http_lua_cache_load_code(ngx_log_t *log, lua_State *L,

36constchar*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

54if(lua_isfunction(L, -1)) {

55/* call closure factory to gen new closure */

56 rc = lua_pcall(L, 0, 1, 0);

57if(rc == 0) {

58 ...

61returnNGX_OK;

62 }

63 ...

75returnNGX_ERROR;

76 }

77 ...

85returnNGX_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,constchar*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 ...

716returnstatus;

#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,constchar*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 ...

131returnNGX_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