nginx的lua模块是针对nginx访问请求过程中没有后端又或者说需要在后端服务访问之前做某些控制,则此时lua脚本可以发挥其作用,由于lua的紧凑、快速以及内建协程,所以在保证高并发服务能力的同时极大地降低了业务逻辑实现成本。

其实在Nginx 世界里有两种类型的“请求”,一种叫做“主请求”(main request),而另一种则叫做“子请求”(subrequest)。

        所谓“主请求”,就是由 HTTP 客户端从 Nginx 外部发起的请求。比如,从浏览器访问Nginx就是一个“主请求”。

        而“子请求”则是由 Nginx 正在处理的请求在 Nginx 内部发起的一种级联请求。“子请求”在外观上很像 HTTP 请求,但实现上却和 HTTP 协议乃至网络通信一点儿关系都没有。它是 Nginx 内部的一种抽象调用,目的是为了方便用户把“主请求”的任务分解为多个较小粒度的“内部请求”,并发或串行地访问多个 location 接口,然后由这些 location 接口通力协作,共同完成整个“主请求”。当然,“子请求”的概念是相对的,任何一个“子请求”也可以再发起更多的“子子请求”,甚至可以玩递归调用(即自 己调用自己)。当一个请求发起一个“子请求”的时候,按照 Nginx 的术语,习惯把前者称为后者的“父请求”(parent request)。 

应用场景:

web应用:会进行一些业务逻辑处理,甚至进行耗CPU的模板渲染,一般流程:mysql/redis/http获取数据、业务处理、产生JSON/XML/模板渲染内容,比如

京东的列表页/商品详情页;

接入网关:实现如数据校验前置、缓存前置、数据过滤、API请求聚合、AB测试、灰度发布、降级、监控等功能,比如京东的交易大Nginx节点、无线部门正在开发的无线网关、单品页统一服务、实时价格、动态服务;

Web防火墙:可以进行IP/URL/UserAgent/Referer黑名单、限流等功能;

缓存服务器:可以对响应内容进行缓存,减少到后端的请求,从而提升性能;


使用Redis做分布式缓存;使用lua API来访问redis缓存;使用nginx向客户端提供服务,ngx_lua将lua嵌入到nginx,让nginx执行lua脚本,高并发,非阻塞的处理各种请求。url请求nginx服务器,然后lua查询redis,返回json数据。

location /main {  
echo_location /foo; # echo_location发送子请求到指定的location
echo_location /bar;
}
location /foo {
echo Tinywan_foo;
}
location /bar {
echo Tinywan_bar;

# service nginx restart [ OK ]
# curl 'http://localhost/main'
Tinywan_foo
Tinywan_bar

这里,main location就是发送2个子请求,分别到foo和bar,这就类似一种函数调用。  “子请求”方式的通信是在同一个虚拟主机内部进行的,所以 Nginx 核心在实现“子请求”的时候,就只调用了若干个 C 函数,完全不涉及任何网络或者 UNIX 套接字(socket)通信。我们由此可以看出“子请求”的执行效率是极高的。

协程(Coroutine)

协程类似一种多线程,与多线程的区别有: 

1. 协程并非os线程,所以创建、切换开销比线程相对要小。 

2. 协程与线程一样有自己的栈、局部变量等,但是协程的栈是在用户进程空间模拟的,所以创建、切换开销很小。

3. 多线程程序是多个线程并发执行,也就是说在一瞬间有多个控制流在执行。而协程强调的是一种多个协程间协作的关系,只有当一个协程主动放弃执行权,另一个协程才能获得执行权,所以在某一瞬间,多个协程间只有一个在运行。 

4. 由于多个协程时只有一个在运行,所以对于临界区的访问不需要加锁,而多线程的情况则必须加锁。 

5. 多线程程序由于有多个控制流,所以程序的行为不可控,而多个协程的执行是由开发者定义的所以是可控的。 

Nginx的每个Worker进程都是在epoll或kqueue这样的事件模型之上,封装成协程,每个请求都有一个协程进行处理。这正好与Lua内建协程的模型是一致的,所以即使ngx_lua需要执行Lua,相对C有一定的开销,但依然能保证高并发能力。

原理介绍

原理: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非常适合用于实现可扩展的、高并发的服务。


==========我是分割线==========


安装Lua模块

下载相关安装包

#yum install GeoIP-devel readline-devel gcc gcc-c++ lua lua-devel install zlib zlib-devel openssl openssl--devel pcre pcre-devel

#wget http://luajit.org/download/LuaJIT-2.0.4.tar.gz
#wget https://github.com/simpl/ngx_devel_kit/archive/v0.3.0.tar.gz或者wget https://github.com/simpl/ngx_devel_kit/release/v0.3.1.tar.gz
#wget https://github.com/openresty/lua-nginx-module/archive/v0.10.13.tar.gz

[root@localhost src]#ll
-rw-r--r-- 1 root root 847615 May 15 2015 LuaJIT-2.0.4.tar.gz
drwxrwxr-x 10 root root 225 Apr 23 2018 lua-nginx-module-0.10.13
drwxr-xr-x 9 1001 1001 186 Nov 4 17:17 nginx-1.18.0
-rw-r--r-- 1 root root 1039530 Apr 21 2020 nginx-1.18.0.tar.gz
drwxrwxr-x 9 root root 212 Jan 28 2018 ngx_devel_kit-0.3.1
drwxr-xr-x 9 1169 1169 8192 Nov 4 17:18 pcre-8.40
-rw-r--r-- 1 root root 2065161 Mar 29 2017 pcre-8.40.tar.gz
-rw-r--r-- 1 root root 624102 Nov 4 16:49 v0.10.13.tar.gz
-rw-r--r-- 1 root root 66542 Nov 4 14:46 v0.3.1.tar.gz

安装LuaJIT

#tar xf LuaJIT-2.0.4.tar.gz
#cd LuaJIT-2.0.4
#make PREFIX=/opt/luajit
#make install PREFIX=/opt/luajit
#ln -s /opt/luajit/lib/libluajit-5.1.so.2 /lib64/libluajit-5.1.so.2


添加环境变量

export LUAJIT_LIB=/usr/local/luajit/lib
export LUAJIT_INC=/usr/local/luajit/include/luajit-2.0
#source /etc/profile


安装nginx
用到的模块:pcre openssl

#cd nginx-1.18.0
./configure \
--user=nginx \
--group=nginx \
--prefix=/opt/nginx \
--with-http_stub_status_module \
--with-http_ssl_module \
--with-http_gzip_static_module \
--with-http_geoip_module \
--with-http_realip_module \
--with-pcre=../pcre-8.40
--add-module=../lua-nginx-module-0.10.13 \
--add-module=../ngx_devel_kit-0.3.1 \
--with-stream \
--with-stream_ssl_module

#make
#make install


验证配置指令和输出
修改nginx.conf配置文件,加入下面指令:

location / {
content_by_lua 'ngx.say("hello world!")';
}


重启nginx,用curl测试

# curl -i localhost
HTTP/1.1 200 OK
Server: nginx/1.4.1
Date: Tue, 24 Sep 2013 23:23:58 GMT
Content-Type: application/octet-stream
Transfer-Encoding: chunked
Connection: keep-alive
hello world!