- 浏览: 32057 次
- 性别:
- 来自: 成都
文章分类
最新评论
一. 概述
Nginx是一个高性能,支持高并发的,轻量级的web服务器。目前,Apache依然web服务器中的老大,但是在全球前1000大的web服务器 中,Nginx的份额为22.4%。Nginx采用模块化的架构,官方版本的Nginx中大部分功能都是通过模块方式提供的,比如Http模块、Mail 模块等。通过开发模块扩展Nginx,可以将Nginx打造成一个全能的应用服务器,这样可以将一些功能在前端Nginx反向代理层解决,比如登录校验、 js合并、甚至数据库访问等等。
但是,Nginx模块需要用C开发,而且必须符合一系列复杂的规则,最重要的用C开发模块必须要熟悉Nginx的源代码,使得开发者对其望而生畏。淘宝的 agentzh和chaoslawful开发的ngx_lua模块通过将lua解释器集成进Nginx,可以采用lua脚本实现业务逻辑,由于lua的紧 凑、快速以及内建协程,所以在保证高并发服务能力的同时极大地降低了业务逻辑实现成本。
本文向大家介绍ngx_lua,以及我在使用它开发项目的过程中遇到的一些问题。
二. 准备
首先,介绍一下Nginx的一些特性,便于后文介绍ngx_lua的相关特性。
1. Nginx进程模型
Nginx采用多进程模型,单Master—多Worker,由Master处理外部信号、配置文件的读取及Worker的初始化,Worker进程采用 单线程、非阻塞的事件模型(Event Loop,事件循环)来实现端口的监听及客户端请求的处理和响应,同时Worker还要处理来自Master的信号。由于Worker使用单线程处理各种 事件,所以一定要保证主循环是非阻塞的,否则会大大降低Worker的响应能力。
图1
2. Nginx处理Http请求的过程
表面上看,当Nginx处理一个来自客户端的请求时,先根据请求头的host、ip和port来确定由哪个server处理,确定了server之后,再 根据请求的uri找到对应的location,这个请求就由这个location处理。实际Nginx将一个请求的处理划分为若干个不同阶段 (phase),这些阶段按照前后顺序依次执行,也就是说NGX_HTTP_POST_READ_PHASE在第一 个,NGX_HTTP_LOG_PHASE在最后一个。
- NGX_HTTP_POST_READ_PHASE, //0读取请求phase
- NGX_HTTP_SERVER_REWRITE_PHASE,//1这个阶段主要是处理全局的(server block)的rewrite
- NGX_HTTP_FIND_CONFIG_PHASE, //2这个阶段主要是通过uri来查找对应的location,然后根据loc_conf设置r的相应变量
- NGX_HTTP_REWRITE_PHASE, //3这个主要处理location的rewrite
- NGX_HTTP_POST_REWRITE_PHASE, //4postrewrite,这个主要是进行一些校验以及收尾工作,以便于交给后面的模块。
- NGX_HTTP_PREACCESS_PHASE, //5比如流控这种类型的access就放在这个phase,也就是说它主要是进行一些比较粗粒度的access。
- NGX_HTTP_ACCESS_PHASE, //6这个比如存取控制,权限验证就放在这个phase,一般来说处理动作是交给下面的模块做的.这个主要是做一些细粒度的access
- NGX_HTTP_POST_ACCESS_PHASE, //7一般来说当上面的access模块得到access_code之后就会由这个模块根据access_code来进行操作
- NGX_HTTP_TRY_FILES_PHASE, //8try_file模块,就是对应配置文件中的try_files指令,可接收多个路径作为参数,当前一个路径的资源无法找到,则自动查找下一个路径
- NGX_HTTP_CONTENT_PHASE, //9内容处理模块
- NGX_HTTP_LOG_PHASE //10log模块
每个阶段上可以注册handler,处理请求就是运行每个阶段上注册的handler。Nginx模块提供的配置指令只会一般只会注册并运行在其中的某一 个处理阶段。比如,set指令属于rewrite模块的,运行在rewrite阶段,deny和allow运行在access阶段。
3. 子请求(subrequest)
其实在Nginx 世界里有两种类型的“请求”,一种叫做“主请求”(main request),而另一种则叫做“子请求”(subrequest)。
所谓“主请求”,就是由 HTTP 客户端从 Nginx 外部发起的请求。比如,从浏览器访问Nginx就是一个“主请求”。
而“子请求”则是由 Nginx 正在处理的请求在 Nginx 内部发起的一种级联请求。“子请求”在外观上很像 HTTP 请求,但实现上却和 HTTP 协议乃至网络通信一点儿关系都没有。它是 Nginx 内部的一种抽象调用,目的是为了方便用户把“主请求”的任务分解为多个较小粒度的“内部请求”,并发或串行地访问多个 location 接口,然后由这些 location 接口通力协作,共同完成整个“主请求”。当然,“子请求”的概念是相对的,任何一个“子请求”也可以再发起更多的“子子请求”,甚至可以玩递归调用(即自 己调用自己)。当一个请求发起一个“子请求”的时候,按照 Nginx 的术语,习惯把前者称为后者的“父请求”(parent request)。
- location /main {
- echo_location /foo; # echo_location发送子请求到指定的location
- echo_location /bar;
- }
- location /foo {
- echo foo;
- }
- location /bar {
- echo bar;
- }
输出:
- $ curl location/main
- $ foo
- bar
这里,main location就是发送2个子请求,分别到foo和bar,这就类似一种函数调用。
“子请求”方式的通信是在同一个虚拟主机内部进行的,所以 Nginx 核心在实现“子请求”的时候,就只调用了若干个 C 函数,完全不涉及任何网络或者 UNIX 套接字(socket)通信。我们由此可以看出“子请求”的执行效率是极高的。
4. 协程(Coroutine)
三. ngx_lua
1. 原理
ngx_lua将Lua嵌入Nginx,可以让Nginx执行Lua脚本,并且高并发、非阻塞的处理各种请求。Lua内建协程,这样就可以很好的将异步回 调转换成顺序调用的形式。ngx_lua在Lua中进行的IO操作都会委托给Nginx的事件模型,从而实现非阻塞调用。开发者可以采用串行的方式编写程 序,ngx_lua会自动的在进行阻塞的IO操作时中断,保存上下文;然后将IO操作委托给Nginx事件处理机制,在IO操作完成后,ngx_lua会 恢复上下文,程序继续执行,这些操作都是对用户程序透明的。
每个NginxWorker进程持有一个Lua解释器或者LuaJIT实例,被这个Worker处理的所有请求共享这个实例。每个请求的Context会被Lua轻量级的协程分割,从而保证各个请求是独立的。
ngx_lua采用“one-coroutine-per-request”的处理模型,对于每个用户请求,ngx_lua会唤醒一个协程用于执行用户代 码处理请求,当请求处理完成这个协程会被销毁。每个协程都有一个独立的全局环境(变量空间),继承于全局共享的、只读的“comman data”。所以,被用户代码注入全局空间的任何变量都不会影响其他请求的处理,并且这些变量在请求处理完成后会被释放,这样就保证所有的用户代码都运行 在一个“sandbox”(沙箱),这个沙箱与请求具有相同的生命周期。
得益于Lua协程的支持,ngx_lua在处理10000个并发请求时只需要很少的内存。根据测试,ngx_lua处理每个请求只需要2KB的内存,如果使用LuaJIT则会更少。所以ngx_lua非常适合用于实现可扩展的、高并发的服务。
2. 典型应用
官网上列出:
· Mashup'ing and processing outputs of various nginx upstream outputs(proxy, drizzle, postgres, redis, memcached, and etc) in Lua,
· doing arbitrarily complex access control and security checks in Luabefore requests actually reach the upstream backends,
· manipulating response headers in an arbitrary way (by Lua)
· fetching backend information from external storage backends (likeredis, memcached, mysql, postgresql) and use that information to choose whichupstream backend to access on-the-fly,
· coding up arbitrarily complex web applications in a content handlerusing synchronous but still non-blocking access to the database backends andother storage,
· doing very complex URL dispatch in Lua at rewrite phase,
· using Lua to implement advanced caching mechanism for nginxsubrequests and arbitrary locations.
3. Hello Lua!
配置:
- # nginx.conf
- worker_processes 4;
- events {
- worker_connections 1024;
- }
- http {
- server {
- listen 80;
- server_name localhost;
- location = /lua {
- content_by_lua ‘
- ngx.say("Hello, Lua!")
- ';
- }
- }
- }
输出:
- $ curl 'localhost/lua'
- Hello,Lua!
这样就实现了一个很简单的ngx_lua应用,如果这么简单的模块要是用C来开发的话,代码量估计得有100行左右,从这就可以看出ngx_lua的开发效率。
4. Benchmark
通过和nginx访问静态文件还有nodejs比较,来看一下ngx_lua提供的高并发能力。
返回的内容都是”Hello World!”,151bytes
通过.ab -n 60000 取10次平均
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的rps都是最高的,并且基本维持在10000rps左右,nginx读取静态文件因为会有磁盘 io所以性能略差一些,而nodejs是相对最差的。通过这个简单的测试,可以看出ngx_lua的高并发能力。
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 ...]
配置:
- 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;
- }
输出:
- $ 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;
- }
adder.lua:
- local a = tonumber(ngx.arg[1])
- local b = tonumber(ngx.arg[2])
- return a + b
输出:
- $ curl 'localhost/adder?a=25&b=75
- $ 100
b. access_by_lua和access_by_lua_file
运行在access阶段,用于访问控制。Nginx原生的allow和deny是基于ip的,通过access_by_lua能完成复杂的访问控制,比如,访问数据库进行用户名、密码验证等。
配置:
- location /auth {
- access_by_lua '
- if ngx.var.arg_user == "ntes" then
- return
- else
- Ngx.exit(ngx.HTTP_FORBIDDEN)
- end
- ';
- echo 'welcome ntes';
- }
输出:
- $ 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阶段执行。
配置:
- location = /foo {
- rewrite_by_lua 'ngx.exec("/bar")';
- echo 'in foo';
- }
- location = /bar {
- echo 'in bar';
- }
输出:
- $ curl 'localhost/lua'
- $ Hello, Lua!
d. content_by_lua和content_by_lua_file
Contenthandler在content阶段执行,生成http响应。由于content阶段只能有一个handler,所以在与echo模块使用 时,不能同时生效,我测试的结果是content_by_lua会覆盖echo。这和之前的hello world的例子是类似的。
配置(直接响应):
- location = /lua {
- content_by_lua 'ngx.say("Hello, Lua!")';
- }
输出:
- $ curl 'localhost/lua'
- $ Hello, Lua!
配置(在Lua中访问Nginx变量):
- location = /hello {
- content_by_lua 'local who = ngx.var.arg_who
- ngx.say("Hello, ", who, "!")';
- }
输出:
- $ curl 'localhost/hello?who=world
- $ Hello, 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
- ';
- }
- $ curl 'http://localhost/lua'
- $ Hello, world!
b. ngx.location.capture_multi
语法:res1,res2, ... = ngx.location.capture_multi({ {uri, options?}, {uri, options?}, ...})
与ngx.location.capture功能一样,可以并行的、非阻塞的发出多个子请求。这个方法在所有子请求处理完成后返回,并且整个方法的运行时间取决于运行时间最长的子请求,并不是所有子请求的运行时间之和。
配置:
- # 同时发送多个子请求(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)
- end
- ';
- }
输出:
- $ curl 'http://localhost/lua'
- $ 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:
配置:
- location / {
- internal;
- root html;
- }
- location /capture {
- content_by_lua '
- res = ngx.location.capture("/")
- echo res.body
- ';
- }
通过标准lua io访问磁盘文件:
配置:
- 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)
- end
- 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 | / |
在大文件的情况,capture就要略好于ngx_lua。
这里没有对Nginx读取静态文件进行优化配置,只是采用了sendfile。如果优化一下,可能nginx读取静态文件的性能会更好一些,这个目前还不 熟悉。所以,在Lua中进行各种IO时,都要通过ngx.location.capture发送子请求委托给Nginx事件模型,这样可以保证IO是非阻 塞的。
相关推荐
ngx_lua 允许开发者在 Nginx 的事件驱动、非阻塞 I/O 模型下编写服务器端的 Lua 应用程序,极大地提高了 Web 应用的并发处理能力。 首先,我们要理解 Nginx 的工作原理。Nginx 是一款高性能的 HTTP 和反向代理...
根据提供的文件内容,这篇文档主要围绕在UPYUN云平台上使用ngx_lua模块与Nginx结合进行...特别是结合了OpenResty所集成了大量高性能Lua库,使得Nginx的应用场景更加广泛,尤其适用于需要处理复杂逻辑的高并发Web服务。
1. **ngx_lua 模块介绍**:ngx_lua 是由 OpenResty(一个基于 Nginx 的高性能 Web 开发框架)提供的,它允许在 Nginx 配置文件中直接编写 Lua 脚本,实现了 HTTP 请求的实时处理,包括请求的接收、响应的构建以及与...
Lua支持轻量级的协程,可以在不引入多线程复杂性的前提下实现并发。 总结,Lua-Nginx-Module 0.10.13是Nginx的强大扩展,它将Lua语言的灵活性和Nginx的高性能结合起来,为Web开发提供了新的可能。通过学习和掌握lua...
【知识点详解】 本文将介绍如何在CentOS 7 64位...通过上述步骤,可以在CentOS 7系统上构建一个具备WAF功能的Nginx服务器,利用ngx_lua模块的强大灵活性来实施自定义的安全策略,保护Web应用程序免受各种网络攻击。
这个架构旨在解决高并发场景下,确保请求按序处理,防止资源争抢,优化服务性能的问题。以下是这个系统架构的关键知识点详解: 1. **Nginx与lua模块**: Nginx是一个高性能的HTTP和反向代理服务器,以其轻量级、...
在Nginx的生态系统中,NDK扮演着至关重要的角色,它提供了一系列的API和宏,帮助开发者更高效地构建自定义模块,从而扩展Nginx的功能。 首先,我们来了解下Nginx的基本架构。Nginx以其高效的事件驱动模型而闻名,它...
1. **基于 cosocket:** `lua-resty-http` 使用 OpenResty 的 cosocket 驱动,提供非阻塞 I/O 操作,能够高效处理高并发请求,提高性能。 2. **HTTP/1.1 支持:** 它支持完整的 HTTP/1.1 协议,包括 GET、POST、PUT...
ngx_openresty是一个高度集成的Nginx发行版,它包含了一个强大的动态脚本语言LuaJIT,并且集成了许多第三方模块,使得开发者能够轻松地构建高性能的Web应用和服务。ngx_openresty-1.9.3.2是该发行版的一个特定版本,...
总的来说,ngx_openresty通过结合Nginx的高性能和Lua的灵活性,为构建高性能、高并发的Web服务提供了一种强大且高效的方式。通过深入学习和掌握OpenResty,开发者可以构建出复杂而强大的Web应用程序。
在实际应用中,`lua-resty-beanstalkd` 可用于构建高性能的 Web 应用,比如处理异步任务(如发送邮件、图片处理等),或者在分布式系统中协调不同组件的工作。通过利用非阻塞 I/O 和 Lua 的简洁性,它可以帮助你构建...
同时,该版本还对内存管理和线程安全进行了优化,确保在高并发场景下的稳定性。 除了基础功能外,lua-nginx-module还支持一些高级特性,如定时任务、持久化存储、与外部服务的异步通信等。例如,通过`ngx.timer.at...
ngx_openresty是一个基于Nginx的全功能Web平台,它集成了LuaJIT脚本语言,使得开发者能够轻松地构建高性能的Web应用。ngx_openresty-1.9.7.1.tar.gz是一个压缩包文件,包含了ngx_openresty在1.9.7.1版本的所有源代码...
使用 `cosocket`,开发者可以在 Lua 脚本中编写高并发、低延迟的网络应用,非常适合于处理大量的并发请求。 Couchbase 是一个分布式文档数据库系统,支持 JSON 文档存储,并提供了丰富的查询语言 N1QL(N1xt Query ...
总之,《Lua程序设计》不仅介绍了Lua语言的基本特性和高级特性,还详细讲解了如何在Nginx环境中运用Lua,帮助读者从零开始,逐步成长为一名熟练的Lua开发者,能够利用Lua和Nginx构建高性能的Web服务。书中的实例和...
在ngx_devel_kit-0.3.0rc1版本中,它提供了许多实用的API和功能,以便开发者能够更方便地构建自定义模块或扩展Nginx的核心功能。 Nginx是一款高性能的HTTP和反向代理服务器,以其轻量级、高效的处理能力以及强大的...
在构建高性能和灵活的服务调度系统时,`nginx`作为一个强大的反向代理和负载均衡器,常常被用来处理复杂的网络流量管理。结合`lua`脚本语言以及特定的模块,如`dyups`,可以实现自定义的服务调度策略。本文将详细...
1. **模块安装**:首先,你需要解压"lua-nginx-module-master.zip"并将其添加到Nginx的源代码构建路径中。然后,使用`--add-module`选项配置Nginx的编译过程,指定lua-nginx-module的源代码目录。完成编译和安装后,...
1. **Nginx模块 ngx_lua**:Nginx与Lua的桥梁是ngx_lua模块,由OpenResty公司开发,它允许我们在Nginx配置文件中直接嵌入Lua脚本,实现动态处理请求。 2. **性能优势**:由于LuaJIT(Just-In-Time编译器)的存在,...