lua&ngx_lua的介绍与应用by陈于喆QQ:34174409大纲•Lua的预备知识•架构背景•nginx的预备知识•ngx_lua•nginx,lua,ngx_lua再说原理•思考lua的预备知识什么是lua噜啊Lua是一种脚本编程语言,于1994年,由巴西里约热内卢天主教大学的研究人员设计开发,“Lua”这个名字是葡萄牙语单词“月亮”。小鸟引发热潮2011年6月排名lua的特点•与一般脚本语言如PHP、Perl、JavaScript等不同,Lua被称为是一种嵌入式脚本语言,Lua最著名的应用是在暴雪公司的网络游戏魔兽世界和网易的大话西游中。•Lua最引人注目的特点:•极小的体积和简单的语法提供相对全面的功能。•简洁的API实现与宿主语言最方便的接口。•与平台无关几乎运行于所有的系统。所谓的“嵌入式”•lua可以独立进行编程,但这不是主要的使用方式。Lua虽然有动态、灵活的语法提供强大的功能,但并不像Java、Python等一样有一个完善的库,这不是缺陷,而是和其定位有关。•“嵌入式”,lua作为一个库,嵌入到其他大型语言(称之为宿主语言)的应用程序之中,为应用程序提供参数配置或逻辑描述等功能,带来前所未有的灵活性。lua的经典使用方式宿主语言lua作为配置文件,为宿主语言提供参数宿主语言为底层库,lua作为逻辑处理宿主语言lualua工作流程4执行读入的Lua程序3读入Lua源程序或预先编译后的Lua程序2将宿主语言实现的Lua扩展,如函数等,注册到Lua解释器中,供其使用1宿主语言建立Lua解释器对象。Lua与宿主语言的交互方式•宿主语言通过虚拟机对Lua脚本中的变量实现增、删、读、写•宿主语言通过虚拟机调用Lua脚本中的函数•宿主语言定义新的数据类型供Lua脚本使用•Lua调用宿主语言编写的函数Lua与C宿主的交互~1进行编译gcc-ohellohello.c-llua–dl运行./hello(有备注)12更深入的交互•上例只实现了对Lua脚本的解析,并没有实现Lua与宿主语言的数据交换和互操作。•和典型的脚本语言引擎相同,Lua虚拟机是一个堆栈机,其一切运算基本都在堆栈上完成,这个堆栈也是LuaAPI的关键部分,是Lua与宿主语言交换数据的手段。13堆栈机的原理实现计算f(a,b,c)先将函数压栈再将参数依次压栈函数执行后将参数弹出并将结果压栈通过堆栈的交互•用宿主语言可以编写供Lua调用的函数,宿主语言需要遵守调用约定,从栈中取得参数,最后也将结果入栈。将宿主函数通过lua_register注册入Lua虚拟机(这一过程实质为向Lua语言添加全局变量),就可以被Lua语言所调用。Lua虚拟机的堆栈•Lua虚拟机内部有一个堆栈,LuaAPI提供了对其的操作,不仅有出入栈操作,还可以以数组的形式,通过索引值随机读写栈元素,这是双方交换数据的主要方式。Lua与C宿主的交互~2Hello2.cHello2.lua进行编译gcc-ohello2hello2.c-llua–dl运行./hello2(有备注)17结果分析(lua_State*s):•数据传递不通过其参数,而是通过堆栈;整型返回值指明了该函数真正向Lua返回的值的个数,即压栈的结果个数。函数返回后,Lua虚拟机会自动进行清栈工作,不需在函数内部来做。注意:•在Lua中函数可以有不止一个返回值基本类型•赋值:a=3x,y,z=12,'Hello',true•基本类型:–空类型nilnil–数值number1233.141591.6e-9–运算:+-*/%^(乘幂)-(负)–布尔booleantruefalse•运算:orandnot•字符串string‘’“你好•运算:..(连接)#(长度)•其他通用运算符:==~===table•Lua使用table类型作为一切数据结构的基础:–t={1234,nil,'hello',true,{'nested',1.414}}•table本质为哈希表,保存键-值对的集合,若不指定键,则默认为从1开始的整数。也可显式指定键:–rec={[‘name’]=‘111',favorite=‘222',[10]=true}•引用表的元素:–rec.namerec['favorite']rec[10]•活用表类型,可以构成结构体、链表、数组、对象等各种复杂数据结构。(有备注)20关于闭包functionnewCounter()locali=0returnfunction()i=i+1returniendendc1=newCounter()print(c1())print(c1())(有备注)21架构背景08年我们的框架BrowserApacheAPPDBLVSsquidBusiness目前我们的架构BrowserNginxAPPDBLVSBusinessContentCDNhttpsqsajax404proxy得到的收益•业务更加稳定–Nginx大连接数目支持非常好–Nginx本身的内存占用很少,不会吃swap•业务性能更高–QPS比Apache要好–节省机器数目–基于Nginx的模块性能往往是之前业务的数倍Nginx的知识预备Nginx进程模式•nginx采用多进程,单Master多Worker•Master处理外部信号,配置文件以及worker的初始化•worker进程采用单线程,非阻塞(Eventloop)来处理客户端请求和响应Nginx处理Http请求的过程clientserverlocationIphostporturlphaseNginx处理Http请求的过程•NGX_HTTP_POST_READ_PHASE读取请求phase•NGX_HTTP_SERVER_REWRITE_PHASE这个阶段主要是处理全局的(serverblock)的rewrite•NGX_HTTP_FIND_CONFIG_PHASE这个阶段主要是通过uri来查找对应的location,然后根据loc_conf设置r的相应变量•NGX_HTTP_REWRITE_PHASE这个主要处理location的rewrite•NGX_HTTP_POST_REWRITE_PHASEpostrewrite,这个主要是进行一些校验以及收尾工作,以便于交给后面的模块。•NGX_HTTP_PREACCESS_PHASE比如流控这种类型的access就放在这个phase,也就是说它主要是进行一些比较粗粒度的access。Nginx处理Http请求的过程•NGX_HTTP_ACCESS_PHASE这个比如存取控制,权限验证就放在这个phase,一般来说处理动作是交给下面的模块做的.这个主要是做一些细粒度的access•NGX_HTTP_POST_ACCESS_PHASE一般来说当上面的access模块得到access_code之后就会由这个模块根据access_code来进行操作•NGX_HTTP_TRY_FILES_PHASEtry_file模块,就是对应配置文件中的try_files指令,可接收多个路径作为参数,当前一个路径的资源无法找到,则自动查找下一个路径•NGX_HTTP_CONTENT_PHASE内容处理模块•NGX_HTTP_LOG_PHASElog模块子请求(subrequest)location/main{echo_location/foo;}location/foo{echofoo;}“子请求”方式的通信是在同一个虚拟主机内部进行的,所以Nginx核心在实现“子请求”的时候,就只调用了若干个C函数,完全不涉及任何网络或者UNIX套接字(socket)通信。我们由此可以看出“子请求”的执行效率是极高的。(有备注)31协程•协程类似一种多线程,与多线程的区别有:•协程并非os线程,所以创建、切换开销比线程相对要小。•协程与线程一样有自己的栈、局部变量等,但是协程的栈是在用户进程空间模拟的,所以创建、切换开销很小。•多线程程序是多个线程并发执行,也就是说在一瞬间有多个控制流在执行。而协程强调的是一种多个协程间协作的关系,只有当一个协程主动放弃执行权,另一个协程才能获得执行权,所以在某一瞬间,多个协程间只有一个在运行。•由于多个协程时只有一个在运行,所以对于临界区的访问不需要加锁,而多线程的情况则必须加锁。•多线程程序由于有多个控制流,所以程序的行为不可控,而多个协程的执行是由开发者定义的所以是可控的。32协程(简单的说)•协程(coroutine)和线程的区别在于调度方式的差异,即让出CPU给别的执行绪(切换)的时机不同:线程:主动让出(yield)、I/O阻塞、时间片到协程:主动让出(yield)、I/O(协程间通信)阻塞33Ngx_luaNgx_lua安装•下载http_lua_module,加载编译•或直接使用openresty•./configure--with-luajit&&make&&makeinstall•的用法•ngx_lua模块提供了配置指令和NginxAPI。•配置指令:在Nginx中使用,和set指令和pass_proxy指令使用方法一样,每个指令都有使用的上下文(context)•NginxAPI:用于在Lua脚本中访问Nginx变量,调用Nginx提供的函数。36配置指令•set_by_lua/set_by_lua_file•access_by_lua/access_by_lua_file•rewrite_by_lua/rewrite_by_lua_file•content_by_lua/content_by_lua_fileset_by_lua=100&b=100和set指令一样用于设置Nginx变量并且在rewrite阶段执行,只不过这个变量是由lua脚本计算并返回的access_by_lua运行在access阶段,用于访问控制。Nginx原生的allow和deny是基于ip的,通过access_by_lua能完成复杂的访问控制,比如,访问数据库进行用户名、密码验证等39rewrite_by_lua实现url重写,在rewrite阶段执行content_by_lua在content阶段执行,生成http响应例子:抵御hash攻击curl--dataa=1&a=11&b=d例子:配合memcachedrequire('Memcached')Module&requireMemcached模块引用了socket动态编译库(有备注)43例子:ip控制例子:与php简单的io比对写入5MB约1s-2s写入5MB约10s-12s://ip:8083/io_test45io:nginx&luacontent_by_lua'res=ngx.location.capture(/subreq)echores.body';localf=assert(io.open(html/index.html,r))在Lua中进行各种IO时,都要通过ngx.location.capture发送子请求委托给Nginx事件模型,这样可以保证IO是非阻塞的location/subreq{internal;roothtml;}(有备注)46再说nginx,lua,ngx_luawhynginxp22whylua•内存开销小•运行速度快•VM可中断/重入原理•ngx_lua实现Proactor模型–业务逻辑以自然逻辑书写–自动获得高并发能力–不会因I/O阻塞等待而浪费CPU资源50原理•每个worker进程使用一个luavm,工作进程内所有协程共享vm•将nginxi/o原句封装后注入lu