Nginx Lua编程基础(nginx luci)
liebian365 2024-11-17 13:21 14 浏览 0 评论
Lua是一门脚本动态语言,并不太适合做复杂业务逻辑的程序开发,但是,在高并发场景下,Nginx Lua编程是解决性能问题的利器。
Nginx Lua编程主要的应用场景如下:
- API网关:实现数据校验前置、请求过滤、API请求聚合、AB测试、灰度发布、降级、监控等功能,著名的开源网关Kong就是基于Nginx Lua开发的。
- 高速缓存:可以对响应内容进行缓存,减少到后端的请求,从而提升性能。比如,Nginx Lua可以和Java容器、Redis整合,由Java容器进行业务处理和数据缓存,而Nginx负责读缓存并进行响应,从而解决Java容器的性能瓶颈。
- 简单的动态Web应用:可以完成一些业务逻辑处理较少但耗费CPU的简单应用,比如模板页面的渲染。一般的Nginx Lua页面渲染处理流程为:从Redis获取业务处理结果数据,从本地加载XML/HTML页面模板,然后进行页面渲染。
- 网关限流:缓存、降级、限流是解决高并发的三大利器,Nginx内置了令牌限流的算法,但是对于分布式的限流场景,可以通过Nginx Lua编程定制自己的限流机制。
ngx_lua是Nginx的一个扩展模块,将Lua VM嵌入Nginx,请求时创建一个VM,请求结束时回收VM,这样就可以在Nginx内部运行Lua脚本,使得Nginx变成一个Web容器。以OpenResty为例,其提供了一些常用的ngx_lua开发模块:
- lua-resty-memcached:通过Lua操作memcache
- lua-resty-mysql:通过Lua操作MySQL
- lua-resty-redis:通过Lua操作Redis缓存
- lua-resty-dns:通过Lua操作DNS域名服务器
- lua-resty-limit-traffic:通过Lua进行限流
- lua-resty-template:通过Lua进行模板渲染
- lua-resty-jwt:通过Lua生成jwt
- lua-resty-kafka:通过Lua操作kafka
Lua脚本需要通过Lua解释器来解释执行,除了Lua官方的默认解释器外,目前使用广泛的Lua解释器叫做LuaJIT。LuaJIT采用C语言编写,被设计成全兼容标准Lua 5.1,因此LuaJIT代码的语法和标准Lua的语法没多大区别。但是LuaJIT的运行速度比标准Lua快数十倍。
Nginx Lua的执行原理
在OpenResty中,每个Worker进程使用一个Lua VM,当请求被分配到Worker时,将在这个Lua VM中创建一个协程,协程之间数据隔离,每个协程都具有独立的全局变量。
ngx_lua是将Lua VM嵌入Nginx,让Nginx执行Lua脚本,并且高并发、非阻塞地处理各种请求。Lua内置协程可以很好地将异步回调转换成顺序调用的形式。ngx_lua在Lua中进行的IO操作都会委托给Nginx的事件模型,从而实现非阻塞调用。开发者可以采用串行的方式编写程序,ngx_lua会在进行阻塞的IO操作时自动中断,保存上下文,然后将IO操作委托给Nginx事件处理机制,在IO操作完成后,ngx_lua会恢复上下文,程序继续执行,这些操作对用户程序都是透明的。
每个Worker进程都持有一个Lua解释器或LuaJIT实例,被这个Worker处理的所有请求共享这个实例。每个请求的context上下文会被Lua轻量级的协程分隔,从而保证每个请求是独立的。
ngx_lua采用one-coroutine-per-request的处理模型,对于每个用户请求,ngx_lua会唤醒一个协程用于执行用户代码处理请求,当请求处理完成后,这个协程会被销毁。每个协程都有一个独立的全局环境,继承于全局共享的、只读的公共数据。所以,被用户代码注入全局空间的任何变量都不会影响其他请求的处理,并且这些变量在请求处理完成后会被释放,这样就保证所有的用户代码都运行在一个sandbox(沙箱)中,这个沙箱与请求具有相同的生命周期。
得益于Lua协程的支持,ngx_lua在处理10000个并发请求时,只需要很少的内存。根据测试,ngx_lua处理每个请求只需要2KB的内存,如果使用LuaJIT就会更少。
Nginx Lua配置指令
ngx_lua定义的Nginx配置指令大致如下:
- lua_package_path:配置Lua外部库的搜索路径,搜索的文件类型为.lua。
- lua_package_cpath:配置Lua外部搜索库的搜索路径,搜索C语言编写的外部库文件。
- init_by_lua:Master进程启动时挂载的Lua代码块,常用于导入公共模块。
- init_by_lua_file:Master进程启动时挂载的Lua脚本文件。
- init_worker_by_lua:Worker进程启动时挂载的Lua代码块,常用于执行一些定时任务。
- init_worker_by_lua_file:Worker进程启动时挂载的Lua文件,常用于执行一些定时任务。
- set_by_lua:类似于rewrite模块的set指令,将Lua代码块的返回结果设置在Nginx的变量中。
- set_by_lua_file:同上,执行的是脚本Lua脚本文件。
- rewrite_by_lua:执行在rewrite阶段的Lua代码块,完成转发、重定向、缓存等功能。
- rewrite_by_lua_file:同上,执行的是Lua脚本文件。
- access_by_lua:执行在access阶段的Lua代码块,完成IP准入、接口权限等功能。
- access_by_lua_file:同上,执行的是Lua脚本文件。
- content_by_lua:执行在content阶段的Lua代码块,执行结果将作为请求响应的内容。
- content_by_lua_file:同上,执行的是Lua脚本文件。
- content_by_lua_block:content_by_lua的升级款,在一对花括号中编写Lua代码,而不需要做特殊字符转译。
- header_filter_by_lua:响应头部过滤处理的Lua代码块,可以用于添加设置响应头部信息,如Cookie相关属性。
- body_filter_by_lua:响应体过滤处理的Lua代码块,例如加密响应体。
- log_by_lua:异步完成日志记录的Lua代码块,例如既可以在本地记录日志,也可以记录到ETL集群。
ngx_lua配置指令在Nginx的HTTP请求处理阶段所处的位置如图:
常用配置指令
- lua_package_path指令:用于设置".lua"外部库的搜索路径,此指令的上下文为http配置块,默认值为LUA_PATH环境变量内容或者lua编译的默认值。
- 格式:lua_package_path lua-style-path-str。
- lua_package_cpath指令:用于设置Lua的C语言块外部库".so"(Linux)或".dll"(Windows)的搜索路径,此指令的上下文为http配置块。
- 格式:lua_package_cpath lua-style-cpath-str
http {
...
#设置“.lua”外部库的搜索路径,此指令的上下文为http配置块
#";;"常用于表示原始的搜索路径
lua_package_path "/foo/bar/?.lua;/blah/?.lua;;";
lua_package_cpath "/usr/local/openresty/lualib/?/?.so;/usr/local/openresty/lualib/?.so;;";
}
对于以上两个指令,OpenResty可以在搜索路径中使用插值变量。例如,可以使用插值变量$prefix或${prefix}获取虚拟服务器server的前缀路径,server的前缀路径通常在Nginx服务器启动时通过-p PATH命令在指定。
- init_by_lua指令:只能用于http上下文,运行在配置加载阶段。当Nginx的master进程在加载Nginx配置文件时,在全局Lua VM级别上运行由参数lua-script-str指定的Lua脚本块。若使用init_by_lua_file指令,后面跟lua文件的路径( lua_file_path),则在全局Lua VM 级别上运行lua_file_path文件指定的lua脚本。如果Lua脚本的缓存是关闭的,那么每一次请求都运行一次init_by_lua处理程序。
格式为:init_by_lua lua-script-str。
- lua_load_cache指令:用于启用或禁止Lua脚本缓存。可以使用的上下文为http、server、location配置块。默认开启。
格式为:lua_code_cache on | off
http {
...
#项目初始化
init_by_lua_file conf/luaScript/initial/loading_config.lua;
#调试模式,关闭lua脚本缓存
lua_code_cache on;
...
}
在缓存关闭的时,set_by_lua_file、content_by_lua_file、access_by_lua_file、content_by_lua_file等指令中引用的Lua脚本都将不会被缓存,所有的Lua脚本都将从头开始加载。
- set_by_lua指令:将Lua脚本块的返回结果设置在Nginx变量中。
格式为:set_by_lua $destVar lua-script-str params
location /set_by_lua_demo {
#set 指令定义两个Nginx变量
set $foo 1;
set $bar 2;
#调用Lua内联代码,将结果放入Nginx变量$sum
#Lua脚本的含义是,将两个输入参数$foo、$bar累积起来,然后相加的结果设置Nginx变量$sum中
set_by_lua $sum 'return tonumber(ngx.arg[1]) + tonumber(ngx.arg[2])' $foo $bar;
echo "$foo + $bar = $sum";
}
运行结果:
? work curl http://localhost/set_by_lua_demo
1 + 2 = 3
- access_by_lua指令:执行在HTTP请求处理11个阶段的access阶段,使用Lua脚本进行访问控制。运行于access阶段的末尾,总是在allow和deny这样的指令之后运行。
格式为:access_by_lua $destVar lua-script-str
location /access_demo {
access_by_lua 'ngx.log(ngx.DEBUG, "remote_addr = "..ngx.var.remote_addr);
if ngx.var.remote_addr == "192.168.56.121" then
return;
end
ngx.exit(ngx.HTTP_UNAUTHORIZED);
';
echo "hello world";
}
location /access_demo_2 {
allow "192.168.56.121";
deny all;
echo "hello world";
}
运行结果:
? work curl http://localhost/access_demo
<html>
<head><title>401 Authorization Required</title></head>
<body bgcolor="white">
<center><h1>401 Authorization Required</h1></center>
<hr><center>openresty/1.13.6.2</center>
</body>
</html>
#上述案例运行日志:
2022/02/15 10:32:17 [debug] 26293#0: *17 [lua] access_by_lua(nginx-lua-demo.conf:85):1: remote_addr = 127.0.0.1
2022/02/15 10:32:17 [info] 26293#0: *17 kevent() reported that client 127.0.0.1 closed keepalive connection
? work curl http://localhost/access_demo_2
<html>
<head><title>403 Forbidden</title></head>
<body bgcolor="white">
<center><h1>403 Forbidden</h1></center>
<hr><center>openresty/1.13.6.2</center>
</body>
</html>
#上述案例运行日志
2022/02/15 10:33:11 [error] 26293#0: *18 access forbidden by rule, client: 127.0.0.1, server: localhost, request: "GET /access_demo_2 HTTP/1.1", host: "localhost"
2022/02/15 10:33:11 [info] 26293#0: *18 kevent() reported that client 127.0.0.1 closed keepalive connection
- content_by_lua/content_by_lua_block指令:用于设置执行在content阶段的Lua代码块,执行结果将作为请求响应的内容。该指令用于location上下文。
格式为:content_by_lua lua-script-str
location /errorLog {
content_by_lua '
ngx.log(ngx.ERR, "this is an error log ");
ngx.say("错误日志调用成功");
';
}
location /infoLog {
content_by_lua '
ngx.log(ngx.ERR, "this is an info log ");
ngx.say("业务日志调用成功");
';
}
location /debugLog {
content_by_lua '
ngx.log(ngx.ERR, "this is an debug log ");
ngx.say("调试日志调用成功");
';
}
OpenResty v0.9.17版本以后,使用content_by_lua_block指令代替content_by_lua指令,避免对代码块中的字符串进行转译。
运行结果:
? work curl http://localhost/errorLog
错误日志调用成功
? work curl http://localhost/infoLog
业务日志调用成功
? work curl http://localhost/debugLog
调试日志调用成功
Nginx Lua的内置常量和变量
内置变量
- ngx.arg:类型为Lua table,ngx.arg.VARIABLE用于获取ngx_lua配置指令后面的调用参数。
- ngx.var:类型为Lua table,ngx.var.VARIABLE用于引用某个Nginx变量。前提是Nginx变量必须提前声明。
- ngx.ctx:类型为Lua table,可以用来访问当前请求的Lua上下文数据,其生存周期与当前请求相同
- ngx.header:类型为Lua table,用于访问HTTP响应头,可以通过ngx.header.HEADER形式引用某个头
- ngx.status:用于设置当前请求的HTTP响应码
内置常量
内置常量基本是见名知意的,可以根据后面的实战案例,加深理解。
核心常量
- ngx.OK(0)
- ngx.ERROR(-1)
- ngx.AGAIN(-2)
- ngx.DONE(-4)
- ngx.DECLINED(-5)
- ngx.nil
HTTP方法常量
- ngx.HTTP.GET
- ngx.HTTP.HEAD
- ngx.HTTP.PUT
- ngx.HTTP.POST
- ngx.HTTP.DELETE
- ngx.HTTP.OPTIONS
- ngx.HTTP.MKCOL
- ngx.HTTP.MOVE
- ngx.HTTP.PROPFIND
- ngx.HTTP.PROPPATCH
- ngx.HTTP.LOCK
- ngx.HTTP.UNLOCK
- ngx.HTTP.PATH
- ngx.HTTP.TRACE
HTTP状态码常量
- ngx.HTTP_OK(200)
- ngx.HTTP_CREATED(201)
- ngx.HTTP_SPECIAL_RESPONSE(300)
- ngx.HTTP_MOVED_PERMANENTLY(301)
- ngx.HTTP_MOVER_TEMPORARILY(302)
- ngx.HTTP_SEE_OTHER(303)
- ngx.HTTP_NOT_MODIFIED(304)
- ngx.HTTP_BAD_REQUEST(400)
- ngx.HTTP_UNAUTHORIZED(401)
- ngx.HTTP_FORBIDDEN(403)
- ngx.HTTP_NOT_FOUND(404)
- ngx.HTTP_NOT_ALLOWED(405)
- ngx.HTTP_GONE(410)
- ngx.HTTP_INTERNAL_SERVER_ERROR(500)
日志类型常量
- ngx.STDERR
- ngx.EMERG
- ngx.ALERT
- ngx.CRIT
- ngx.ERR
- ngx.WARE
- ngx.NOTICE
- ngx.INFO
- ngx.DEBUG
Nginx+LUA基础到此结束,下一篇开始实战!并在实战中掌握基础。
相关推荐
- “版本末期”了?下周平衡补丁!国服最强5套牌!上分首选
-
明天,酒馆战棋就将迎来大更新,也聊了很多天战棋相关的内容了,趁此机会,给兄弟们穿插一篇构筑模式的卡组推荐!老规矩,我们先来看10职业胜率。目前10职业胜率排名与一周前基本类似,没有太多的变化。平衡补丁...
- VS2017 C++ 程序报错“error C2065:“M_PI”: 未声明的标识符"
-
首先,程序中头文件的选择,要选择头文件,在文件中是没有对M_PI的定义的。选择:项目——>”XXX属性"——>配置属性——>C/C++——>预处理器——>预处理器定义,...
- 东营交警实名曝光一批酒驾人员名单 88人受处罚
-
齐鲁网·闪电新闻5月24日讯酒后驾驶是对自己和他人生命安全极不负责的行为,为守护大家的平安出行路,东营交警一直将酒驾作为重点打击对象。5月23日,东营交警公布最新一批饮酒、醉酒名单。对以下驾驶人醉酒...
- Qt界面——搭配QCustomPlot(qt platform)
-
这是我第一个使用QCustomPlot控件的上位机,通过串口精确的5ms发送一次数据,再将读取的数据绘制到图表中。界面方面,尝试卡片式设计,外加QSS简单的配了个色。QCustomPlot官网:Qt...
- 大话西游2分享赢取种族坐骑手办!PK趣闻录由你书写
-
老友相聚,仗剑江湖!《大话西游2》2021全民PK季4月激燃打响,各PK玩法鏖战齐开,零门槛参与热情高涨。PK季期间,不仅各种玩法奖励丰厚,参与PK趣闻录活动,投稿自己在PK季遇到的趣事,还有机会带走...
- 测试谷歌VS Code AI 编程插件 Gemini Code Assist
-
用ClaudeSonnet3.7的天气测试编码,让谷歌VSCodeAI编程插件GeminiCodeAssist自动编程。生成的文件在浏览器中的效果如下:(附源代码)VSCode...
- 顾爷想知道第4.5期 国服便利性到底需优化啥?
-
前段时间DNF国服推出了名为“阿拉德B计划”的系列改版计划,截至目前我们已经看到了两项实装。不过关于便利性上,国服似乎还有很多路要走。自从顾爷回归DNF以来,几乎每天都在跟我抱怨关于DNF里面各种各样...
- 掌握Visual Studio项目配置【基础篇】
-
1.前言VisualStudio是Windows上最常用的C++集成开发环境之一,简称VS。VS功能十分强大,对应的,其配置系统较为复杂。不管是对于初学者还是有一定开发经验的开发者来说,捋清楚VS...
- 还嫌LED驱动设计套路深?那就来看看这篇文章吧
-
随着LED在各个领域的不同应用需求,LED驱动电路也在不断进步和发展。本文从LED的特性入手,推导出适合LED的电源驱动类型,再进一步介绍各类LED驱动设计。设计必读:LED四个关键特性特性一:非线...
- Visual Studio Community 2022(VS2022)安装图文方法
-
直接上步骤:1,首先可以下载安装一个VisualStudio安装器,叫做VisualStudioinstaller。这个安装文件很小,很快就安装完成了。2,打开VisualStudioins...
- Qt添加MSVC构建套件的方法(qt添加c++11)
-
前言有些时候,在Windows下因为某些需求需要使用MSVC编译器对程序进行编译,假设我们安装Qt的时候又只是安装了MingW构建套件,那么此时我们该如何给现有的Qt添加一个MSVC构建套件呢?本文以...
- Qt为什么站稳c++GUI的top1(qt c)
-
为什么现在QT越来越成为c++界面编程的第一选择,从事QT编程多年,在这之前做C++界面都是基于MFC。当时为什么会从MFC转到QT?主要原因是MFC开发界面想做得好看一些十分困难,引用第三方基于MF...
- qt开发IDE应该选择VS还是qt creator
-
如果一个公司选择了qt来开发自己的产品,在面临IDE的选择时会出现vs或者qtcreator,选择qt的IDE需要结合产品需求、部署平台、项目定位、程序猿本身和公司战略,因为大的软件产品需要明确IDE...
- Qt 5.14.2超详细安装教程,不会来打我
-
Qt简介Qt(官方发音[kju:t],音同cute)是一个跨平台的C++开库,主要用来开发图形用户界面(GraphicalUserInterface,GUI)程序。Qt是纯C++开...
- Cygwin配置与使用(四)——VI字体和颜色的配置
-
简介:VI的操作模式,基本上VI可以分为三种状态,分别是命令模式(commandmode)、插入模式(Insertmode)和底行模式(lastlinemode),各模式的功能区分如下:1)...
你 发表评论:
欢迎- 一周热门
- 最近发表
-
- “版本末期”了?下周平衡补丁!国服最强5套牌!上分首选
- VS2017 C++ 程序报错“error C2065:“M_PI”: 未声明的标识符"
- 东营交警实名曝光一批酒驾人员名单 88人受处罚
- Qt界面——搭配QCustomPlot(qt platform)
- 大话西游2分享赢取种族坐骑手办!PK趣闻录由你书写
- 测试谷歌VS Code AI 编程插件 Gemini Code Assist
- 顾爷想知道第4.5期 国服便利性到底需优化啥?
- 掌握Visual Studio项目配置【基础篇】
- 还嫌LED驱动设计套路深?那就来看看这篇文章吧
- Visual Studio Community 2022(VS2022)安装图文方法
- 标签列表
-
- wireshark怎么抓包 (75)
- qt sleep (64)
- cs1.6指令代码大全 (55)
- factory-method (60)
- sqlite3_bind_blob (52)
- hibernate update (63)
- c++ base64 (70)
- nc 命令 (52)
- wm_close (51)
- epollin (51)
- sqlca.sqlcode (57)
- lua ipairs (60)
- tv_usec (64)
- 命令行进入文件夹 (53)
- postgresql array (57)
- statfs函数 (57)
- .project文件 (54)
- lua require (56)
- for_each (67)
- c#工厂模式 (57)
- wxsqlite3 (66)
- dmesg -c (58)
- fopen参数 (53)
- tar -zxvf -c (55)
- 速递查询 (52)