使用ngx_lua构建高并发应用(1)
http://www.voidcn.com/article/p-onxowufv-dy.html
|
? | 1000 | 3000 | 5000 | 7000 | 10000 |
nginx 静态文件 | 11351 | 9653 | 8929 | 8997 | 9722 |
nodejs | 10846 | 9510 | 8898 | 8387 | 7820 |
ngx_lua | 13839 | 10174 | 9523 | 10309 | 10711 |
? ? ? ? ngx_lua的开发者也做过一个测试对比nginx+fpm+php和nodejs,他得出的结果是ngx_lua可以达到28000rps,而nodejs有10000多一点,php则最差只有6000。可能是有些配置我没有配好导致ngx_lua rps没那么高。
5. ngx_lua安装
? ? ? ? ngx_lua安装可以通过下载模块源码,编译Nginx,但是推荐采用openresty。Openresty就是一个打包程序,包含大量的第三方Nginx模块,比如HttpLuaModule,HttpRedis2Module,HttpEchoModule等。省去下载模块,并且安装非常方便。
? ? ? ? ngx_openresty bundle:?openresty
? ? ? ? ./configure --with-luajit&& make && make install
? ? ? ? 默认Openresty中ngx_lua模块采用的是标准的Lua5.1解释器,通过--with-luajit使用LuaJIT。
6. ngx_lua的用法
? ? ? ? ngx_lua模块提供了配置指令和Nginx API。
? ? ? ? 配置指令:在Nginx中使用,和set指令和pass_proxy指令使用方法一样,每个指令都有使用的context。??????
? ? ? ? Nginx API:用于在Lua脚本中访问Nginx变量,调用Nginx提供的函数。
? ? ? ? 下面举例说明常见的指令和API。
7. 配置指令
? ? ? ??a. set_by_lua和set_by_lua_file
? ? ? ? 和set指令一样用于设置Nginx变量并且在rewrite阶段执行,只不过这个变量是由lua脚本计算并返回的。
? ? ? ? 语法:set_by_lua$res <lua-script-str> [$arg1 $arg2 ...]
copy
- location?=?/adder?{??
- ????set_by_lua?$res?"??
- ????????????local?a?=?tonumber(ngx.arg[1])??
- ????????????????local?b?=?tonumber(ngx.arg[2])??
- ????????????????return?a?+?b"?$arg_a?$arg_b;??
- ???
- ????????echo?$res;??
- copy
- $?curl?'localhost/adder?a=25&b=75'??
- $?100??
? ? ? ? set_by_lua_file执行Nginx外部的lua脚本,可以避免在配置文件中使用大量的转义。
? ? ? ? 配置:- location?=?/fib?{??
- ????????set_by_lua_file?$res?"conf/adder.lua"?$arg_n;??
- ???
- ????????echo?$res;??
- }??
- local?a?=?tonumber(ngx.arg[1])??
- local?b?=?tonumber(ngx.arg[2])??
- return?a?+?b??
copy
- $?curl?'localhost/adder?a=25&b=75??
- ? ? ? ??b. access_by_lua和access_by_lua_file
? ? ? ? 运行在access阶段,用于访问控制。Nginx原生的allow和deny是基于ip的,通过access_by_lua能完成复杂的访问控制,比如,访问数据库进行用户名、密码验证等。
copy
- location?/auth?{??
- ????access_by_lua?'??
- ????????if?ngx.var.arg_user?==?"ntes"?then??
- ????????????return??
- ????????else???
- ????????????Ngx.exit(ngx.HTTP_FORBIDDEN)??
- ????????end??
- ????';??
- ????echo?'welcome?ntes';??
- copy
- $?curl?'localhost/auth?user=sohu'??
- $?Welcome?ntes??
- $?curl?'localhost/auth?user=ntes'??
- $?<html>??
- <head><title>403?Forbidden</title></heda>??
- <body?bgcolor="white">??
- <center><h1>403?Forbidden</h1></center>??
- <hr><center>ngx_openresty/1.0.10.48</center>??
- </body>??
- </html>??
? ? ? ??c. rewrite_by_lua和rewrite_by_lua_file
? ? ? ? 实现url重写,在rewrite阶段执行。
copy
- location?=?/foo?{??
- ????????rewrite_by_lua?'ngx.exec("/bar")';??
- ????echo?'in?foo';??
- location?=?/bar?{??
- ????????echo?'in?bar';??
- copy
- $?Hello,?Lua!??
? ? ? ? Contenthandler在content阶段执行,生成http响应。由于content阶段只能有一个handler,所以在与echo模块使用时,不能同时生效,我测试的结果是content_by_lua会覆盖echo。这和之前的hello world的例子是类似的。
? ? ? ? 配置(直接响应):
- location?=?/lua?{??
- ????????content_by_lua?'ngx.say("Hello,?Lua!")';??
- ? ? ? ? 输出:
copy
- ? ? ? ? 配置(在Lua中访问Nginx变量):
copy- location?=?/hello?{??
- ????????content_by_lua?'local?who?=?ngx.var.arg_who??
- ????????ngx.say("Hello,?",?who,?"!")';??
- }??
- $?curl?'localhost/hello?who=world??
- 8. Nginx API
? ? ? ? Nginx API被封装ngx和ndk两个package中。比如ngx.var.NGX_VAR_NAME可以访问Nginx变量。这里着重介绍一下ngx.location.capture和ngx.location.capture_multi。
? ? ?? ? a.?ngx.location.capture? ? ? ? 语法:res= ngx.location.capture(uri,options?)
? ? ? ? 用于发出一个同步的,非阻塞的Nginxsubrequest(子请求)。可以通过Nginx subrequest向其它location发出非阻塞的内部请求,这些location可以是配置用于读取文件夹的,也可以是其它的C模块,比如ngx_proxy,ngx_fastcgi,ngx_memc,ngx_postgres,ngx_drizzle甚至是ngx_lua自己。
? ? ? ? Subrequest只是模拟Http接口,并没有额外的Http或者Tcp传输开销,它在C层次上运行,非常高效。Subrequest不同于Http 301/302重定向,以及内部重定向(通过ngx.redirection)。
? ? ? ? 配置:
- location?=?/other?{??
- ????ehco?'Hello,?world!';??
- }??
- ??????
- #?Lua非阻塞IO??
- location?=?/lua?{??
- ????content_by_lua?'??
- ????????local?res?=?ngx.location.capture("/other")??
- ????????if?res.status?==?200?then??
- ????????????ngx.print(res.body)??
- ????????end??
- ????';??
- }??
copy- $?curl??'http://localhost/lua'??
- copy
- #?同时发送多个子请求(subrequest)??
- location?=?/moon?{??
- ????ehco?'moon';??
- }??
- location?=?/earth?{??
- ????ehco?'earth';??
- ???????
- location?=?/lua?{??
- ????content_by_lua?'??
- ????????local?res1,res2?=?ngx.location.capture_multi({?{"/moon"},?{"earth"}?})??
- ????????if?res1.status?==?200?then??
- ????????????ngx.print(res1.body)??
- ????????end??
- ????????ngx.print(",")??
- ????????if?res2.status?==?200?then??
- ????????????ngx.print(res2.body)??
- ????';??
- }??
? ? ? ? 输出:
- $?moon,earth??
? ? ? ? 在Lua代码中的网络IO操作只能通过Nginx Lua API完成,如果通过标准Lua API会导致Nginx的事件循环被阻塞,这样性能会急剧下降。
? ? ? ? 在进行数据量相当小的磁盘IO时可以采用标准Lua io库,但是当读写大文件时这样是不行的,因为会阻塞整个NginxWorker进程。为了获得更大的性能,强烈建议将所有的网络IO和磁盘IO委托给Nginx子请求完成(通过ngx.location.capture)。
? ? ? ? 下面通过访问/html/index.html这个文件,来测试将磁盘IO委托给Nginx和通过Lua io直接访问的效率。
? ? ? ? 通过ngx.location.capture委托磁盘IO:
copy
- location?/?{???
- ????internal;??
- ????root?html;??
- location?/capture?{??
- ????content_by_lua?'??
- ????????res?=?ngx.location.capture("/")??
- ????????echo?res.body??
- ? ? ? ? 通过标准lua io访问磁盘文件:
copy
- location?/luaio{???
- ????content_by_lua?'??????
- ????????local?io?=?require("io")??
- ????????local?chunk_SIZE?=?4096??
- ????????local?f?=?assert(io.open("html/index.html","r"))??
- ????????while?true?do??
- ????????????local?chunk?=?f:read(chunk)??
- ????????????if?not?chunk?then??
- ????????????????break??
- ????????????end??
- ????????????ngx.print(chunk)??
- ????????????ngx.flush(true)??
- ????????f:close()??
- ????';??
- ? ? ? ? 这里通过ab去压,在各种并发条件下,分别返回151bytes、151000bytes的数据,取10次平均,得到两种方式的rps。
? ? ? ? 静态文件:151bytes
? 1000 3000 5000 7000 10000 capture 11067 8880 8873 8952 9023 Lua io 11379 9724 8938 9705 9561
? ? ? ? 静态文件:151000bytes,在10000并发下内存占用情况太严重,测不出结果? ? ? ? 这种情况下,文件较小,通过Nginx访问静态文件需要额外的系统调用,性能略逊于ngx_lua。?
? | 1000 | 3000 | 5000 | 7000 | 10000 |
capture | 3338 | 3435 | 3178 | 3043 | ? ? ? ? / |
Lua io | 3174 | 3094 | 3081 | 2916 | ? ? ? ? / |
? ? ? ? 这里没有对Nginx读取静态文件进行优化配置,只是采用了sendfile。如果优化一下,可能nginx读取静态文件的性能会更好一些,这个目前还不熟悉。所以,在Lua中进行各种IO时,都要通过ngx.location.capture发送子请求委托给Nginx事件模型,这样可以保证IO是非阻塞的。
四. 小结
(编辑:李大同)
【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容!