openresty源码剖析——lua代码的加载
##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
openresty源码剖析——lua代码的加载的更多相关文章
- openresty源码剖析——lua代码的执行
上一篇文章中我们讨论了openresty是如何加载lua代码的 那么加载完成之后的lua代码又是如何执行的呢 ##代码的执行 在init_by_lua等阶段 openresty是在主协程中通过lu ...
- Spring源码剖析4:懒加载的单例Bean获取过程分析
本文转自五月的仓颉 https://www.cnblogs.com/xrq730 本系列文章将整理到我在GitHub上的<Java面试指南>仓库,更多精彩内容请到我的仓库里查看 https ...
- ArrayList源码剖析与代码实测
ArrayList源码剖析与代码实测(基于OpenJdk14) 目录 ArrayList源码剖析与代码实测(基于OpenJdk14) 继承关系 从构造函数开始 从add方法深入 / 数组的扩容 其他的 ...
- wemall app商城源码Android之ListView异步加载网络图片(优化缓存机制)
wemall-mobile是基于WeMall的android app商城,只需要在原商城目录下上传接口文件即可完成服务端的配置,客户端可定制修改.本文分享wemall app商城源码Android之L ...
- lua代码的加载
lua代码的加载 Openresty是什么 OpenResty是一个基于 Nginx 与 Lua 的高性能 Web 平台,通过把lua嵌入到Nginx中,使得我们可以用轻巧的lua语言进行nginx的 ...
- 【Spring源码分析】非懒加载的单例Bean初始化过程(下篇)
doCreateBean方法 上文[Spring源码分析]非懒加载的单例Bean初始化过程(上篇),分析了单例的Bean初始化流程,并跟踪代码进入了主流程,看到了Bean是如何被实例化出来的.先贴一下 ...
- 【Spring源码分析】非懒加载的单例Bean初始化前后的一些操作
前言 之前两篇文章[Spring源码分析]非懒加载的单例Bean初始化过程(上篇)和[Spring源码分析]非懒加载的单例Bean初始化过程(下篇)比较详细地分析了非懒加载的单例Bean的初始化过程, ...
- Spring源码分析:非懒加载的单例Bean初始化前后的一些操作
之前两篇文章Spring源码分析:非懒加载的单例Bean初始化过程(上)和Spring源码分析:非懒加载的单例Bean初始化过程(下)比较详细地分析了非懒加载的单例Bean的初始化过程,整个流程始于A ...
- Spring源码分析:非懒加载的单例Bean初始化过程(下)
上文Spring源码分析:非懒加载的单例Bean初始化过程(上),分析了单例的Bean初始化流程,并跟踪代码进入了主流程,看到了Bean是如何被实例化出来的.先贴一下AbstractAutowireC ...
随机推荐
- Java数据结构之Set学习总结
前言: 前面介绍了Java的数据结构List.Map,今天抽空学习总结一下另一种数据结构Set. Set介绍 Set相对于List.Map是最简单的一种集合.集合中的对象不按特定的方式排序,并且没有重 ...
- 微信公众平台跳转网页url详解
1.利用Tomcat服务器部署项目,本地访问可以获取路径资源 2.开辟外网映射,实现全网访问 利用nat123端口映射工具 全网访问 3.将所获取的域名和端口设置到微信页面跳转的url,就完成了该项功 ...
- iOS性能之HTTP2.0
在移动互联网领域蓬勃发展的今天,APP的性能也成为各大公司重点关注的方向,该系列文章主要针对iOS的性能的几个方面做一些研究. 什么是HTTP2.0? 网上很容易搜到关于HTTP2.0的概念的文章,这 ...
- python--对函数的理解
函数的定义 def functionname(arg): Suite Return [expression] 1.def 定义函数时的关键字 2.functionname 函数名 3.arg函数的参数 ...
- C++ 容器对象vector和list 的使用
在<<c++ primer>>第四版Exercise Section 9.3.4 的Exercise 9.20 是这样的一道题目:编写程序判断一个vector<int&g ...
- LODOP的一次使用后的总结
一.lodop打印预览效果图 LODOP.PRINT_SETUP();打印维护效果图 LODOP.PREVIEW();打印预览图 二.写在前面 最近项目用到了LODOP的套打,主要用到两个地方,一是物 ...
- 一段从TXT导入excel的py脚本
#创建文件file = Workbook(encoding='utf-8')#创建sleettableTotalTime = file.add_sheet('TotalTime')#每列给出名称tab ...
- El表达式的用法个人总结
EL表达式的好处: 通过EL可以简化在JSP开发中对对象的引用,从而规范页面代码,增加程序的可读性及可维护性. EL表达式的几个特点: 1:可以与jsp标签库结合使用,也可以与javascript语 ...
- Google分析language垃圾信息
最近一段时间,我在Google Analytics(以下简称GA)中查看网站数据时,发现一个非常可疑的信息: 这什么鬼? "language"这一项一般是 "zh-tw& ...
- Tcl与Design Compiler (二)——DC综合与Tcl语法结构概述
1.逻辑综合的概述 synthesis = translation + logic optimization + gate mapping . DC工作流程主要分为这三步 Translation : ...