前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >openresty源码剖析——lua代码的加载

openresty源码剖析——lua代码的加载

作者头像
magicsoar
发布2018-02-06 11:35:58
2.9K0
发布2018-02-06 11:35:58
举报
文章被收录于专栏:magicsoar

##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相关的配置如下所示

代码语言:js
复制
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

配置项相关

代码语言:javascript
复制
  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 } 
代码语言:javascript
复制
  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 }

处理函数

代码语言:javascript
复制
  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 }

创建上下文结构

代码语言:javascript
复制
  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的类型如下

代码语言:javascript
复制
typedef struct {
     lua_State       *vm;
     ngx_int_t        count;
 } ngx_http_lua_vm_state_t;

 回调函数

代码语言:javascript
复制
  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 }    

##代码加载

代码语言:javascript
复制
  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中加载代码

代码语言:javascript
复制
 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中找到了

从文件中读取代码

代码语言:javascript
复制
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

缓存代码

代码语言:javascript
复制
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

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档